JavaWeb学习笔记(十一)--会话与状态
1. 会话
1.1 什么是会话
会话可简单理解为:用户打开一个浏览器,点击多个超链接,访问服务器的多个web资源,然后关闭浏览器,整个过程称之为一个会话
1.2 会话过程中要解决的问题
每个用户在使用浏览器与服务器进行会话的过程中,各自会不可避免的产生一些数据,服务器要想办法为每个用户保存这些数据。例如:
多个用户点击超链接通过一个Servlet各自购买了一个商品,服务器应该想办法把每一个用户购买的商品保存在各自的地方,以便于这些用户点结账Servlet时,结账Servlet可以得到用户各自购买的商品为用户结账。
1.3 保存会话数据的两种技术
- Cookie:Cookie是客户端技术,服务器把每个用户的数据以Cookie的方式写个用户各自的浏览器。当用户使用浏览器再去访问服务器的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。
- Session:Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的Session对象,由于Session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的Session中,当用户再去访问服务器中的其他web资源时,其他web资源再从用户各自的Session中取出数据为用户服务。
Cookie技术:
Session技术:
2. Cookie
2.1 Cookie API
Cookie: Cookie(String name, String value) // Cookie的构造函数 String getValue() // 获取Cookie的值 void setValue(java.lang.String newValue) // 设置Cookie的值 int getMaxAge() // 获取cookie的有效期,以秒为单位 void setMaxAge(int expiry) // 设置cookie的有效期,以秒为单位 String getName() // 获取Cookie的名字 void setPath(String uri) // 设置cookie的有效路径 String getPath() // 获取cookie的有效路径, 不清楚为什么硬盘读取cookie的时候获取的path总是null,直接创建cookie,setPath后再getPath可以获取到路径
HttpServletRequest: Cookie[] getCookies() // 返回一个数组,包含客户端请求所包含的所有Cookie对象,如果没有Cookie则返回null
HttpServletResponse: void addCookie(Cookie cookie) // 向响应中增加一个Cookie
2.2 Cookie细节
- 一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(name)和设置的值(value)。
- 一个web站点可以给一个web浏览器发送多个Cookie,一个web浏览器也可以存储多个web站点提供的Cookie。
- 浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。(这些数据可能根据浏览器的不同而不同)
- 如果创建了一个Cookie,并把它发送到浏览器,默认情况下它是一个会话级别的Cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。若希望浏览器将该Cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。如果将最大时效设置为0,则是命令浏览器删除该Cookie。
- 删除Cookie时,path必须一致,否则不会删除
2.3 Cookie案例一:显示用户上次访问网站的时间
1 @WebServlet(name = "CookieDemo01") 2 public class CookieDemo01 extends HttpServlet { 3 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 4 5 } 6 7 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 8 //设置服务器端以UTF-8编码进行输出 9 response.setCharacterEncoding("UTF-8"); 10 //设置浏览器以UTF-8编码进行接收,解决中文乱码问题 11 response.setContentType("text/html;charset=UTF-8"); 12 PrintWriter out = response.getWriter(); 13 //获取浏览器访问访问服务器时传递过来的cookie数组 14 Cookie[] cookies = request.getCookies(); 15 out.write("您上次访问的时间是:"); 16 for (int i = 0; cookies != null && i < cookies.length; i++) { 17 Cookie cookie = cookies[i]; 18 if (cookie.getName().equals("lastAccessTime")) { 19 Long lastAccessTime =Long.parseLong(cookie.getValue()); 20 Date date = new Date(lastAccessTime); 21 out.write(date.toLocaleString()); 22 } 23 } 24 //用户访问过之后重新设置用户的访问时间,存储到cookie中,然后发送到客户端浏览器 25 Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");//创建一个cookie,cookie的名字是lastAccessTime 26 cookie.setMaxAge(600); 27 cookie.setPath("/cookie"); 28 //将cookie对象添加到response对象中,这样服务器在输出response对象中的内容时就会把cookie也输出到客户端浏览器 29 response.addCookie(cookie); 30 } 31 }
1 @WebServlet(name = "CookieDemo02") 2 public class CookieDemo02 extends HttpServlet { 3 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 4 5 } 6 7 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 8 response.setCharacterEncoding("UTF-8"); 9 response.setContentType("text/html;charset=UTF-8"); 10 PrintWriter out = response.getWriter(); 11 Cookie[] cookies = request.getCookies(); 12 out.write("您上次访问的时间是:"); 13 for (int i = 0; cookies != null && i < cookies.length; i++) { 14 Cookie cookie = cookies[i]; 15 if (cookie.getName().equals("lastAccessTime")) { 16 Long lastAccessTime =Long.parseLong(cookie.getValue()); 17 Date date = new Date(lastAccessTime); 18 out.write(date.toLocaleString()); 19 } 20 } 21 } 22 }
1 @WebServlet(name = "CookieDemo03") 2 public class CookieDemo03 extends HttpServlet { 3 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 4 5 } 6 7 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 8 response.setCharacterEncoding("UTF-8"); 9 response.setContentType("text/html;charset=UTF-8"); 10 PrintWriter out = response.getWriter(); 11 Cookie[] cookies = request.getCookies(); 12 out.write("您上次访问的时间是:"); 13 for (int i = 0; cookies != null && i < cookies.length; i++) { 14 Cookie cookie = cookies[i]; 15 if (cookie.getName().equals("lastAccessTime")) { 16 Long lastAccessTime =Long.parseLong(cookie.getValue()); 17 Date date = new Date(lastAccessTime); 18 out.write(date.toLocaleString()); 19 } 20 } 21 } 22 }
配置web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>CookieDemo01</servlet-name> <servlet-class>com.servlet.cookie.CookieDemo01</servlet-class> </servlet> <servlet-mapping> <servlet-name>CookieDemo01</servlet-name> <url-pattern>/cookie/CookieDemo01</url-pattern> </servlet-mapping> <servlet> <servlet-name>CookieDemo02</servlet-name> <servlet-class>com.servlet.cookie.CookieDemo02</servlet-class> </servlet> <servlet-mapping> <servlet-name>CookieDemo02</servlet-name> <url-pattern>/test/CookieDemo02</url-pattern> </servlet-mapping> <servlet> <servlet-name>CookieDemo03</servlet-name> <servlet-class>com.servlet.cookie.CookieDemo03</servlet-class> </servlet> <servlet-mapping> <servlet-name>CookieDemo03</servlet-name> <url-pattern>/cookie/CookieDemo03</url-pattern> </servlet-mapping> </web-app>
运行结果:
代码分析:
1. 添加Cookie时设置了有效路径和有效时间,只要访问的路径在/cookie下,时间在10分钟之内都,请求都会携带Cookie。
cookie.setMaxAge(600);
cookie.setPath("/cookie");
2. CookieDemo02 和 CookieDemo03都是获取上次访问的时间,不同的是CookieDemo02的访问URL是/test/CookieDemo02,而CookieDemo03的访问URL是/cookie/CookieDemo03,可以看到当Cookie设置了有效路径后,URL不在访问路径中,是无法获取Cookie的。如果不设置有效目录,那么默认的有效路径是创建cookie的Servlet的所在目录。例如:Cookie由/cookie/CookieDemo01创建的,那么cookie的有效目录就是:/cookie。
3. Cookie的有效期可以通过setMaxAge设置,值以秒单位。不设置则默认浏览器关闭就会删除。如果设置了大于0的值,Cookie会存储在硬盘上;如果设置是0,表示清除该cookie;如果添加Cookie时设置了有效路径,删除也要设置,否则删除不了。
cookie.setMaxAge(0);
cookie.setPath("/cookie");
2.4 Cookie案例二:显示商品浏览历史纪录
1 package com.servlet.cookie; 2 3 import javax.servlet.ServletException; 4 import javax.servlet.annotation.WebServlet; 5 import javax.servlet.http.Cookie; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 import java.io.IOException; 10 import java.io.PrintWriter; 11 import java.util.LinkedHashMap; 12 import java.util.Map; 13 14 /** 15 * 主页,显示所有商品和已浏览过的商品 16 */ 17 @WebServlet(name = "CookieDemo04") 18 public class CookieDemo04 extends HttpServlet { 19 20 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 21 response.setCharacterEncoding("UTF-8"); 22 response.setContentType("text/html;charset=UTF-8"); 23 PrintWriter out = response.getWriter(); 24 25 // 获取所有的书籍 26 out.write("网站的所有书籍:<br>"); 27 Map<String, Book> bookMap = DB.getAll(); 28 for (Map.Entry<String, Book> entry : bookMap.entrySet()) { 29 out.write("<a href=/cookie/CookieDemo05?id=" + entry.getKey() + " target='_blank'>" + entry.getValue().getName() + "</a><br>"); 30 } 31 32 out.write("<br>您浏览过的商品:<br>"); 33 Cookie[] cookies = request.getCookies(); 34 for (int i = 0; cookies != null && i < cookies.length; i++){ 35 if (cookies[i].getName().equals("BookHistory")) { 36 String[] ids = cookies[i].getValue().split("#"); 37 for (String id : ids) { 38 out.write(bookMap.get(id).getName() + "<br>"); 39 } 40 } 41 } 42 } 43 } 44 45 // 模拟数据库 46 class DB { 47 private static Map<String, Book> bookMap = new LinkedHashMap<String, Book>(); 48 static { 49 bookMap.put("1", new Book("1", "Java编程入门", "老张", 100)); 50 bookMap.put("2", new Book("2", "Java高级编程", "老李", 150)); 51 bookMap.put("3", new Book("3", "MySql入门", "老王", 50)); 52 bookMap.put("4", new Book("4", "Spring从入门到精通", "老张", 26)); 53 bookMap.put("5", new Book("5", "C++编程实践", "老李", 42)); 54 bookMap.put("6", new Book("6", "Python基础教程", "老李", 30)); 55 } 56 public static Map<String, Book> getAll() { 57 return bookMap; 58 } 59 } 60 61 // 实体类 62 class Book{ 63 private String id; 64 private String name; 65 private String author; 66 private double price; 67 68 public Book(String id, String name, String author, double price) { 69 this.id = id; 70 this.name = name; 71 this.author = author; 72 this.price = price; 73 } 74 75 public String getId() { 76 return id; 77 } 78 79 public void setId(String id) { 80 this.id = id; 81 } 82 83 public String getName() { 84 return name; 85 } 86 87 public void setName(String name) { 88 this.name = name; 89 } 90 91 public String getAuthor() { 92 return author; 93 } 94 95 public void setAuthor(String author) { 96 this.author = author; 97 } 98 99 public double getPrice() { 100 return price; 101 } 102 103 public void setPrice(double price) { 104 this.price = price; 105 } 106 }
1 package com.servlet.cookie; 2 3 import javax.servlet.ServletException; 4 import javax.servlet.annotation.WebServlet; 5 import javax.servlet.http.Cookie; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 import java.io.IOException; 10 import java.io.PrintWriter; 11 import java.util.Arrays; 12 import java.util.LinkedList; 13 import java.util.List; 14 15 /** 16 * 显示商品详情信息,并将当前浏览的商品加入Cookie中 17 */ 18 @WebServlet(name = "CookieDemo05") 19 public class CookieDemo05 extends HttpServlet { 20 21 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 22 23 response.setCharacterEncoding("UTF-8"); 24 response.setContentType("text/html;charset=UTF-8"); 25 PrintWriter out = response.getWriter(); 26 27 // 根据用户传过来的id,显示商品的详细信息 28 String id = request.getParameter("id"); 29 Book book = (Book) DB.getAll().get(id); 30 out.write(book.getId() + "<br>"); 31 out.write(book.getName() + "<br>"); 32 out.write(book.getAuthor() + "<br>"); 33 34 String cookieValue = buildCookie(request, id); 35 Cookie cookie = new Cookie("BookHistory", cookieValue); 36 cookie.setMaxAge(600); 37 cookie.setPath("/cookie"); 38 response.addCookie(cookie); 39 } 40 41 /** 42 * 已浏览商品的显示规则 43 * 1. 最多显示的3个商品 44 * 2. 最近时间浏览的商品显示在最前面 45 * 根据规则有以下几种情况: 46 * 1. 第一次浏览商品 47 * 2. 已经浏览过商品,当前查看的商品在已浏览商品中 48 * 3. 已经浏览过商品,当前查看的商品不在已浏览的商品中 49 * 3.1 浏览过的商品数量>=3 50 * 3.2 浏览过的商品数量<3 51 */ 52 private String buildCookie(HttpServletRequest request, String id) { 53 54 String bookHistory = null; 55 Cookie[] cookies = request.getCookies(); 56 for (int i = 0; cookies != null && i < cookies.length; i++) { 57 if (cookies[i].getName().equals("BookHistory")) { 58 bookHistory = cookies[i].getValue(); 59 } 60 } 61 if (bookHistory == null) { 62 return id; 63 } 64 65 List<String> idList = Arrays.asList(bookHistory.split("#")); 66 LinkedList<String> idLinkedList = new LinkedList<String>(idList); 67 if (idLinkedList.contains(id)) { 68 idLinkedList.remove(id); 69 idLinkedList.addFirst(id); 70 } else { 71 if (idLinkedList.size() < 3) { 72 idLinkedList.addFirst(id); 73 } else { 74 idLinkedList.removeLast(); 75 idLinkedList.addFirst(id); 76 } 77 } 78 StringBuffer stringBuffer = new StringBuffer(); 79 for (String bookID : idLinkedList) { 80 stringBuffer.append(bookID + "#"); 81 } 82 return stringBuffer.deleteCharAt(stringBuffer.length() - 1).toString(); 83 } 84 }
运行结果:
3. Session
在web开发中,服务器可以为每个用户浏览器创建一个会话对象(Session),一个浏览器独占一个Session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的Session中,当用户使用浏览器访问其他程序时,其他程序可以从用户的Session中取出该用户的数据,为用户服务。
Session和Cookie的主要区别在于:
- Cookie是把用户的数据写个用户的浏览器。
- Session是把用户的数据写到用户独占的Session中。
Session对象由服务器创建,使用request对象的getSession()方法得到Session对象。
3.1 Session API
HttpServletRequest: HttpSession getSession() // 返回当前请求对应的Session。如果没有Session,则创建一个并返回。 HttpSession getSession(boolean create) // 返回当前请求对应的Session。如果没有Session且create为true,则创建一个返回;如果没有Session且create为false,则返回null。
HttpSession: String getId() // 返回包含分配给此会话的唯一标识符的字符串。 Object getAttribute(String name) // 返回在此会话中使用指定名称绑定的对象,如果名称下没有绑定任何对象,则返回null。 void setAttribute(String name, Object value) // 将对象绑定到当前会话的指定属性(name)上,如果没有则创建该属性并绑定。如果传入的值为null,则与调用removeAttribute()相同。 void removeAttribute(String name) // 从此会话删除指定的属性。 如果会话没有该属性,则此方法不执行任何操作。
3.2 Session实现原理
当一个用户打开浏览器,服务器会为当前用户常见一个Session对象,当访问Servlet1后,Servlet1会给写给浏览器一个名为"JSESSIONID"的Cookie,当访问Servlet2时,请求会携带Cookie,Servlet2就可以解析Cookie获取存储的Session信息,从而获取用户数据。
当另一个用户打开浏览器时,跟上个步骤相同,只是创建了一个新的Session对象,这样Servelt2再根据新的SessionID获取数据。
举例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <a href="/SessionDemo01" target="_blank">购买</a> <br> <a href="/SessionDemo02" target="_blank">结账</a> <br> </body> </html>
1 /** 2 * 购买商品 3 */ 4 public class SessionDemo01 extends HttpServlet { 5 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException { 6 7 } 8 9 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws javax.servlet.ServletException, IOException { 10 HttpSession session = request.getSession(); 11 session.setAttribute("name", "洗衣机"); 12 } 13 }
1 /** 2 * 显示已购买的商品 3 */ 4 @WebServlet(name = "SessionDemo02") 5 public class SessionDemo02 extends HttpServlet { 6 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 7 8 } 9 10 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 11 12 response.setContentType("text/html;charset=UTF-8"); 13 PrintWriter out = response.getWriter(); 14 HttpSession session = request.getSession(false); 15 out.write("您购买的商品是:"); 16 if (session != null) { 17 out.write((String)session.getAttribute("name")); 18 } 19 } 20 }
运行结果:
关闭浏览器后重新访问SessionDemo02:
浏览器请求和响应:
服务器写入Cookie没有设置有效期的,关闭浏览器Cookie就没有,我们需要自己加入Cookie覆盖服务器写的Cookie,并设置有效期,这样关闭浏览器也可以获取到已购买的商品:
1 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 2 3 HttpSession session = request.getSession(); 4 session.setAttribute("name", "洗衣机"); 5 // 手动创建cooike,设置有效期,覆盖服务器默认的cookie 6 String sessionId = session.getId(); 7 Cookie cookie = new Cookie("JSESSIONID", sessionId); // 服务器创建的cookie名字叫JSESSIONID 8 cookie.setMaxAge(60*30); // 设置30分钟,多了也没有session30分钟就销毁了 9 response.addCookie(cookie); 10 }
3.3 IE禁用Cookie后的Session处理
如果用户的浏览器禁用Cookie,那么上面通过Cookie创建JSESSIONID的方式就行不通了,就需要使用另一种方式解决:URL重写
说明:IE11禁用Cookie已经不生效了,通过在web资源前清空Cookie,模拟此场景。
创建一个Servlet模拟网站首页:
/** * 模拟网站首页 */ @WebServlet(name = "SessionDemo03") public class SessionDemo03 extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); request.getSession(); // 对URL编码 String buyUrl = response.encodeURL("/SessionDemo01"); String payUrl = response.encodeURL("/SessionDemo02"); out.write("<a href='" + buyUrl + "'>购买</a><br>"); out.write("<a href='" + payUrl + "'>结账</a>"); } }
运行结果:
可以看到,当清除掉Cookie后,购买和结账的URL被重写了,后面都追加了一个jsessionid。request.getSession()可以通过这种方式获取到Session信息。
<a href='/SessionDemo01;jsessionid=3F7A8AA410672CDFD45C3F7CEB65783D'>购买</a><br>
<a href='/SessionDemo02;jsessionid=3F7A8AA410672CDFD45C3F7CEB65783D'>结账</a> --第一次访问
还有需要注意的一点,第一次访问的时候URL地址都重写,第二次访问的时候发现URL地址没有重写:
<a href='/SessionDemo01'>购买</a><br><a href='/SessionDemo02'>结账</a> --发现Cookie没有禁用,第二次访问不重写
这是因为第一次访问的时候服务器不知道有没有禁用Cookie,直接会对URL进行编码重写,第二次访问的时候服务器已经知道了没有禁用Cookie,发现用户带了Cookie,response.encodeURL()就不在进行重写。
3.4 Session 创建和销毁
Session创建时间:打开浏览器,当Servlet中调用request.getSession()时,就会创建Session。
Session销毁时间:当Session30分钟没人用了就会自动销毁(不管浏览器是否关闭),web.xml可以使用<session-config>中<session-timeout>标签配置session的失效时间,使用分钟的值。
<!--配置session的失效时长,值是分钟--> <session-config> <session-timeout>10</session-timeout> </session-config>
代码中可以使用session.invalidate()摧毁session
HttpSession session = request.getSession(); session.invalidate();