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 | |
Object | getAttribute(String name) |
Enumeration | 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() |
String | getId() |
long | getLastAccessedTime() |
int | getMaxInactiveInterval() |
ServletContext | 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>
没有高深的知识,没有进阶的技巧,万丈高楼平地起~!