6、会话管理
在人机交互过程中,会话管理是指保持用户的整个会话活动的交互与计算机系统跟踪的过程。会话管理分为桌面会话管理、浏览器会话管理、Web 会话管理。本书讨论的是 Web 会话管理(通常指的是 session 以及Cookie),也称为会话跟踪。
本章主要涉及的知识点有:
(1)会话管理的基本原理
(2)HttpSession 会话管理
(3)HttpSession 会话管理的实例演示
6.1 会话管理的基本原理
会话管理可以通过以下 3 种解决方案实现:使用隐藏域、使用 Cookie、使用 URL 重写。下面分别介绍各方案。
6.1.1、使用隐藏域
隐藏域是指在显示页面时隐藏表单中的内容,即不显示数据。在 JSP 中将 input 标签的 type 属性值设定为hidden,即生成一个隐藏表单域。再将会话的唯一标识记录到隐藏域中的 value 属性中,并设定 name 属性值。当提交表单时,会话标识也被提交到服务器端,服务器端根据它找到对应的会话对象。
使用隐藏域时,需要在每个页面中都包含会话标识表单,这样才能在许多页面跳转之间保存会话,并对它进行管理。此方法实现起来比较烦琐,安全性较差,不适合隐秘性的数据,在目前的开发中较少使用。
6.1.2、使用Cookie
利用 Cookie 实现会话管理是最容易的实现方式,也是使用较多的方式。原理是在服务器端保存的会话对象中设定会话的唯一标识,客户端将会话标识保存在 Cookie 中,当浏览器发起请求时,从 Cookie 中取得会话标识并发送给服务器端,服务器端在收到请求后,根据发送过来的会话标识查找到对应的会话对象,这样服务器端就清楚当前是哪个客户端在连接,并且可以从会话中获得信息。
利用 Cookie 实现会话管理是目前开发中采用的主流方法,大多数的动态页面开发技术都实现了这一功能,并且其管理流程是自动完成的,在实现上并不需要多大的开发工作量。使用 Cookie 实现会话管理的过程如图 6.1 所示。

图 6.1 使用 Cookie 实现会话管理的过程
6.1.3、使用 URL 重写
顾名思义,URL 重写是指在 URL 地址的末尾添加会话标识,改写了原先的 URL 地址。其本质是用于唯一标识会话的信息以参数的形式添加到 URL 中,服务器端接收到请求时,解析出会话标识,然后利用会话标识査找出与当前请求对应的会话对象。该方法用在浏览器中的 Cookie 被禁用的情况下,利用该方法可以很好地实现会话跟踪而不会受到浏览器参数设定的影响。
URL 重写方法是在 URL 后面添加会话标识,因此整个 Web 应用中的超链接或者脚本中用到的 URL 都需要添加会话标识,Web 应用中的每一个页面都需要动态生成,页面中的每一个超链接或者由客户端生成的跳转指令都必须加上会话标识,这样才能确保是当前会话。当客户端访问静态页面时,会话标识将会丢失,当重回动态页面时将不能继续此前的会话。以上是它的特点,也是它的缺陷。
该方法还有一个问题,即当用户在浏览网页时,可能会将 URL 复制下来分享给朋友,因为 URL 中会包含会话标识,那么其他人可能与当前浏览者使用同一会话对象,这样当前浏览者的个人隐私就暴露了,信息不再安全。
无论采用哪种方式实现会话管理,在服务器端都需要完成相关代码才能完整地实现会话管理的任务。这些任务主要是生成唯一的会话标识、存储会话对象、从Web容器中取得当前请求的会鯬话、回收空闲会话。
注意:会话管理会产生会话对象,它存储在服务器的内存中,会占用内存,因此需要注意空闲会话的回收,以提高服务器的性能。如果 Web 应用不需要会话的存在,那么就禁用会话,从而提高服务器的运行速率。
6.2、HttpSession 会话管理
HTTP 协议(http://www.w3.org/Protocols/)是无意识的、单向的协议。服务器端不能主动连接客户端,只能等待并答复客户端请求。客户端连接服务器端,发出一个 HTTP 请求,服务器端处理请求,并返回一个 HTTP 响应给客户端,至此,本次会话结束。从这一过程可以看出,HTTP 协议本身并不支持服务器端保存客户端的状态等信息。于是,Web 服务器中引入了 session 的概念,用来保存客户端的信息。
6.2.1、HttpSession 管理会话
在 Java 中,使用 jakarta.servlet.http.HtpSession 类来实现 session 会话。每个请求者对应一个 session 对象,客户的所有状态信息都保存在该对象里。当用户第一次请求服务器时,就创建了session 对象。它以 key-value 的形式进行保存,通过相应的读写方法来保存客户的状态信息,例如:
|
getAttribute(String key) |
用于获得参数 |
|
setAttribute(Sting key,Object value) |
用于设定参数和参数值 |
在 Servlet 中可以通过 request.getSession() 方法获取客户的 session 对象,例如:
// 1. 获取会话对象
HttpSession session = request.getSession();
// 2. 将信息保存到 session 中
session.setAttribute("username","张三");
在 JSP 中内置了 session 对象,可以直接使用,Servlet 使用上述方法获取 session 对象。若 JSP 页面中设定了
<%@page session="false"%>
则该 JSP 页面的 session 对象是不可用的。下面通过例子说明如何利用 session 来保存登录信息。
(1)login.jsp
【例6.1】 利用 session 来保存登录信息。
首先,新建一个登录页面 login.jsp,源代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 1. 获取当前应用路径
String contextPath = request.getContextPath();
System.out.println("当前应用的路径是:" + contextPath);
%>
<html>
<head>
<title>用户登陆</title>
</head>
<body>
<p>用户登陆</p>
<form action="<%=contextPath%>\checkUserServlet" method="post">
<table border="1" width="250px">
<tr>
<td width="75px">用户名:</td>
<td><input type="text" name="userId"></td>
</tr>
<tr>
<td width="75px">密 码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交">
<input type="reset" value="重置">
</td>
</tr>
</table>
</form>
</body>
</html>
在上述代码中,表单内容很简单,只有用户名和密码两个输入框,第 13 行代码用于填写提交表单时跳转的 action。
(2)CheckUserServlet
本例是用 Servlet 实现的,所以需要编写跳转的 Servlet 类 CheckUserServlet,其源代码如下:
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet(name = "checkUserServlet",urlPatterns = "/checkUserServlet")
public class CheckUserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 接收请求部分
// 1. 设置请求的编码
request.setCharacterEncoding("UTF-8");
// 2. 接收请求的数据
String userId = request.getParameter("userId");
String password = request.getParameter("password");
// 3. 校验用户
if (userId != null && "111".equals(userId) && password != null && "111".equals(password)){
// 1. 针对该请求获取一个会话对象
HttpSession session = request.getSession();
// 2. 将请求的信息保存到会话对象中
session.setAttribute("user",userId);
// 3. 请求分派
RequestDispatcher dispatcher = request.getRequestDispatcher("/ch6/welcome.jsp");
// 4. 请求转发
dispatcher.forward(request,response);
}else {
RequestDispatcher dispatcher = request.getRequestDispatcher("/ch6/login.jsp");
dispatcher.forward(request,response);
}
}
}
在上述代码中:
第 05、06 行用于获取页面请求的用户名与密码参数;
第 09~18 行用于判断用户是否存在,如果存在就存放到 session 中并跳转到欢迎页面 welcome.jsp,如果用户不存在,就跳转到登录页面 login.isp。
(3)welcome.jsp
欢迎页面显示当前用户的用户名,其实现比较简单,使用 JSP 页面中的 session 对象就可以取出 user 对象,源代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%
String user = (String) session.getAttribute("user");
if (user == null){
%>
<jsp:forward page="login.jsp"/>
<% } %>
</head>
<body>
欢迎您!<%=user%>
</body>
</html>
在上述代码中,第 07~10行是利用 JSP的 session 获取 user 对象,如果用户为空,就返回登录页面。运行结果如图 6.2所示。


图 6.2 保存登录信息效果图
6.2.2、HttpSession管理会话的原理
HttpSession 会话管理是利用服务器来管理会话的机制。当程序为某个客户端的请求创建了一个 session 的时候,服务器会检查客户端的请求是否已经包含了一个 session 标识,如果已经有了session 标识,那么服务器就把该 session 检索出来使用;如果请求不包含 session 标识,那就为客户端创建一个该请求的唯一 session 标识。HtpSession 会话管理流程如图 6.3 所示。

图 6.3 HtpSession 会话管理流程示意图
6.2.3、HttpSession 与 URL 重写
在第 6.12 节中,提到 URL 重写是用在客户端不支持 Cookie 的情况下。它的实现方式是将 sesion 的标识 ID 添加到 URL 中。服务器通过解析重写后的 URL,获取 session 的标识 ID。这样即使客户端不支持 Cookie,也可以用 session 来记录状态信息。重写的方法如下:
response.encodeURL("login .jsp");
或者
response.sendRedirect(response.encodeRedirectURL("login.jsp"));
这两种方法的效果是一样的,如果客户端支持 Cookie,那么生成的 URL 不变;如果不支持那么生成的 URL中就会带有 jsessionid 参数字符串,超链接如图 6.4 所示。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<a href="login.jsp; jsp:jsessionid=93641A11965F7D979E3232D4A7B93234?username=zhangSan"></a>
欢迎您!
</body>
</html>
图 6.4 URL中带有 jsessionid 的示意图
从图 6.4 可以看出,在 URL 的文件名后面,参数的前面添加了字符串“jsessionid=标识ID”。用户单击这个超链接时,客户端会把 session 的 ID 值通过 URL 提交到服务器上,服务器通过解析 URL 地址获坼得 session 的 ID 值。
6.2.4、HttpSession 中禁用 Cookie
可以通过配置的方式在 Web 项目中禁用 Cookie,禁用方法如下:
- 在 Web 项目的 web 目录中的 META-INF 文件夹下,打开或者创建 context.xml 文件,编辑如下
内容:
<?xml version="1.0" encoding="UTF-8"?>
<Context cookies="false" path="/ch06">
</Context>
- 打开 Tomcat 的配置文件 context.xml,编辑如下内容:
<?xml version="1.0" encoding="UTF-8"?>
<Context cookies="false">
</Context>
上述两种方法都是在 Context 元素中添加属性 cookies="false",二者的区别在于前者是禁止单个项日使用 Cookie,后者是禁止部署在 Tomcat 服务器里的 Web 项目使用 Cookie。
6.2.5、HttpSession的生命周期
Servlet 有生命周期,同样,HttpSession 也有生命周期:创建 ——> 使用 ——> 消亡,
1、HtpSession 对象的创建
当客户端第一次访问服务器时,服务器为每个浏览器创建不同 session 的 ID 值。在服务器端使用
request.getSession()
或者
request.getSession(true)
方法来获得 HttpSession 对象。
2、HtpSession 对象的使用
创建 HttpSession 对象后,使用 session 对象进行数据的存取和传输。具体的过程如下:
(1)将产生的 sessionID 存入 Cookie 中。
(2)当客户端再次发送请求时,会将 sessionID 与 request 一起传送给服务器端。
(3)服务器根据请求过来的 sessionID 与保存在服务器端的 session 对应起来,判断是否为同一
session。
3、HttpSession 对象的消亡
有如下 3 种方式可以结束 session 对象:
(1)关闭浏览器。
(2)调用 HtpSession 的 invalidate() 方法。
(3)session 超时。
关闭浏览器,这样会使浏览器端的 session 失效,服务器端 session 并不会失效。如果服务器进程终止了,那么 session 会被结束。
在 session 结束时,服务器会清空当前浏览器的相关数据信息。
以上就是 HttpSession 的生命周期过程,在请求处理时不断循环第 2 步(指上面 HtpSession 对象的使用),直到 session 对象消亡。
Session 是保存在服务器内存中的,每个用户都有一个独立的 session。session 中的内容应该尽量少,这样当有大量客户访问服务器时才不会导致内存溢出。
注意:只有访问 JSP、Servlet 时才会创建 session,访问静态页面时是不会创建 session 对象的。
6.2.6、HttpSession的有效期
通常情况下,会给 session 设定一个有效期,当某用户访问的 session 超过这个有效期时,即不是活跃的session,那么 session 就失效了,服务器会将它从内存中清除。设定 session 的有效期有以下3种方法:
(1)在对应的 Web 服务器配置中设置所有 session 的有效期。
(2)调用 session 的 setMaxInactiveInterval(long interval) 进行设定。
(3)在 web.xml 中修改,例如:
<session-config>
<!--会话超时时长为30分钟-->
<session-timeout>30</session-timeout>
</session-config>
注意:调用 session的 invalidate() 可以销毁 session.
6.3 HttpSession 会话管理实例演示
利用 HttpSession 可以实现 Web 的基本功能与操作,例如实现简单购物车、在线猜数字游戏等。下面通过在线猜数字游戏来说明 HttpSession 会话管理的过程。
(1)guessNumber.jsp
【例6.2】 在线猜数字游戏。首先,新建一个猜数字页面 guessNumber.jsp,源代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String contextPath = request.getContextPath();
%>
<html>
<head>
<title>在线猜数字</title>
<%
String flag = request.getParameter("flag");
String message = "";
if (flag != null && "larger".equals(flag)){
message = "太大了";
} else if (flag != null && "lessner".equals(flag)) {
message = "太小了";
} else if (flag != null && "success".equals(flag)) {
message = "您猜对了";
}
%>
</head>
<body>
<form action="<%=contextPath%>/guess" method="post">
<span>请输入您的数字:</span>
<input name="guessNumber" size="10">
<span style="color: red"><%=message%></span>
<input type="submit" value="提交">
</form>
</body>
</html>
在上述代码中,第 10~20 行用于判断返回值的情况:
如果所猜值大于随机数的值,就输出“太大了”;
如果所猜值小于随机数的值,就输出“太小了”;
如果猜对了,就输出“您猜对了”。
(2)Guess
其次,编写提交的 Servlet 类 Guess,源代码如下:
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet(name = "Guess",urlPatterns = "/guess")
public class Guess extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String guessNumber = request.getParameter("guessNumber"); // 接收数据
int number = Integer.parseInt(guessNumber); // 解析成整数
HttpSession session = request.getSession(); // 获取对象
session.setMaxInactiveInterval(5);
Integer currentNumber = (Integer) session.getAttribute("currentNumber"); // 获取保存在 session 中的数据
String contextPath = request.getContextPath(); // 获取请求路径
if (currentNumber == null){
currentNumber = 1+(int) (Math.random() * 50); // 生成 1-50 的随机数
session.setAttribute("currentNumber",currentNumber); // 保存到 session
}
// 判断所猜的数字与 session 中保存的数据是否一致
if (number > currentNumber){
response.sendRedirect(contextPath + "/ch6/guessNumber.jsp?flag=larger");
} else if (number < currentNumber) {
response.sendRedirect(contextPath + "/ch6/guessNumber.jsp?flag=lessner");
}else {
currentNumber = 1+(int) (Math.random() * 50);
session.setAttribute("currentNumber",currentNumber);
response.sendRedirect(contextPath + "/ch6/guessNumber.jsp?flag=success");
}
}
}
在上述代码中:
第 11~15 行利用 Math.random 方法产生 1~50 的随机数,并把它保存在 session,
第17~25行用于判断提交的数与 session 中的值是否相符,利用重定向的方法跳转到指定页面开携带 fag 标识。代码运行效果如图 6.5 所示。


图 6.5 在线猜数字效果图
注意:本例利用重定向跳转到 guessNumber.jsp 页面,读者也可以利用 RequestDispatcher 中的 forward 方法实现跳转。
6.4、小结
本章首先简单介绍了会话管理的基本原理,一般有 3 种实现方式:使用隐藏域、Cookie 和 URL 重写。这3种方式各有其特点,但目前流行的是 Cookie 的方式。接着介绍了 HttpSession 会话管理的原理、使用方法、禁用Cookie、HttpSession 生命周期及其有效期。最后通过一个实例演示了 HttpSession 会话管理。熟练掌握会话的功能和特征,并根据需求灵活应用,这是 Web 开发的必要技能,希望读者能深入研究。
1023

被折叠的 条评论
为什么被折叠?



