目录
Session
- 使用 Cookie 的问题:
- 最多存储 4K 字符串;
- 存储数据不太安全。
- Session 作用:在一次会话的多次请求之间共享数据,将数据 保存到服务器端。
1. HttpSession
- HttpSession 是 Java 平台对 Session 机制的实现规范,HttpSession 也是一个域对象。
- 常用 API:
- 存储数据:
void setAttribute(String name,Object value)
- 获取数据:
Object getAttribute(String name)
- 删除数据:
void removeAttribute(String name)
- 存储数据:
2. 存取与获得 Session
a. 将数据存储到 Session 中
- 通过 Request 对象,获取 Session 对象:
HttpSession session = request.getSession();
- 操作 Session 的 API,存储数据:
session.setAttribute("username","Regino,哈哈");
b. 从 Session 中获取数据
- 通过 Request 对象,获取 Session 对象:
HttpSession session = request.getSession();
- 操作 Session 的 API,获取数据:
session.getAttribute("username");
c. 代码示例
- SetSession:
package servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/SetSession")
public class SetSession extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.通过rquest对象,获取session对象
HttpSession session = request.getSession();
// 2.操作session的API,存储数据
session.setAttribute("username", "Regino,哈哈");
}
}
- GetSession:
package servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/GetSession")
public class GetSession extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.通过rquest对象,获取session对象
HttpSession session = request.getSession();
// 2.操作session的API,获取数据
String username = (String) session.getAttribute("username");
System.out.println("GetSession获取:" + username);
}
}
- 依次访问 http://localhost:8080/webappPractice2/SetSession,http://localhost:8080/webappPractice2/GetSession,控制台输出:
3. Session 的工作原理
- Session 是基于 Cookie 技术实现的。
a. 第一次请求的 Request
b. 第二次请求的 Response
c. 创建 Session 空间
HttpSession session = request.getSession();
- 如果用户是第 1 次访问,表示创建 Session 对象,并生成编号;
- 如果用户是第 N 次访问,根据浏览器携带编号找 Session 对象,如果没找到再创建。
d. IDEA 中的 Session 位置
- 先找到 IDEA 克隆 Tomcat 时复制出的临时空间:
- 根据空间找到对应的 Session 文件:
4. JSESSIONID
- JSESSIONID 就是 Session 的 ID。
- JSESSIONID 只是 Tomcat 中对 Session ID 的叫法,在其它容器里面,不一定是叫 JSESSIONID。
- JSESSIONID 与 Session 是唯一对应关系。
a. 客户端关闭,服务器不关闭
- 两次获取的 Session 数据是否为相同?
- 默认情况下,浏览器关闭,再次打开后两次获取的 Session 不一样。因为 Session 是基于 Cookie 的实现(浏览器关闭,Cookie 会销毁)
- 通过设置 Cookie 的存活时间(即指定 JSESSIONID 的存活时间)可以改变 Session 的存活时间:
cookie.setMaxAge(60);
i. 关闭浏览器,如何找回 Session
- 需要创建一个同名的 Cookie,保存 Session 的 ID,并且设置有效时间。
b. 客户端不关闭,服务器关闭
- 两次获取的 Session 数据是否为相同?
- 当服务器正常关闭,重启后两次获取的 Session 数据一样。
- 原因:Tomcat 实现了以下 2 个功能:
- 钝化(序列化):当 Tomcat 服务器正常关闭时,Session 中的数据,会序列化到磁盘;
- 活化(反序列化):当 Tomcat 服务器开启后,数据从磁盘文件中,被反序列化到内存中,从而重新读取到 Session 的数据。
- 注意:IDEA 默认仅支持钝化,不支持活化。因为 Session 会话在 IDEA 关闭后会自动清除。要让 IDEA 支持活化,可以强制设置 IDEA 重启时,不清除 Session 会话:
i. 关闭服务器,如何保存 Session 的数据
- Tomcat 会完成钝化的工作,只不过钝化的数据所属的类一定要实现 Serializable 接口。
ii. Session 钝化的注意事项
- 钝化的数据所属类一定要实现 Serializable 接口,否则无法钝化。
1. 实体类
package servlet;
import java.io.Serializable;
public class User implements Serializable {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
2. SetSession
package servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/setSession")
public class SetSession extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 创建一个User对象
User user = new User();
user.setId(110);
user.setName("Regino");
//2. 获取Session
HttpSession session = request.getSession();
//3. 把User对象存储到Session中
session.setAttribute("user", user);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
3. GetSession
package servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/getSession")
public class GetSession extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
HttpSession session = request.getSession();
//从Session获取User
User user = (User) session.getAttribute("user");
response.getWriter().write("从Session获取到的数据:" + user);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
- 出现了
22-Apr-2020 02:25:59.169 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.tomcat.util.modeler.BaseModelMBean.invoke Exception
,导致 Tomcat 启动失败:
- 成功 Debug。原来是把 GetSession 里的
@WebServlet("/getSession")
也写成了"/setSession"
。
4. 测试
- 依次访问 http://localhost:8080/webappPractice2/SetSession,http://localhost:8080/webappPractice2/GetSession,页面显示:
5. Session 的生命周期
a. 何时创建
- 用户第一次调用
request.getSession()
方法时创建。
- 如果用户是第 N 次访问,根据浏览器携带编号找 Session 对象,如果没找到再创建。
b. 何时销毁
- 非活跃状态 30 分钟(默认)后。
i. 自定义销毁时间
- Tomcat 可以进行配置
/tocmat安装目录/conf/web.xml
。推荐在项目下的 web.xml(默认继承了 Tomcat 中的 web.xml)中修改,不建议修改全局。
<session-cofig>
<session-timeout>30</session-timeout>
</session-config>
ii. 其他销毁方式
- 服务器非正常关闭时。
- 调用
session.invalidate();
方法时(表示 Session 对象的自杀)。
c. 作用范围
- 一次会话中,多次请求之间。
- 注意:每一个浏览器跟服务器都是独立的会话。
6. URL 的重写
- Session 基于 Cookie 技术实现的,而浏览器的 Cookie 是可以被禁用的,所以一旦禁用之后,Session 就会出现问题。
- 开发中,一般是不关注用户的 Cookie 是否被禁用,如果要处理用户禁用 Cookie 的问题,可以使用 URL 重写技术。即用另一个 Servlet 处理获得的 JSESSIONID。
a. 写入 Session
package servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/URLRewrite")
public class URLRewrite extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取Session对象
HttpSession session = request.getSession();
// 向Session中存数据
session.setAttribute("username", "浏览器禁用了Cookie,但不影响使用");
// 定义URL
String url = "/webappPractice2/GetSession?JSESSIONID=" + session.getId();
// 重写URL,拼接JSESSIONID
url = response.encodeURL(url);
System.out.println(url);// /webappPractice2/GetSession?JSESSIONID=547B2304838BDDCE7C7A1A8F572DE9ED
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("<a href='" + url + "'>跳转到获取Session内容</a>");
}
}
b. 获得 Session
- 与一般的获得 Session 的 Servlet 一样就可以,不用改变。
package servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/GetSession")
public class GetSession extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.通过rquest对象,获取session对象
HttpSession session = request.getSession();
// 2.操作session的API,获取数据
String username = (String) session.getAttribute("username");
System.out.println("GetSession获取:" + username);
}
}
c. 测试
- 访问 http://localhost:8080/webappPractice2/URLRewrite,页面显示:
- 点击链接,自动跳转至 http://localhost:8080/webappPractice2/GetSession?JSESSIONID=547B2304838BDDCE7C7A1A8F572DE9ED,页面显示:
7. Session 的特点
- Session 存储数据在服务器。
- Session 存储类型任意(Object)。
- Session 存储大小和数量没有限制(相对于内存)。
- Session 存储相对安全。
8. Cookie 和 Session 的选择
a. 选择 Cookie
- Cookie 存储的数据保存在浏览器端,相对不安全:
- Cookie 存储的数据成本低,对服务器要求不高;
- 建议敏感的数据不要放在 Cookie 中;
- Cookie 存储的数据大小是有限制的,但是可以通过在浏览器端存放 localStroage,来解决这个容量不足的问题。
b. 选择 Session
- Session 存储的数据在服务器端,相对安全;
- Session 存储的数据大小要比 Cookie 中的数据灵活很多;
- Session 存储的数据成本较高,对服务器压力较大。
9. 综合案例:商品购物车
a. 主要需求
- 有一个商品页面,可以点击超链接将商品添加到购物车,还有一个超链接,点击它的时候可以查看购物车中商品信息。
b. 步骤分析
c. 代码实现
i. goods.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>goods.jsp</title>
</head>
<body>
<h3>商品列表</h3>
<a href="/webappPractice2/AddCartServlet?name=电视机">电视机,加入购物车</a><br>
<a href="/webappPractice2/AddCartServlet?name=冰箱">冰箱,加入购物车</a><br>
<a href="/webappPractice2/AddCartServlet?name=洗衣机">洗衣机,加入购物车</a><br>
<a href="/webappPractice2/AddCartServlet?name=电脑">电脑,加入购物车</a><br>
</body>
</html>
ii. AddCartServlet
package servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@WebServlet("/AddCartServlet")
public class AddCartServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8"); // 请求解码
response.setContentType("text/html;charset=utf-8");// 响应编码
// 1.获取请求参数
String product = request.getParameter("name");
// 2.返回结果
response.getWriter().write(product + ",商品已成功加入购物车<br>");
// 3.从Session中获取购物车
Map<String, Integer> cart = (Map<String, Integer>) request.getSession().getAttribute("cart");
// 4.判断购物车是否为空
if (cart == null) {
cart = new HashMap<>();
}
// 5.判断购物车中是否包含本次添加的商品
if (cart.containsKey(product)) {// 6.存在,数量+1
Integer oldCount = cart.get(product); // 之前数量
cart.put(product, oldCount + 1);// 数量加1
} else { // 7.不存在,直接添加商品,数量为1
cart.put(product, 1);
}
// 8.重新将购物车,写入到Session中
request.getSession().setAttribute("cart", cart);
// 9.继续浏览
response.getWriter().write("<a href='/webappPractice2/goods.jsp'>继续浏览</a><br>");
// 10.查看购物车
response.getWriter().write("<a href='/webappPractice2/cart.jsp'>查看购物车</a><br>");
}
}
iii. cart.jsp
<%@ page import="java.util.Map" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>cart</title>
</head>
<body>
<h3>购物车页面</h3>
<table border="1" width="200px" align="center">
<tr>
<th>商品</th>
<th>数量</th>
</tr>
<%
// 1.从Session中获取购物车
Map<String, Integer> cart = (Map<String, Integer>) request.getSession().getAttribute("cart");
// 2.判断是否为空
if (cart == null) {
out.write("购物车内暂时没有商品<br>");
} else {
for (String s : cart.keySet()) {
out.write("<tr><td>" + s + "</td><td>" + cart.get(s) + "</td></tr>");
}
}
%>
</table>
</body>
</html>
- JSP 要放在 web 目录下,src 中只能放 Java 文件。
- JSP 虽然在应用上更接近浏览器端,但是操作上更接近服务器端,而且不管静态动态资料都会在 Tomcat 中运行的,所以 JSP 一般和 Servlet 一起出现在服务器里。
iv. 测试
10. 综合案例:用户登录(有验证码)
a. 主要需求
- 用户访问带有验证码的登录页面,输入用户名,密码以及验证码实现登录功能。
b. 步骤分析
c. 代码实现
i. 创建 web 项目
ii. CheckcodeServlet
package servlet;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
@WebServlet("/CheckcodeServlet")
public class CheckcodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 创建画布
int width = 120;
int height = 40;
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 获得画笔
Graphics g = bufferedImage.getGraphics();
// 填充背景颜色
g.setColor(Color.white);
g.fillRect(0, 0, width, height);
// 绘制边框
g.setColor(Color.red);
g.drawRect(0, 0, width - 1, height - 1);
// 生成随机字符
// 准备数据
String data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
// 准备随机对象
Random r = new Random();
// 声明一个变量 保存验证码
String code = "";
// 书写4个随机字符
for (int i = 0; i < 4; i++) {
// 设置字体
g.setFont(new Font("宋体", Font.BOLD, 28));
// 设置随机颜色
g.setColor(new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255)));
String str = data.charAt(r.nextInt(data.length())) + "";
g.drawString(str, 10 + i * 28, 30);
// 将新的字符 保存到验证码中
code = code + str;
}
// 绘制干扰线
for (int i = 0; i < 6; i++) {
// 设置随机颜色
g.setColor(new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255)));
g.drawLine(r.nextInt(width), r.nextInt(height), r.nextInt(width), r.nextInt(height));
}
// 将验证码 打印到控制台
System.out.println(code);
// 将验证码放到session中
request.getSession().setAttribute("code_session", code);
// 将画布显示在浏览器中
ImageIO.write(bufferedImage, "jpg", response.getOutputStream());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
- 访问 http://localhost:8080/webappPractice2/CheckcodeServlet,页面显示:(刷新页面画面可更新)
iii. login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>login.jsp</title>
</head>
<body>
<h3>用户登录</h3>
<form action="/webappPractice2/LoginServlet" method="post">
用户:<input type="text" name="username"> <br>
密码:<input type="password" name="password"><br>
验证码:<input type="text" name="checkcode"> <img src="/webappPractice2/CheckcodeServlet" alt=""><br>
<input type="submit" value="登录">
<span style="color:red">
<%
String error = (String) request.getAttribute("error");
if (error != null) {
out.write(error);// 输出提示信息
}
%>
</span>
</form>
</body>
</html>
iv. LoginServlet
package servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
// 1.获取用户输入的验证码
String checkcode = request.getParameter("checkcode");
// 2.获取Session中验证码
String codeSession = (String) request.getSession().getAttribute("code_session");
// 3.校验是否匹配
if (!checkcode.equalsIgnoreCase(codeSession)) {
// 验证码不配,友情提示...
request.setAttribute("error", "验证码输入错误...");
// 转发到login.jsp
request.getRequestDispatcher("/login.jsp").forward(request, response);
// 代码不在往下执行....
return;
}
// 4.获取用户输入的用户名和密码
String username = request.getParameter("username");
String password = request.getParameter("password");
// 5.判断用户名密码不正确
if (!("Regino".equals(username) && "123".equals(password))) {
// 友情提示
request.setAttribute("error", "用户名或密码错误...");
// 转发到login.jsp
request.getRequestDispatcher("/login.jsp").forward(request, response);
// 代码不在往下执行....
return;
}
// 6. 将用户名存入到Session中
request.getSession().setAttribute("username", username);
// 7.重定向到success.jsp
response.sendRedirect(request.getContextPath() + "/success.jsp");
}
}
- 不能直接访问 http://localhost:8080/webappPractice2/LoginServlet,会出现
500 Internal Server Error
(这里是空指针异常,因为没有获得到 login.jsp 请求中的表单数据)
v. success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>success</title>
</head>
<body>
<h3><%
// 获取用户信息
String username = (String) request.getSession().getAttribute("username");
if (username != null) {
out.write("用户您好,登录成功:"+username);
}
%></h3>
</body>
</html>
vi. 测试
- 访问 http://localhost:8080/webappPractice2/login.jsp,输入错误的用户名和密码:
- 输入错误的验证码:
- 输入正确的用户名、密码和验证码: