什么是会话:用户打开浏览器,点击多个超链接,访问服务器的多个web资源,然后关闭浏览器,整个过程就称为一个会话;
会话过程需要解决的问题:每个用户在使用浏览器与服务器进行会话的过程中,都可能会产生一些数据,这些输入如何来进行保存?比如用户在购物网站浏览的商品记录,用户添加购物车的记录等等这些信息如何进行存储?在程序中会话跟踪是一件非常重要的事情,一个用户的所有请求操作都应该属于同一个会话,而另一个人的所有请求操作应该属于另一个人,二者不能混淆!当想到需要在保存数据时,我们首先肯定会想到使用域对象,这些数据是否可以使用Request或者ServletContext对象来保存呢?
首先我们举例说明:登录的场景
1 Context对象:
小张: 输入“张三” (保存数据: context.setAttribute("name","张三")) -> 用户主页(显示“张三”)
小李: 输入“李四”(保存数据:context.setAttribute("name","李四")) -> 用户主页(显示“李四”)
context是所有用户公有的资源,因此当小李登录后,用户主页将全部显示为“李四”,新的数据将会覆盖原有的数据,因此不能够使用context对象;
2 Request对象:该对象只在同一个页面有效,当需要进行页面跳转的时候,显然必须使用转发技术来实现,因此Request对象也不能够有效解决该问题。
会话技术
为了解决上述的问题,这里引入了会话技术,其中会话技术主要分为两个部分,cookie技术和session技术,前者将数据保存在客户端,后者将数据保存在服务器。
- Cookie技术:Cookie是客户端技术,服务器把每个用户的数据以cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。
- Session技术:Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它web资源时,其它web资源再从用户各自的session中取出数据为用户服务。
(一) Cookie技术
1.1 Cookie技术核心
Cookie类:用于存储会话数据
1)构造Cookie对象
- Cookie(java.lang.String name, java.lang.String value)
2)设置cookie
- void setPath(java.lang.String uri) :设置cookie的有效访问路径
- void setMaxAge(int expiry) : 设置cookie的有效时间
- void setValue(java.lang.String newValue) :设置cookie的值
3)发送cookie到浏览器端保存
- void response.addCookie(Cookie cookie) : 发送cookie
4)服务器接收cookie:
- Cookie[] request.getCookies() : 接收cookie,返回所有cookie的数据信息
1.2 Cookie原理
1)服务器创建cookie对象,把会话数据存储到cookie对象中。
- new Cookie("name","value");
2)服务器发送cookie信息到浏览器
- response.addCookie(cookie);
举例: set-cookie: name=Infaraway ( 隐藏发送了一个set-cookie名称的响应头 )
3)浏览器得到服务器发送的cookie,然后保存在浏览器端。
4)浏览器在下次访问服务器时,会带着cookie信息
举例: cookie: name=Infaraway (隐藏带着一个叫cookie名称的请求头)
5)服务器接收到浏览器带来的cookie信息
- request.getCookies();
1 package com.infaraway.servlet; 2 3 import javax.servlet.ServletException; 4 import javax.servlet.http.Cookie; 5 import javax.servlet.http.HttpServletRequest; 6 import javax.servlet.http.HttpServletResponse; 7 import java.io.IOException; 8 9 /** 10 * Created by :Infaraway 11 * DATE : 2017/3/25 12 * Time : 22:08 13 * Funtion : cookie测试 14 */ 15 public class CreateCookie { 16 public void doGet(HttpServletRequest request, HttpServletResponse response) 17 throws ServletException, IOException { 18 //1.创建Cookie对象 19 Cookie cookie1 = new Cookie("name","Infaraway"); 20 //Cookie cookie2 = new Cookie("email","Infaraway@qq.com"); 21 //Cookie cookie1 = new Cookie("email","Infaraway@qq.com"); 22 23 /** 24 * 1)设置cookie的有效路径。默认情况:有效路径在当前web应用下。 /cookie 25 */ 26 //cookie1.setPath("/cookie"); 27 //cookie2.setPath("/basisJavaWeb"); 28 /** 29 * 2)设置cookie的有效时间 30 * 正整数:表示cookie数据保存浏览器的缓存目录(硬盘中),数值表示保存的时间。 31 * 负整数:表示cookie数据保存浏览器的内存中。浏览器关闭cookie就丢失了! 32 * 零:表示删除同名的cookie数据 33 */ 34 //cookie1.setMaxAge(20); //20秒,从最后不调用cookie开始计算 35 cookie1.setMaxAge(-1); //cookie保存在浏览器内存(会话cookie) 36 //cookie1.setMaxAge(0); 37 38 //2.把cookie数据发送到浏览器(通过响应头发送: set-cookie名称) 39 //response.setHeader("set-cookie", cookie.getName()+"="+cookie.getValue()+",email=eric@qq.com"); 40 //推荐使用这种方法,避免手动发送cookie信息 41 response.addCookie(cookie1); 42 //response.addCookie(cookie2); 43 //response.addCookie(cookie1); 44 45 //3.接收浏览器发送的cookie信息 46 /*String name = request.getHeader("cookie"); 47 System.out.println(name);*/ 48 Cookie[] cookies = request.getCookies(); 49 //注意:判断null,否则空指针 50 if(cookies!=null){ 51 //遍历 52 for(Cookie c:cookies){ 53 String name = c.getName(); 54 String value = c.getValue(); 55 System.out.println(name+"="+value); 56 } 57 }else{ 58 System.out.println("没有接收cookie数据"); 59 } 60 } 61 }
1.3 Cookie的细节
1)void setPath(java.lang.String uri) :设置cookie的有效访问路径。有效路径指的是cookie的有效路径保存在哪里,那么浏览器在有效路径下访问服务器时就会带着cookie信息,否则不带cookie信息。
2)void setMaxAge(int expiry) : 设置cookie的有效时间。
- 正整数:表示cookie数据保存浏览器的缓存目录(硬盘中),数值表示保存的时间。
- 负整数:表示cookie数据保存浏览器的内存中。浏览器关闭cookie就丢失了!!
- 零:表示删除同名的cookie数据
3)Cookie数据类型只能保存非中文字符串类型的。可以保存多个cookie,但是浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
1.4 案例-查看用户浏览器过的商品
首先是商品的实体类:
1 package com.infaraway.entity; 2 3 4 /** 5 * Created by :Infaraway 6 * DATE : 2017/3/25 7 * Time : 14:34 8 * Funtion : 商品类 9 */ 10 public class Product { 11 12 private String id; 13 private String proName; 14 private String proType; 15 private double price; 16 public String getId() { 17 return id; 18 } 19 public void setId(String id) { 20 this.id = id; 21 } 22 public String getProName() { 23 return proName; 24 } 25 public void setProName(String proName) { 26 this.proName = proName; 27 } 28 public String getProType() { 29 return proType; 30 } 31 public void setProType(String proType) { 32 this.proType = proType; 33 } 34 public double getPrice() { 35 return price; 36 } 37 public void setPrice(double price) { 38 this.price = price; 39 } 40 public Product(String id, String proName, String proType, double price) { 41 super(); 42 this.id = id; 43 this.proName = proName; 44 this.proType = proType; 45 this.price = price; 46 } 47 public Product() { 48 super(); 49 // TODO Auto-generated constructor stub 50 } 51 @Override 52 public String toString() { 53 return "Product [id=" + id + ", price=" + price + ", proName=" 54 + proName + ", proType=" + proType + "]"; 55 } 56 57 }
这里我们使用list集合来模拟数据库,使用dao层进行增删改查操作
1 package com.infaraway.dao; 2 3 4 import com.infaraway.entity.Product; 5 6 import java.util.ArrayList; 7 import java.util.List; 8 9 10 /** 11 * Created by :Infaraway 12 * DATE : 2017/3/25 13 * Time : 14:34 14 * Funtion : 商品的增删改查操作 15 */ 16 public class ProductDao { 17 //模拟数据库,存储所有的商品信息 18 private static List<Product> data = new ArrayList<Product>(); 19 20 /** 21 * 静态代码块,初始化 22 */ 23 static{ 24 //初始化 25 for(int i=1;i<=10;i++){ 26 data.add(new Product(""+i,"笔记本"+i,"LN00"+i,334.0+i)); 27 } 28 } 29 30 31 /** 32 * 显示所有的商品 33 */ 34 public List<Product> findAll(){ 35 return data; 36 } 37 38 /** 39 * 根据id查找商品 40 */ 41 public Product findById(String id){ 42 for(Product p:data){ 43 if(p.getId().equals(id)){ 44 return p; 45 } 46 } 47 return null; 48 } 49 50 }
然后给出商品列表界面,由用户点击选择浏览的商品,并且给出了用户的浏览记录
1 package com.infaraway.servlet; 2 3 import com.infaraway.dao.ProductDao; 4 import com.infaraway.entity.Product; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.Cookie; 8 import javax.servlet.http.HttpServlet; 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpServletResponse; 11 import java.io.IOException; 12 import java.io.PrintWriter; 13 import java.util.List; 14 15 /** 16 * Created by :Infaraway 17 * DATE : 2017/3/25 18 * Time : 14:34 19 * Funtion : 20 */ 21 public class ListServlet extends HttpServlet { 22 @Override 23 public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 24 25 resp.setContentType("text/html;charset=utf-8"); 26 ProductDao dao = new ProductDao(); 27 List<Product> productList = dao.findAll(); 28 29 //2.把商品显示到浏览器 30 PrintWriter writer = resp.getWriter(); 31 String html = ""; 32 33 html += "<html>"; 34 html += "<head>"; 35 html += "<title>显示商品列表</title>"; 36 html += "</head>"; 37 html += "<body>"; 38 html += "<table border='1' align='center' width='600px'>"; 39 html += "<tr>"; 40 html += "<th>编号</th><th>商品名称</th><th>商品型号</th><th>商品价格</th>"; 41 html += "</tr>"; 42 //遍历商品 43 if(productList!=null){ 44 for(Product p:productList){ 45 html += "<tr>"; 46 // /day11_hist/DetailServlet?id=1 访问DetailSErvlet的servlet程序,同时传递 名为id,值为1 的参数 47 html += "<td>"+p.getId()+"</td><td><a href='"+req.getContextPath()+"/DetailServlet?id="+p.getId()+"'>"+p.getProName()+"</a></td><td>"+p.getProType()+"</td><td>"+p.getPrice()+"</td>"; 48 html += "<tr>"; 49 } 50 } 51 html += "</table>"; 52 53 54 /** 55 * 显示浏览过的商品 56 */ 57 html += "最近浏览过的商品:<br/>"; 58 //取出prodHist的cookie 59 Cookie[] cookies = req.getCookies(); 60 if(cookies!=null){ 61 for (Cookie cookie : cookies) { 62 if(cookie.getName().equals("prodHist")){ 63 String prodHist = cookie.getValue(); // 3,2,1 64 String[] ids = prodHist.split(","); 65 //遍历浏览过的商品id 66 for (String id : ids) { 67 //查询数据库,查询对应的商品 68 Product p = dao.findById(id); 69 //显示到浏览器 70 html += ""+p.getId()+" "+p.getProName()+" "+p.getPrice()+"<br/>"; 71 } 72 } 73 } 74 } 75 76 77 html += "</body>"; 78 html += "</html>"; 79 80 writer.write(html); 81 82 } 83 84 @Override 85 public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 86 super.doGet(req, resp); 87 } 88 }
最后给出商品详情界面
1 package com.infaraway.servlet; 2 3 4 import com.infaraway.dao.ProductDao; 5 import com.infaraway.entity.Product; 6 7 import java.io.IOException; 8 import java.io.PrintWriter; 9 import java.util.Arrays; 10 import java.util.Collection; 11 import java.util.LinkedList; 12 13 import javax.servlet.ServletException; 14 import javax.servlet.http.Cookie; 15 import javax.servlet.http.HttpServlet; 16 import javax.servlet.http.HttpServletRequest; 17 import javax.servlet.http.HttpServletResponse; 18 /** 19 * Created by :Infaraway 20 * DATE : 2017/3/25 21 * Time : 14:34 22 * Funtion : 商品信息详细页面 23 */ 24 public class DetailServlet extends HttpServlet { 25 26 public void doGet(HttpServletRequest request, HttpServletResponse response) 27 throws ServletException, IOException { 28 response.setContentType("text/html;charset=utf-8"); 29 //1.获取传来的商品id 30 String id = request.getParameter("id"); 31 32 //2. 创建dao,进行商品的操作 33 ProductDao dao = new ProductDao(); 34 Product product = dao.findById(id); 35 36 //3.将商品信息打印到浏览器页面 37 PrintWriter writer = response.getWriter(); 38 String html = ""; 39 40 html += "<html>"; 41 html += "<head>"; 42 html += "<title>商品详情</title>"; 43 html += "</head>"; 44 html += "<body>"; 45 html += "<table border='1' align='center' width='300px'>"; 46 if(product!=null){ 47 html += "<tr><th>编号:</th><td>"+product.getId()+"</td></tr>"; 48 html += "<tr><th>商品名称:</th><td>"+product.getProName()+"</td></tr>"; 49 html += "<tr><th>商品类型:</th><td>"+product.getProType()+"</td></tr>"; 50 html += "<tr><th>商品价格:</th><td>"+product.getPrice()+"</td></tr>"; 51 } 52 53 html += "</table>"; 54 html += "<center><a href='"+request.getContextPath()+"/ListServlet'>[返回列表]</a></center>"; 55 html += "</body>"; 56 html += "</html>"; 57 58 writer.write(html); 59 60 61 /** 62 * 将浏览过的商品存入cookie 63 */ 64 //1.创建cookie 65 Cookie cookie = new Cookie("prodHist",createValue(request,id)); 66 cookie.setMaxAge(1*30*24*60*60);//设置cookie有效时间(单位:s) 67 //2.保存cookie 68 response.addCookie(cookie); 69 } 70 71 /** 72 * 生成cookie的值信息 73 * 当前cookie值 --> 传入的商品id --> 最终的cookie值 74 * null或没有prodHist 1 1 (算法: 直接返回传入的id ) 75 * 1 2 2,1 (没有重复且小于3个。算法:直接把传入的id放最前面 ) 76 * 2,1 1 1,2(有重复且小于3个。算法:去除重复id,把传入的id放最前面 ) 77 * 3,2,1 2 2,3,1(有重复且3个。算法:去除重复id,把传入的id放最前面) 78 * 3,2,1 4 4,3,2(没有重复且3个。算法:去最后的id,把传入的id放最前面) 79 */ 80 private String createValue(HttpServletRequest request,String id) { 81 82 Cookie[] cookies = request.getCookies(); 83 String prodHist = null; 84 if(cookies!=null){ 85 for (Cookie cookie : cookies) { 86 if(cookie.getName().equals("prodHist")){ 87 prodHist = cookie.getValue(); 88 break; 89 } 90 } 91 } 92 93 94 // null或没有prodHist 95 if(cookies==null || prodHist==null){ 96 //直接返回传入的id 97 return id; 98 } 99 100 // 3,21 2 101 //String -> String[] -> Collection :为了方便判断重复id 102 String[] ids = prodHist.split(","); 103 Collection<String> colls = Arrays.asList(ids); //<3,21> 104 // LinkedList 方便地操作(增删改元素)集合 105 // Collection -> LinkedList 106 LinkedList<String> list = new LinkedList<String>(colls); 107 108 109 //不超过3个 110 if(list.size()<3){ 111 //id重复 112 if(list.contains(id)){ 113 //去除重复id,把传入的id放最前面 114 list.remove(id); 115 list.addFirst(id); 116 }else{ 117 //直接把传入的id放最前面 118 list.addFirst(id); 119 } 120 }else{ 121 //等于3个 122 //id重复 123 if(list.contains(id)){ 124 //去除重复id,把传入的id放最前面 125 list.remove(id); 126 list.addFirst(id); 127 }else{ 128 //去最后的id,把传入的id放最前面 129 list.removeLast(); 130 list.addFirst(id); 131 } 132 } 133 134 // LinedList -> String 135 StringBuffer sb = new StringBuffer(); 136 for (Object object : list) { 137 sb.append(object+","); 138 } 139 //去掉最后的逗号 140 String result = sb.toString(); 141 result = result.substring(0, result.length()-1); 142 return result; 143 } 144 145 public void doPost(HttpServletRequest request, HttpServletResponse response) 146 throws ServletException, IOException { 147 doGet(request, response); 148 } 149 150 }
上述代码可以在这里找到:https://git.oschina.net/infaraway/basisJavaWeb/tree/master/cookie
(二) Session技术
2.1 引入
Cookie的局限:
- 1)Cookie只能存字符串类型。不能保存对象
- 2)只能存非中文。
- 3)1个Cookie的容量不超过4KB。
如果要保存非字符串,超过4kb内容,只能使用session技术!
Session特点:会话数据保存在服务器端。(内存中)
2.2 Session技术核心
HttpSession类:用于保存会话数据
1)创建或得到session对象
- HttpSession getSession()
- HttpSession getSession(boolean create)
2)设置session对象
- void setMaxInactiveInterval(int interval) : 设置session的有效时间
- void invalidate() : 销毁session对象
- java.lang.String getId() : 得到session编号
3)保存会话数据到session对象
- void setAttribute(java.lang.String name, java.lang.Object value) : 保存数据
- java.lang.Object getAttribute(java.lang.String name) : 获取数据
- void removeAttribute(java.lang.String name) : 清除数据
2.3 Session原理
问题: 服务器能够识别不同的浏览者!
关键: 在哪个session域对象保存数据,就必须从哪个域对象取出!
代码解读:HttpSession session = request.getSession();
1)第一次访问创建session对象,给session对象分配一个唯一的ID,叫JSESSIONID
new HttpSession();
2)把JSESSIONID作为Cookie的值发送给浏览器保存
Cookie cookie = new Cookie("JSESSIONID", sessionID);
response.addCookie(cookie);
3)第二次访问的时候,浏览器带着JSESSIONID的cookie访问服务器
4)服务器得到JSESSIONID,在服务器的内存中搜索是否存放对应编号的session对象。
if(找到){
return map.get(sessionID);
}
Map<String,HttpSession>
5)如果找到对应编号的session对象,直接返回该对象
6)如果找不到对应编号的session对象,创建新的session对象,继续走1的流程
结论:通过JSESSION的cookie值在服务器找session对象!
1 import java.io.IOException; 2 3 import javax.servlet.ServletException; 4 import javax.servlet.http.Cookie; 5 import javax.servlet.http.HttpServlet; 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletResponse; 8 import javax.servlet.http.HttpSession; 9 /** 10 * Created by :Infaraway 11 * DATE : 2017/3/25 12 * Time : 14:34 13 * Funtion : session对象的创建 14 */ 15 public class CreateSession extends HttpServlet { 16 17 public void doGet(HttpServletRequest request, HttpServletResponse response) 18 throws ServletException, IOException { 19 //1.创建session对象 20 HttpSession session = request.getSession(); 21 //得到session编号 22 System.out.println("id="+session.getId()); 23 //修改session的有效时间 24 //session.setMaxInactiveInterval(20); 25 26 //手动发送一个硬盘保存的cookie给浏览器 27 Cookie c = new Cookie("JSESSIONID",session.getId()); 28 c.setMaxAge(60*60); 29 response.addCookie(c); 30 31 //2.保存会话数据 32 session.setAttribute("name", "Infaraway"); 33 } 34 }
1 package com.infaraway.servlet; 2 3 import javax.servlet.ServletException; 4 import javax.servlet.http.Cookie; 5 import javax.servlet.http.HttpServlet; 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletResponse; 8 import java.io.IOException; 9 10 /** 11 * Created by :Infaraway 12 * DATE : 2017/3/25 13 * Time : 22:17 14 * Funtion : 15 */ 16 public class DeleteCookie extends HttpServlet { 17 18 public void doGet(HttpServletRequest request, HttpServletResponse response) 19 throws ServletException, IOException { 20 /** 21 * 需求: 删除cookie 22 */ 23 Cookie cookie = new Cookie("name","xxxx"); 24 cookie.setMaxAge(0);//删除同名的cookie 25 response.addCookie(cookie); 26 System.out.println("删除成功"); 27 28 } 29 }
2.4 Sesson细节
1)java.lang.String getId() : 得到session编号
2)两个getSession方法:
- getSession(true) / getSession() : 创建或得到session对象。没有匹配的session编号,自动创 建新的session对象。
- getSession(false): 得到session对象。没有匹配的session编号,返回null
3)void setMaxInactiveInterval(int interval) : 设置session的有效时间
session对象销毁时间:
- 1 默认情况30分服务器自动回收
- 2 修改session回收时间
- 3 全局修改session有效时间
1 <!-- 修改session全局有效时间:分钟 --> 2 <session-config> 3 <session-timeout>1</session-timeout> 4 </session-config>
- 4.手动销毁session对象
void invalidate() : 销毁session对象
4)如何避免浏览器的JSESSIONID的cookie随着浏览器关闭而丢失的问题?
浏览器关闭而丢失cookie的原因是cookie的有效时间设置中参数为负整数导致,因此需求使用setMaxAge()函数将时间修正为正整数即可。
1 /** 2 * 手动发送一个硬盘保存的cookie给浏览器 3 */ 4 Cookie c = new Cookie("JSESSIONID",session.getId()); 5 c.setMaxAge(60*60); 6 response.addCookie(c);
上述代码可以在这里找到:https://git.oschina.net/infaraway/basisJavaWeb/tree/master/session
总结:
1)会话管理: 浏览器和服务器会话过程中的产生的会话数据的管理。
2)Cookie技术:
- new Cookie("name","value")
- response.addCookie(coookie)
- request.getCookies()
3)Session技术
- request.getSession();
- setAttrbute("name","会话数据");
- getAttribute("会话数据")