JavaWeb Session
1. Session概述
1.1. 什么是Session
Session一般译为会话,是解决Http协议的无状态问题的方案,可以将一次会话中的数据存储在服务器端的内存中,保证在下一次的会话中可以使用。
在客户端浏览器第一次向服务器端发送请求时,服务器端会为这个客户端创建独有的Session,并具有唯一的Session ID,存储在服务器端的内存中。在客户端第二次访问服务器端时,会携带Session ID在请求中,服务器端会根据Session ID查找对应的Session信息,进行进一步地操作。
在JavaEE中提供了javax.servlet.http.HttpSession接口,通过该接口可以将共享的数据内容存储在HttpSession对象中,从而解决Http协议的无状态问题。
1.2. 百度百科
Session直接翻译成中文比较困难,一般都译成时域。在计算机专业术语中,Session是指一个终端用户与交互系统进行通信的时间间隔,通常指从注册进入系统到注销退出系统之间所经过的时间。以及如果需要的话,可能还有一定的操作空间。
具体到Web中的Session指的就是用户在浏览某个网站时,从进入网站到关闭这个网站所经过的这段时间,也就是用户浏览这个网站所花费的时间。因此从上述的定义中我们可以看到,Session实际上是一个特定的时间概念。
需要注意的是,一个Session的概念需要包括特定的客户端,特定的服务器端以及不中断的操作时间。A用户和C服务器建立连接时所处的Session同B用户和C服务器建立连接时所处的Session是两个不同的Session。
1.3. 维基百科
会话(session)是一种持久网络协议,在用户(或用户代理)端和服务器端之间创建关联,从而起到交换数据包的作用机制,session在网络协议(例如telnet或FTP)中是非常重要的部分。
在不包含会话层(例如UDP)或者是无法长时间驻留会话层(例如HTTP)的传输协议中,会话的维持需要依靠在传输数据中的高级别程序。例如,在浏览器和远程主机之间的HTTP传输中,HTTP cookie就会被用来包含一些相关的信息,例如session ID,参数和权限信息等。
1.4. Session与Cookie的区别
Session与Cookie都是解决Http协议的无状态问题,但是两者之间还是存在一定区别的:
- Cookie数据存储在客户端的浏览器内存中或本地缓存文件中,Session数据存储在服务器端的内存中。
- Cookie数据存储安全性较低,Session数据存储安全性较高。
- Session数据存储在服务器端内存中,访问增多时,降低服务器端性能。而Cookie则不会对服务器端性能造成影响。
- 单个Cookie存储的数据最大是4KB,一个网站只能存储20个Cookie。Session则没有这个问题。
- Session在关闭浏览器时失效,而持久Cookie则可以存储更长有效时间。
总的来说,Session与Cookie各有优势,不能简单来说谁更优。具体用法要考虑具体案例情况而定。
2. Session入门
2.1. Session常用API
在JavaEE提供的javax.servlet.http.HttpSession接口,是Web应用程序开发使用Session的接口,该接口提供了很多API方法,而常用的方法有以下几个:
Method Summary |
|
getAttribute(String name) |
|
getAttributeNames() |
|
void |
removeAttribute(String name) |
void |
setAttribute(String name, Object value) |
- 通过Request对象获得HttpSession对象。
HttpSession session = request.getSession();
- 通过HttpSession对象设置和获取共享数据内容。
session.setAttribute("name", "longestory");
String name = (String)session.getAttribute("name");
2.2. 第一个Session
掌握了如何获取Session对象及向Session对象中设置及获取共享数据内容,下面我们就来利用HttpSession对象实现数据内容共享。
- 创建一个Servlet用于向HttpSession对象中存储共享数据内容。
public class FirstServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); session.setAttribute("name", "longestory"); System.out.println("已经成功向HttpSession对象中存储了共享数据内容name=longestory..."); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 创建另一个Servlet用于从HttpSession对象中获取储存的共享数据内容。
public class SecondServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); HttpSession session = request.getSession(); String name = (String)session.getAttribute("name"); out.println("<h1>你存储的共享数据内容为name="+name+"</h1>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name></display-name> <servlet> <servlet-name>FirstServlet</servlet-name> <servlet-class>app.java.session.FirstServlet</servlet-class> </servlet> <servlet> <servlet-name>SecondServlet</servlet-name> <servlet-class>app.java.session.SecondServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>FirstServlet</servlet-name> <url-pattern>/first</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>SecondServlet</servlet-name> <url-pattern>/second</url-pattern> </servlet-mapping> </web-app>
2.3. Session其他API
在JavaEE的javax.servlet.http.HttpSession接口提供了除常用方法外,还有很多其他方法可供使用:
Method Summary |
|
long |
getCreationTime() |
getId() |
|
long |
getLastAccessedTime() |
int |
getMaxInactiveInterval() |
getServletContext() |
|
void |
invalidate() |
boolean |
isNew() |
void |
setMaxInactiveInterval(int interval) |
- String getId():获取sessionId;
- long getCreationTime():返回session的创建时间,返回值为当前时间的毫秒值;
- long getLastAccessedTime():返回session的最后活动时间,返回值为当前时间的毫秒值;
- boolean isNew():查看session是否为新。当客户端第一次请求时,服务器为客户端创建session,但这时服务器还没有响应客户端,也就是还没有把sessionId响应给客户端时,这时session的状态为新;
- int getMaxInactiveInterval():获取session可以的最大不活动时间(秒),默认为30分钟。当session在30分钟内没有使用,那么Tomcat会在session池中移除这个session;
- void setMaxInactiveInterval(int interval):设置session允许的最大不活动时间(秒),如果设置为1秒,那么只要session在1秒内不被使用,那么session就会被移除;
- void invalidate():让session失效!调用这个方法会被session失效,当session失效后,客户端再次请求,服务器会给客户端创建一个新的session,并在响应中给客户端新session的sessionId。
下面我们通过一个Servlet将上述中的一些方法进行测试,这里理解性记忆就好,后面案例中用到哪个方式再具体掌握。
- 创建一个Servlet用于测试HttpSession对象的方法。
public class ThreeServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); Calendar calendar = Calendar.getInstance(); HttpSession session = request.getSession(); System.out.println("Session的ID为"+session.getId()); calendar.setTimeInMillis(session.getCreationTime()); System.out.println("Session的创建时间为"+formatter.format(calendar.getTime())); calendar.setTimeInMillis(session.getLastAccessedTime()); System.out.println("Session的最后活动时间为"+formatter.format(calendar.getTime())); System.out.println("当前Session是否为新的?"+session.isNew()); System.out.println("Session的默认活动时间为"+session.getMaxInactiveInterval()/60+"分钟"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name></display-name> <servlet> <servlet-name>ThreeServlet</servlet-name> <servlet-class>app.java.session.ThreeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ThreeServlet</servlet-name> <url-pattern>/three</url-pattern> </servlet-mapping> </web-app>
2.4. Servlet三大域对象
现在掌握了HttpSession对象的基本使用方法,到目前为止,Servlet的三大域对象都已经掌握。Servlet的三大域对象分别为HttpServletRequest对象、ServletContext对象及HttpSession对象:
- HttpServletRequest:一个请求创建一个request对象,所以在同一个请求中可以共享request,例如一个请求从AServlet转发到BServlet,那么AServlet和BServlet可以共享request域中的数据;
- ServletContext:一个应用只创建一个ServletContext对象,所以在ServletContext中的数据可以在整个应用中共享,只要不启动服务器,那么ServletContext中的数据就可以共享;
- HttpSession:一个会话创建一个HttpSession对象,同一会话中的多个请求中可以共享session中的数据。
3. Session详解
3.1. Session的实现原理
通过HttpSession对象实现了在多次会话中共享数据内容,HttpSession对象底层到底是如何实现这样的效果?下面我们来讨论一下。
- 利用MyEclipse工具的Debug模式进行调试代码,在request.getSession()处打上断点。
- Debug模式启动Tomcat服务器,并访问对应的Servlet,抓取获取的Session内容。
会发现实际得到的是org.apache.catalina.session.StandardSession对象,该对象是由Tomcat服务器的Session池创建,并为该对象创建唯一的ID值。
- 通过IE浏览器的HttpWatch工具抓取请求响应协议内容。
会发现服务器端向客户端进行响应时,向客户端发送了一个Cookie信息,具体内容如下:
Set-Cookie: JSESSIONID=0BD17B07E383FA86703B370560E823F2; Path=/11_session/; HttpOnly
- 客户端再次访问对应Servlet时,通过IE浏览器的HttpWatch工具抓取请求响应协议内容。
会发现客户端向服务器端发送请求时,向服务器端发送了一个Cookie信息,具体内容如下:
Cookie: JSESSIONID=0BD17B07E383FA86703B370560E823F2
通过上述操作,我们会发现在第一次请求时,服务器端向客户端响应Cookie信息,在第二次请求时,客户端向服务器端发送了Cookie信息。进而可以总结出Session的实现原理如下:
因为Session使用的是会话Cookie,所以当浏览器关闭后,Session会失效。重新打开浏览器访问对应Servlet时,服务器端会重新创建Session对象。可以使用持久Cookie来延长Session的有效时间。
3.2. 禁用Cookie后的Session
通过Session的实现原理可以知道,Session的实现是基于Cookie的。如果浏览器禁用Cookie的话,Session是否还是有效的呢?下面我们具体操作来看一看。
- 打开IE浏览器的“工具”->“Internet选项”->“隐私”选项,阻止所有Cookie。
- 这时重新启动Tomcat服务器,访问对应的Servlet。
这个问题是否可以解决呢?答案是可以的,可以利用URL重写方式来解决,具体操作如下:
- 通过Response对象的encodeURL()方法将URL进行重写。
String url = request.getRequestURI(); url = response.encodeURL(url); response.getWriter().println("<a href='" + url + "'>second</a>");
禁用Cookie解决Session的问题,这种解决方案是具有理论意义的,但不具备实际意义。因为大部分的网站都是基于Cookie完成数据共享的,例如京东网站或淘宝网站等。如果浏览器禁用Cookie,网站会提示相关信息。
3.3. Session的生命周期
关于Sessioin的生命周期,在之前的内容都有学习到,只是没有专门归纳总结。下面总结一下Session的生命周期,主要是Session的创建和销毁。
- Session的创建:在客户端第一次向服务器端发送请求,并执行request.getSession()方法时。
- Session的销毁:
- 不正常关闭浏览器时。(正常关闭浏览器,Session信息被序列化到硬盘中,保存在Tomcat服务器安装目录/work目录)
- Session信息过期时(Session默认的有效时间为30分钟)。
可以利用setMaxInactiveInterval(int interval)方法设置Session的有效时间。
-
- 在服务器端程序中执行session.invalidate()方法,手动销毁Session对象。
4. Session案例
4.1. 商品购物车案例
利用Session实现商品购物车的逻辑功能,具体操作如下:
- 创建一个JSP页面用于显示商品信息。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ page import="java.util.Map"%> <%@ page import="java.util.Set"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'show.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> </head> <body> <!-- 商品列表 --> <h1>商品列表</h1> Java编程思想<a href="/session/show?id=1">购买</a><br> Java设计模式<a href="/session/show?id=2">购买</a><br> Java语言入门<a href="/session/show?id=3">购买</a><br> Oracle数据库<a href="/session/show?id=4">购买</a><br> MySQL数据库<a href="/session/show?id=5">购买</a><br> <!-- 购物车记录 --> <h1>购物车记录</h1> <h2><a href="/session/clear">清空购物车</a></h2> <% Map<String,Integer> map = (Map<String,Integer>)request.getSession().getAttribute("cart"); if(map == null){ // 购物车对象不存在 out.println("<h2>购物车无任何商品信息!</h2>"); }else{ // 购物车对象已经存在 Set<String> keySet = map.keySet(); for(String productName: keySet){ int number = map.get(productName);// 购买数量 out.println("商品名称: " + productName +", 购买数量:" + number + "<br/>"); } } %> </body> </html>
- 创建一个Servlet用于处理添加购物车的逻辑。
public class ShowServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获得购买商品 id String id = request.getParameter("id"); // 获得id 对应商品名称 String[] names = { "Java编程思想", "Java设计模式", "Java语言入门", "Oracle数据库", "MySQL数据库" }; String productName = names[Integer.parseInt(id) - 1]; // 判断Session中购物车是否存在 HttpSession session = request.getSession(); Map<String, Integer> cart = (Map<String, Integer>) session.getAttribute("cart"); if (cart == null) { // 购物车不存在 cart = new HashMap<String, Integer>(); } // 判断购买商品是否存在于购物车中,商品名称就是map的key if (cart.containsKey(productName)) { // 商品已经在购物车中 int number = cart.get(productName);// 取出原来数量 cart.put(productName, number + 1);// 数量+1 放回购物车 } else { // 商品不在购物车中 cart.put(productName, 1);// 保存商品到购物车,数量为1 } // 将购物车对象保存Session session.setAttribute("cart", cart); // 给用户提示 response.setContentType("text/html;charset=utf-8"); response.getWriter().println("商品已经添加到购物车!<a href='/session/cart/show.jsp'>返回</a>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 创建一个Servlet用于处理清空购物车记录的逻辑。
public class ClearServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取购物车Session对象 HttpSession session = request.getSession(); // 删除购物车cart对象 session.removeAttribute("cart"); // 跳转show.jsp response.sendRedirect("/session/cart/show.jsp"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name></display-name> <servlet> <servlet-name>ShowServlet</servlet-name> <servlet-class>app.java.session.ShowServlet</servlet-class> </servlet> <servlet> <servlet-name>ClearServlet</servlet-name> <servlet-class>app.java.session.ClearServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ShowServlet</servlet-name> <url-pattern>/show</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ClearServlet</servlet-name> <url-pattern>/clear</url-pattern> </servlet-mapping> </web-app>
4.2. 一次验证码登录案例
所谓一次验证码,就是验证码生成后,只能使用一次,不管成功或者失败,验证码都将失效。具体实现步骤如下:
- 创建一个JSP页面用于显示用户登录信息。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'login.jsp' starting page</title> </head> <script type="text/javascript"> function change(){ document.getElementById("myimg").src = "/session/checkimg?timeStamp="+new Date().getTime(); } </script> <body> <h1>登陆页面</h1> <h2 style="color:red;">${requestScope.msg }</h2> <form action="/11_session/login" method="post"> <table> <tr> <td>用户名</td> <td><input type="text" name="username" /></td> </tr> <tr> <td>密码</td> <td><input type="password" name="password"/> </td> </tr> <tr> <td>验证码</td> <td><input type="text" name="checkcode" /> <img src="/session/checkimg" onclick="change();" id="myimg" style="cursor: pointer;"/></td> </tr> <tr> <td colspan="2"><input type="submit" value="登陆" /></td> </tr> </table> </form> </body> </html>
- 创建一个Servlet用于生成验证码图片。
public class CheckImgServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int width = 120; int height = 30; // 步骤一 绘制一张内存中图片 BufferedImage bufferedImage = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB); // 步骤二 图片绘制背景颜色 ---通过绘图对象 Graphics graphics = bufferedImage.getGraphics();//得到画图对象 - 画笔 // 绘制任何图形之前 都必须指定一个颜色 graphics.setColor(getRandColor(200, 250)); graphics.fillRect(0, 0, width, height); // 步骤三 绘制边框 graphics.setColor(Color.WHITE); graphics.drawRect(0, 0, width - 1, height - 1); // 步骤四 四个随机数字 Graphics2D graphics2d = (Graphics2D) graphics; // 设置输出字体 graphics2d.setFont(new Font("宋体", Font.BOLD, 18)); String words = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; Random random = new Random();// 生成随机数 // 为了将验证码保存Session StringBuffer buffer = new StringBuffer(); // 定义x坐标 int x = 10; for (int i = 0; i < 4; i++) { // 随机颜色 graphics2d.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110))); // 旋转 -30 --- 30度 int jiaodu = random.nextInt(60) - 30; // 换算弧度 double theta = jiaodu * Math.PI / 180; // 生成一个随机数字 int index = random.nextInt(words.length());//生成随机数0到 length-1 // 获得字母数字 char c = words.charAt(index); // 将生成汉字 加入buffer buffer.append(c); // 将c 输出到图片 graphics2d.rotate(theta, x, 20); graphics2d.drawString(String.valueOf(c), x, 20); graphics2d.rotate(-theta, x, 20); x += 30; } // 将验证码内容保存session request.getSession().setAttribute("checkcode_session",buffer.toString()); // 步骤五 绘制干扰线 graphics.setColor(getRandColor(160, 200)); int x1; int x2; int y1; int y2; for (int i = 0; i < 30; i++) { x1 = random.nextInt(width); x2 = random.nextInt(12); y1 = random.nextInt(height); y2 = random.nextInt(12); graphics.drawLine(x1, y1, x1 + x2, x2 + y2); } // 将上面图片输出到浏览器 ImageIO graphics.dispose();// 释放资源 ImageIO.write(bufferedImage, "jpg", response.getOutputStream()); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } /** * 取其某一范围的color * * @param fc * int 范围参数1 * @param bc * int 范围参数2 * @return Color */ private Color getRandColor(int fc, int bc) { // 取其随机颜色 Random random = new Random(); if (fc > 255) { fc = 255; } if (bc > 255) { bc = 255; } int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } }
- 创建一个Servlet用于处理登录验证逻辑。
public class LoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获得用户名、密码和 验证码 request.setCharacterEncoding("utf-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); String checkcode = request.getParameter("checkcode"); // 判断验证码是否正确 String checkcode_session = (String) request.getSession().getAttribute("checkcode_session"); request.getSession().removeAttribute("checkcode_session"); if (checkcode_session == null || !checkcode_session.equals(checkcode)) { // 验证码输入错误 request.setAttribute("msg", "验证码输入错误!"); request.getRequestDispatcher("/login/login.jsp").forward(request,response); return; } // 判断用户名和密码是否正确 ,假设用户名和密码都是admin/admin if ("admin".equals(username) && "admin".equals(password)) { // 登陆成功 // 将登陆信息保存session request.getSession().setAttribute("username", username); response.sendRedirect("/session/login/welcome.jsp"); } else { // 登陆失败 request.setAttribute("msg", "用户名或者密码错误!"); request.getRequestDispatcher("/login/login.jsp").forward(request,response); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 创建一个JSP页面用于显示欢迎信息。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP 'welcome.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> </head> <body> <h1>登陆后的欢迎页面</h1> <% String username = (String)request.getSession().getAttribute("username"); if(username == null){ // 未登陆 out.println("您还未登陆,<a href='/session/login/login.jsp'>去登陆</a>"); }else{ // 已经登陆 out.println("欢迎您,"+username); } %> </body> </html>
没有高深的知识,没有进阶的技巧,万丈高楼平地起~!