由于HTTP的无状态性,使得会话管理或者会话跟踪成为Web应用开发一个无法避免的 问题。
会话可以简单的理解为客户端用户打开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器的整个过程称为一个会话。即一个客户端用户和服务器端进行通讯的过程,也是客户端和服务器端之间的数据传输过程。
HTTP协议的无状态性导致web服务器无法区分一个HTTP请求是否为第一次访问,因此需要程序必须能管理会话,并为每个用户保存其与服务器交互过程中产生的数据。
会话管理就是管理浏览器客户端和服务器端之间会话过程中产生的数据。
Servlet有4中不同的状态保持技术:
– URL重写(token-based会话跟踪技术)
– 表单隐藏域(仅当网页中有表单时使用)
– cookies(客户端技术,数据保存在客户端)
– HTTPSession(服务器端技术,数据保存在服务器端)。
本文将就这四种技术做简单介绍。
先实现
URL重写
(Token-based会话跟踪技术)。Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
HttpServletRequest类的getParameter(String)方法获取的参数,除了之前HTML中form表单中的“name”属性,还可以是form表单提交时的“action”属性,还可以是HTML中url的内容。在URL重写中,用到的便是url的内容作为参数。
先分析第一个代码:
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Top10Servlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private List<String> londonAttractions;
private List<String> parisAttractions;
public Top10Servlet() {
super();
}
@Override
public void init() throws ServletException { // 用户第一次访问时,Tomcat初始化Servlet,init()方法被调用。
londonAttractions = new ArrayList<String>(10); // 赋值类的成员变量
londonAttractions.add("1.Buckingham Palace");
londonAttractions.add("2.Lodon Eye");
londonAttractions.add("3.British Museum");
londonAttractions.add("4.National Gallery");
londonAttractions.add("5.Big Ben");
londonAttractions.add("6.Tower of London");
londonAttractions.add("7.Natural History Museum");
londonAttractions.add("8.Canary Wharf");
londonAttractions.add("9.2012 Olympic Park");
londonAttractions.add("10.ST Paul's Cathedral");
parisAttractions = new ArrayList<String>(10); // 赋值类的成员变量
parisAttractions.add("1.Eiffel Tower");
parisAttractions.add("2.Notre Dame");
parisAttractions.add("3.The Louver");
parisAttractions.add("4.Champs Elysees");
parisAttractions.add("5.Arc de Triomphe");
parisAttractions.add("6.Sainte Chapelle Church");
parisAttractions.add("7.Les Invalides");
parisAttractions.add("8.Muess d'Orsay");
parisAttractions.add("9.Montmarte");
parisAttractions.add("10.Sacre Couer Basilica");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String city = request.getParameter("city"); // 获取token的值,该token随URL传递过来
if (city != null && (city.equals("london") || city.equals("paris"))) {
showAttractions(request, response, city);
} else { showMainPage(request, response);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
private void showMainPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter writer = response.getWriter(); // 向客户端发送信息
writer.println("<html>");
writer.println("<head>");
writer.println("<title>Top10 Tourist Atrractions</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("Please select a city:<br />");
writer.println("<a href='?city=london'>London</a><br />"); // 相对url,由 http://localhost:8080/top10/top10
writer.println("<a href='?city=paris'>Paris</a><br />"); // 变为 http://localhost:8080/top10/top10?city=london
writer.println("</body>"); // 并将后者发送到服务器
writer.println("</html>");
}
private void showAttractions(HttpServletRequest request, HttpServletResponse response, String city) throws ServletException, IOException {
int page = 1;
String pageParameter = request.getParameter("page"); // 获取token的值
if (pageParameter != null) {
try {
page = Integer.parseInt(pageParameter);
}catch(NumberFormatException e) {
e.printStackTrace();
}
if (page > 2) {
page = 1;
}
}
List<String> attractions = null;
if (city.equals("london")) {
attractions = londonAttractions;
} else {
attractions = parisAttractions;
}
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<head>");
writer.println("<title>Top 10 Tourist Attractions</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("<a href='top10'>Select City</a>"); // 相对url 由 http://localhost:8080/top10/top10?city=london
writer.println("<hr />"); // 变为 http://localhost:8080/top10/top10
writer.println("Page " + page);
writer.println("<hr />");
int start = page * 5 -5;
for (int i = start; i < start + 5; i++) {
writer.println(attractions.get(i) + "<br />");
}
writer.print("<hr style='color:blue' />");
writer.println("<a href='?city=" + city + "&page=1'>Page 1</a>"); // 由 localhost:8080/top10/top10?city=london 变为 localhost:8080/top10/top10?city=london&page=1
writer.println(" <a href='?city=" + city + "&page=2'>Page 2</a>"); // 由 localhost:8080/top10/top10?city=london 变为 localhost:8080/top10/top10?city=london&page=2
writer.println("</body>");
writer.println("</html>");
}
}
实现的功能如下:
点击“London”,
点击“Page2”:
首先,重写doGet()方法,第一次Get,这时取得的“city”参数为空,那么调用 showMainPage(request, response)方法,即展示选择城市的页面。
在showMainPage(request, response)方法中,以下是关键代码:
writer.println("<a href='?city=london'>London</a><br />");
writer.println("<a href='?city=paris'>Paris</a><br />");
第一个链接的url为?city=london,链接名为London,即点击“London”,浏览器的地址栏就会变成http://localhost:8080/top10?city=london,而此时,会又一次调用doGet()方法,此时取得的“city”属性不再是空,正是url内容中的“london”。调用 showAttractions(request, response, city)方法。
在 showAttractions(request, response, city)方法中,与第一次Get类似,此时取得的“page”参数为空,先为其赋值为1,即相当于还没有选择page,就展示page1 的内容。而后将相应page和相应List输出即可。
再看一个代码,是要实现省,市,区三级联动。
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TokenServlet extends HttpServlet {
private Map<String,List<String>>cityMap=new HashMap<>();
private Map<String,List<String>>countryMap=new HashMap<>();
@Override
public void init()
{List<String>ShanXi=new ArrayList<>();
ShanXi.add("西安市");
ShanXi.add("宝鸡市");
ShanXi.add("铜川市");
ShanXi.add("咸阳市");
cityMap.put("ShanXi",ShanXi);
List<String>Xian=new ArrayList<>();
Xian.add("临潼区");
Xian.add("灞桥区");
Xian.add("长安区");
countryMap.put("西安市",Xian);}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer=resp.getWriter();
String pro=req.getParameter("pro");
String city=req.getParameter("city");
if(pro==null&&city==null)
{
resp.setContentType("text/html'");
writer.println("<html>");
writer.println("<head>");
writer.println("<title>Pro-city-reg</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("Please select a province:<br />");
writer.println("<a href='?pro=ShanXi'>陕西省</a><br />");
writer.println("</body>");
writer.println("</html>"); }
else
{if(pro==null)
{writer.write("pro省份参数不能为空");}
else
{if(city==null) //省份不为空
{List<String> cityList=cityMap.get(pro);
StringBuilder sb=new StringBuilder();
for(String c:cityList)
{sb.append("<a href='/token")
.append("?")
.append("pro=")
.append(pro)
.append("&")
.append("city=")
.append(c)
.append("'>")
.append("陕西省")
.append(",")
.append(c)
.append("</a>")
.append("</br>");
}
//http://localhost:8080/token?pro=ShanXi&city=西安市
writer.write(sb.toString());}
else
{List<String> cityList=cityMap.get(pro);
if(cityList.contains(city))
{StringBuilder sb=new StringBuilder();
for(String country:countryMap.get(city))
{sb.append("<a href='/token")
.append("?")
.append("pro=").append(pro)
.append("&")
.append("city=")
.append(city)
.append(" '>")
.append(pro)
.append(",")
.append(city)
.append(",")
.append(country)
.append("</a>")
.append("</br>"); }
writer.write(sb.toString());}
else
{writer.write(city+"不属于"+pro);}
}
}
}
}
}
在这段代码中简单写了一个省份“陕西”及其对应的市“西安”对应的区。可是运行之后,在浏览器的效果却是:
选择“陕西省”后:
再选择“西安市”:
报了400的错误,再看Message那一项写着: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986。
原来,错误原因是:当在浏览器中访问时 URL中带有特殊字符,如花括号冒号时,就会出现这个错误。例如:http://localhost:8080/index.do?{id:123} 纠正方法可参考这个博客:https://www.cnblogs.com/dygrkf/p/9088370.html
此时的url重写成了http://localhost:8080/token?pro=ShanXi&city=西安市,包含中文,自然是GET处理不来的 ,因此,将程序中的url都改为只有英文的情况,如下所示:
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TokenServlet extends HttpServlet {
private Map<String,List<String>>cityMap=new HashMap<>();
private Map<String,List<String>>countryMap=new HashMap<>();
@Override
public void init()
{List<String>ShanXi=new ArrayList<>();
ShanXi.add("XiAn");
ShanXi.add("BaoJi");
ShanXi.add("TongChuan");
ShanXi.add("XianYang");
cityMap.put("ShanXi",ShanXi);
List<String>Xian=new ArrayList<>();
Xian.add("LinTong");
Xian.add("BaQiao");
Xian.add("ChangAn");
countryMap.put("XiAn",Xian);}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer=resp.getWriter();
String pro=req.getParameter("pro");
String city=req.getParameter("city");
if(pro==null&&city==null)
{
resp.setContentType("text/html'");
writer.println("<html>");
writer.println("<head>");
writer.println("<title>Pro-city-reg</title>");
writer.println("</head>");
writer.println("<body>");
writer.println("Please select a province:<br />");
writer.println("<a href='?pro=ShanXi'>陕西省</a><br />");
writer.println("</body>");
writer.println("</html>"); }
else
{if(pro==null)
{writer.write("pro省份参数不能为空");}
else
{if(city==null) //省份不为空
{List<String> cityList=cityMap.get(pro);
StringBuilder sb=new StringBuilder();
for(String c:cityList)
{sb.append("<a href='/token")
.append("?")
.append("pro=")
.append(pro)
.append("&")
.append("city=")
.append(c)
.append("'>")
.append("陕西省")
.append(",")
.append(c)
.append("</a>")
.append("</br>");
}
writer.write(sb.toString());}
else
{List<String> cityList=cityMap.get(pro);
if(cityList.contains(city))
{StringBuilder sb=new StringBuilder();
if(countryMap.get(city)==null)
writer.write("暂无");
else{ for(String country:countryMap.get(city))
{sb.append("<a href='/token")
.append("?")
.append("pro=").append(pro)
.append("&")
.append("city=")
.append(city)
.append(" '>")
.append(pro)
.append(",")
.append(city)
.append(",")
.append(country)
.append("</a>")
.append("</br>"); }
writer.write(sb.toString());}}
else
{writer.write(city+"不属于"+pro);}}
}
}
}
}
运行后,在浏览器中的显示为:
点击“陕西省”后:
点击第一个选项后:
若是点击第二个选项,即BaoJi,浏览器的显示结果为:
URL重写适合于token无须在太多URL之间传递的情况下,然而它有以下限制:
-
URL在某些浏览器上最大长度为2000字符。
扫描二维码关注公众号,回复: 8597381 查看本文章 -
若要传递值到下一个资源,需要将值插入到链接都必须带值,因此当一个页面存在很多链接时,处理过程会是一个不小的挑战。
-特殊字符不易处理,例如空格、与和问号等必须用base64编码。 -
所有的信息都是可见的,安全性有要求的情况下不合适。
综上,URL重写仅适合于信息仅在少量页面间传递,且信息本身不敏感。
接下来,说说**
表单隐藏域技术
**。
使用隐藏域来保持状态类似于URL重写技术,但不是将值附加到URL上,而是返到HTML表单的隐藏域中。当表单提交时,隐藏域的值也同时提交到服务器端。隐藏域技术仅当网页有表单时有效。该技术相对于URL重写的优势在于没有字符数限制,同时无需额外的编码,但是该技术同URL重写一样,不适合跨越多个界面。
代码如下:
import javax.jws.Oneway;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.util.HashMap;
import java.util.Map;
public class HiddenServlet extends HttpServlet {
protected static Map<String, Person>personMap=new HashMap<>();
@Override
public void init() throws ServletException {super.init();
Person java=new Person("1001","java",20);
Person php=new Person("1002","PHP",18);
Person c=new Person("1003","c",30);
personMap.put("1001",java);
personMap.put("1002",php);
personMap.put("1003",c); }
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {res.setContentType("text/html;charset=UTF-8");
PrintWriter writer=res.getWriter();
writer.append("<!DOCTYPE html>\n"+"<html lang=\"en\">\n"+"<head>\n"
+"<meta charset=\"UTF-8\">\n"
+"<title>用户信息</title>\n"
+"</head>\n"
+"<body>\n"
+"<h1>用户信息列表</h1>\n"
+"<table>\n"
+"<thead>\n"
+"<tr>\n"
+"<td>编号</td>\n"
+"<td>姓名</td>\n"
+"<td>年龄</td>\n"
+"</tr>\n"
+"</thead>\n"
+"<tbody>");
for(Map.Entry<String,Person> entry:personMap.entrySet())
{Person person=entry.getValue();
writer.append("<tr>")
.append("<td>")
.append("<a href='/person?id=")
.append(person.getId())
.append("'>")
// .append(String.valueOf(person.id))
.append(person.id)
.append("</a>")
.append("</td>")
.append("<td>")
.append(person.getName())
.append("</td>")
.append("<td>")
.append(String.valueOf(person.age))
.append("</td>")
.append("</tr>");}
writer.append("</tbody>"+"</table>"+"</body>"+"</html>");}
public static class Person {
private String id;
private String name;
private int age;
public Person(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
import com.sun.deploy.net.HttpResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class PersonServlet extends HiddenServlet{
@Override
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {res.setContentType("text/html;charset=UTF-8");
res.setContentType("text/html;charset=UTF-8");
PrintWriter writer=res.getWriter();
String id=req.getParameter("id");
if(id==null)
{writer.append("参数不能为空");}
else
{Person person=personMap.get(id);
writer.append("<!DOCTYPE html>\n"+
"<html lang=\"en\">\n"
+"<head>\n"
+"<meta charset=\"UTF-8\">\n"
+"<title>修改人员信息</title>\n"
+"</head>\n"
+"<body>\n"
+"<h1>修改人员信息</h1>\n"
+"<form method=\"post\" action=\"/person\">\n"
+"<input type=\"text\" name=\"id\" hidden=\"hidden\" value=\""+person.getId()+"\"/>\n"
+"姓名:<input type=\"text\" name=\"name\" value=\""+person.getName()+"\" placeholder=\"请输入姓名\">\n"
+"年龄:<input type=\"text\" name=\"age\" value=\"" +person.getAge()+"\" placeholder=\"请输入年龄\">\n"
+"<input type=\"submit\" value=\"保存更新\">\n"
+"</form>\n"
+"</body>\n"
+"</html>\n");
}}
@Override
protected void doPost(HttpServletRequest req,HttpServletResponse res) throws IOException {res.setContentType("text/html;charset=UTF-8");
PrintWriter writer=res.getWriter();
String id=req.getParameter("id");
if(id==null)
{writer.append("暂时不支持"); }
else
{String name=req.getParameter("name");
String agestr=req.getParameter("age");
Person person=personMap.get(id);
person.setAge(Integer.parseInt(agestr));
person.setName(name);
writer.append("<a href='/hidden'>")
.append("回到列表")
.append("</a>");
}
}
}
HiddenServlet HiddenServlet >
<servlet-mapping>
<servlet-name>HiddenServlet</servlet-name>
<url-pattern>/hidden</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>PersonServlet</servlet-name>
<servlet-class>PersonServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PersonServlet</servlet-name>
<url-pattern>/person</url-pattern>
</servlet-mapping>
而且这时的地址栏也变成了之前URL重写中token的形式,request.getParameter(“id”);也是从url取得“id”属性的。
在PersonServlet类中,HTML的效果如下所示:
注意到,有一个隐藏的input用于存放“id”属性。这个form的提交method是post,所以接下来调用doPost()方法。在doPost()方法中,通过表单提交的数据,调用getParameter(String)方法取得“id”属性,而这个“id”正是隐藏的那个“id”。接下来,表单提交还可以取得“name”、“age”属性,再通过person类的set()方法改变属性值。
点击“保存更新”提交表单后,地址栏跳转到/person,调用doPost(),浏览器显示:
点击“回到列表”,跳转到/hidden。浏览器显示为:
这时的用户信息已被更改。
如果需要再多个页面之间传递信息,那么以上两种技术显然成本较高,因为需要在每个页面都进行相应的处理,而Cookies可以存储在多个页面之间传递的信息。
接下来,说说
Cookies
当我们使用京东商城时,发现有时打开计算机后访问这个网站时,我们的名字已经出现在菜单条上了,根本不需要登录。样实现的:将一些信息存放到浏览器所在的计算机的硬盘中,即使是重启计算机,这些信息也还在,这样的技术就叫Cookies。
Cookies是一个很少的信息片段,可自动在浏览器和Web服务器之间交互。它作为HTTP header的一部分,其传输由HTTP协议控制。此外,可以控制Cookies的有效时间。浏览器通常支持每个网站高达20个Cookies。用户可以通过改变浏览器设置来拒绝接受Cookies或者人为清除Cookies。
服务器可以利用Cookies包含信息的任意性来筛选并经常性维护这些信息,以判断在HTTP传输中的状态。Cookies最典型的应用是判定注册用户是否已经登录网站,用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续,这些都是Cookies的功用。另一个重要应用场合是“购物车”之类处理。用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都会写入Cookies,以便在最后付款时提取信息。
这是关于Cookie的文档说明:
写代码如下:
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/*将HTML从一个Servlet发送到浏览器。表单视图*/
public class UseCookies extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer=resp.getWriter();
writer.append("<html>")
.append("<head>")
.append("<meta charset='UTF-8'>")
.append("<title>Form</title>")
.append("<head>")
.append("<body>")
.append("<form method='POST' action='/useCookies'>")
.append("请输入姓名:")
.append("<input name='name' type='text' value=''/>")
.append("<input type='submit' value-'提交'>")
.append("</form>")
.append("</body>")
.append("</html>");
}
@Override
protected void doPost(HttpServletRequest req,HttpServletResponse resp) throws IOException {
String name = req.getParameter("name");
resp.setContentType("text/html;charset=UTF-8");
Cookie[] cookies = req.getCookies();
for (Cookie cookie : cookies) {
System.out.println("name:" + cookie.getName() + "\ndomain:" + cookie.getDomain() + "\nvalue:" + cookie.getValue() + "\nmaxage:" + cookie.getMaxAge());
}}}
则在浏览器中输入
点击提交后,在IDEA的控制台会输出:
当doGet()方法中添加
Cookie c = new Cookie("username","peter");// 新建一个Cookie对象
c.setMaxAge(24*60*60); // 设置过期时间1天,以秒为单位
resp.addCookie(c); // 保存cookie到客户端
在按上述步骤访问浏览器后,控制台的输出为:
(Cookie中的内容大多数经过了加密处理,因此一般用户看来只是一些毫无意义的字母数字组合,只有服务器的CGI处理程序才知道它们真正的含义。)
其中,可以看到,生成并获取Cookie是成功的。Cookie对象有几个属性,如name,domain,value,maxage等,
domain是域,maxage是cookie 的最大生存时间,以秒为单位。 正值表示 cookie 将在经过该值表示的秒数后过期。注意,该值是 cookie 过期的最大 生存时间,不是 cookie 的当前生存时间。 负值意味着 cookie 不会被持久存储,将在 Web 浏览器退出时删除。0 值会导致删除 cookie。 (因此删除Cookie操作只需setMaxAge(0)即可。)
如果你创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie:(maxage=-1)存储在浏览器的内存中,用户退出浏览器之后被删除。如果你希望浏览器将该cookie存储在磁盘上,则
需要使用maxAge,并给出一个以秒为单位的时间。
https://blog.csdn.net/mastershaw/article/details/79959944这篇关于Java Cookie的博客写的很好,可以参考一下。
最后一个技术是
HttpSession
这是个接口,文档如下:
在doPost()方法中写:
HttpSession httpSession=req.getSession();
System.out.println(httpSession);
重复之前的提交,则控制台的输出为:
- 保存session id的几种方式
A.保存session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。
B.由于cookie可以被人为的禁止,必须有其它的机制以便在cookie被禁止时仍然能够把session id传递回服务器,经常采用的一种技术叫做URL重写,就是把session id附加在URL路径的后面,附加的方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。网络在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。
C.另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。
那么,Cookie和Session之间有什么联系呢。
简单点说,每一个session对象都有一个sessionId,而在cookie数组中,又一个元素叫做JSESSIONID,服务器将session对象的sessionId,以名称叫做JSESSIONID,值为sessionId的形式存入cookie数组中,这样cookie和session就发生了关联。(参考博客https://www.cnblogs.com/mei0619/p/8259031.html)
上面的描述可以用下图来表示。
在此讨论一个问题:
当我们清空浏览器的时候,Session会消失吗?
这个问题包含着一些陷阱。因为很多时候当我们清空浏览器以后,确实需要重新登录系统才可以操作,所以很多人自然而然认为清空浏览器缓存(包含cookie)以后。Session就会消失。
其实这种结论是错误的。要知道,Session是存在于服务器的,你清除浏览器缓存,只是清除了cookie,跟Session一点关系都没有。那么为什么我们却不能访问网站,而需要重新登录了呢?
一般的web项目会通过Session来判断用户是否有登录,常用的判断语句是if(session.get(“userId”)==null。如果为空,则表示需要重新登录。这时候其实隐式地调用了getSession()方法,这就回到了上一步我们所讲的Session获取的具体步骤了。
因为清空了浏览器缓存,这时候cookie数组中必定不会有JSESSIONID这个cookie,所以必须得新建一个Session,用新的sessionId来给JSESSIONID这个cookie赋值。由于是新建的session,session中必定没有userId这样的属性值,所以判断结果自然为空,所以需要重新登录。这次赋值以后,下一次再请求该网站的时候,由于cookie数组中已经有了JSESSIONID这个cookie,并且能通过该JSESSIONID的值找到相应的session,所以就不需要再重新登录了。