Session
今日目标
1. session【重点】
它也是一个域对象
2. 三大域对象总结
servletContext
session
request
3. 综合案例
商品购物车
用户登录(验证码)
一 Session
1.1 概述
session是服务器端的会话技术
# session的作用
在一次会话的多次请求之间共享数据,将数据保存到服务器端
# HttpSession是一个域对象
HttpSession是一个接口
域对象可以看成是map(存储多个键值对), cookie是一个entry(只能存一个键值对)
1. 域对象的方法
a. 存储数据
void setAttribute(String name,Object value)
b. 获取数据
Object getAttribute(String name)
c. 删除数据
void removeAttribute(String name)
2. 生命周期: 一次会话的多次请求之间
pageContext(JSP) < request < session < servletContext
从api上来说, 小域对象可以获取大域对象
1.2 工作原理
Session基于Cookie技术实现
方法介绍
1. 获取session对象:
HttpSession session = request.getSession()
1). 通过请求对象创建一个会话对象,如果当前用户会话不存在,创建会话。
2). 如果会话已经存在,这个方法返回已经存在的会话对象。
2. 获取session的id
String sessionId = session.getId();
3. 使当前session失效
ression.invalidate();
package com.itheima01.session;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet("/Session01Servlet")
public class Session01Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取请求
//2. 业务处理
//获取session对象(tomcat创建新的) !
HttpSession session = request.getSession();
session.setAttribute("sick","有点虚");
System.out.println("Session01Servlet:" + session);
//3. 响应
//tomcat底层自动实现
// String sessionId = session.getId();
// Cookie cookie = new Cookie("JSESSIONID", sessionId);
// response.addCookie(cookie);
}
}
package com.itheima01.session;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet("/Session02Servlet")
public class Session02Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取session对象(tomcat会根据浏览器发送过来的JSESSIONID,找出对应的session对象)
HttpSession session = request.getSession();
Object sick = session.getAttribute("sick");
System.out.println("Session02Servlet:" + session);
System.out.println("之前的病情:" + sick + ",接着看病");
}
}
1.3 Session细节
# 什么情况下会找不到原来的session (通俗版)
问题: 张三再次去医院,什么情况下找不到原来的病历本?
1. 浏览器的原因 : 编号丢了(cookie删除)
1). 用户清除浏览记录
浏览记录有包含cookie
2). 用户关闭浏览器
cookie默认存活到浏览会话结束
1. 解决方案 session持久化
2. 原理 实际上是持久化cookie 达到 session持久化的目的
2. 服务器的原因 : 病历本没了(session销毁)
1). 主动销毁
session.invalidate();
2). 默认间隔30分钟不访问过期
I. tomcat的设置(tomcat/conf/web.xml)
<session-config>
<session-timeout>30</session-timeout>
</session-config>
II. 当前项目进行设置(web.xml) 覆盖tomcat默认设置
<session-config>
<session-timeout>5</session-timeout>
</session-config>
3). tomcat非正常关闭
但是tomcat正常关闭,session会被tomcat保存起来 -> session钝化和活化
# 找不到当前会话中的session原因分析(专业版)
1. 浏览器方面的原因
0). 核心: 保存JSESSIONID的cookie被销毁
1). 因为cookie存活时间默认为会话,所以用户关闭浏览器就会销毁(用户无意识)
-> session持久化
2). 用户清除浏览记录(包含cookie)
2. 服务器方面的原因
0). 核心: session对象是存在服务器的内存,被销毁
1). session手动销毁:session.invalidate();
备注: session对象立即销毁
2). 过期销毁:session默认存活时间--30min
备注: (该用户连续30分钟不访问,服务器会自动销毁session)
文件配置: web.xml (tomcat中的默认设置)
1. tomcat/config/web.xml 中有session-config配置 (全局有效)
2. 我们可以在项目中web.xml覆盖其配置 (只对当前项目有效)
<session-config>
<session-timeout>30</session-timeout>
</session-config>
3). 非正常关闭tomcat(比如突然断电)
备注: 如果正常关闭tomcat,tomcat在停止之前会钝化session,下次启动时活化
1.4 session的持久化
#浏览器关闭后,session的持久化方案
1. 问题: 从以上的分析我们得知, 浏览器关闭之后,就找不到原来的session了
2. 原因:
1. 浏览器关闭,服务器中的session是在的
2. 但是前端的JESSIONID这个cookie消失了
3. 浏览器提交请求没有这个id,服务器自然就找不到之前的session了
3. 解决: 浏览器关闭,session依然找到的
1. 在Servlet中手动创建name=JESSIONID的cookie;
2. 这个cookie存储session的id,设置持久化级别 setMaxAge(秒);
3. 将JESSIONID的cookie响应给浏览器;
package com.itheima01.session;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/Session03Servlet")
public class Session03Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取请求
//2. 业务处理
//获取session对象(tomcat创建新的) !
HttpSession session = request.getSession();
session.setAttribute("sick","有点虚");
System.out.println("Session01Servlet:" + session);
//3. 响应
//TODO session持久化
//Servlet如果设置了跟tomcat中一样的东西,会覆盖tomcat中的默认设置
String sessionId = session.getId();
Cookie cookie = new Cookie("JSESSIONID", sessionId);
cookie.setMaxAge(60*30); // 30分钟
response.addCookie(cookie);
}
}
1.5 session的钝化和活化
# 之前提到, 当服务器正常关闭,重启后,还可以再获取session(跟之前的一样)
这是因为tomcat已实现以下二个功能
1. 钝化(序列化: ObjectOutputStream) 保存
当服务器正常关闭时,session中的数据,会序列化到硬盘 (持久化)
序列化的目的: 将内存中对象或数据结构 保存 到硬盘 (编码: 看得懂 -> 看不懂)
内存: 临时性存储设备, 断电了数据就消失
硬盘: 持久性存储设备, 断电了数据依然在
2. 活化(反序列化 : ObjectInputStream) 读取
当服务器开启后,从磁盘文件中,将数据反序列化到内存中
反序列化的目的: 将硬盘上的数据读取到内存,形成对象或数据结构 (解码: 看不懂 -> 看得懂)
备注: 钝化和活化的本质是序列化技术, 所以保存的存储数据类型需要实现serializable接口
我们使用的idea工具有坑:
1. 创建session对象之后 2. 我们正常关闭tomcat,tomcat确实将session钝化到磁盘(下图的位置中的sessions.ser) 2. 坑: 但是在idea重启tomcat时,会默认删除之前保存的sessions.ser文件,造成tomcat没有活化数据 idea由于开发工具运行效率,设计的一套机制 3. 解决: 设置idea重启时,不清除session会话(下图)
支持钝化
我们可以设置idea重启时,不清除session数据
在重启之前,就应该要勾起
1.6 Session特点
# session是服务器端的会话技术
作用: 在一次会话的多次请求之间共享数据
从浏览器第一次向服务器发起请求建立会话, 直到其中一方断开为止会话结束
1. session存储数据在服务器
2. session存储任意类型的数据(Object)
3. session存储大小和数量没有限制(在服务器内存)
4. session存储相对安全
cookie和session的对比
cookie和session的选择
1. cookie将数据保存在浏览器端,数据相对不安全,而且大小有限制.但是成本低,对服务器要求不高
建议敏感的或大量的数据不要放在cookie中
比如: 游客的购物车数据, 浏览记录之类的
2. session将数据保存在服务器端内存,数据相对安全,但是成本较高,对服务器压力较大
用户相关数据,登录信息,账户信息,验证码等
二 综合案例
2.1 用户登录(验证码)
需求
用户访问带有验证码的登录页面,输入用户名,密码以及验证码实现登录功能。
2.1.1 需求分析
2.1.2 代码实现
① 打开之前实现的login项目
② 导入验证码Servlet
package com.itheima.login;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
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;
/*
验证码图片不能是xx.jpg(静态资源,是不会改变)
是CheckcodeServlet生成的(动态资源)
验证码图片的实现技术 : GUI 图形用户界面(Graphical User Interface)
1). GUI是java的图形绘制技术, 此技术已经被淘汰了
javame(micro edition)
2). 实现逻辑
1). 验证码文字准备
I. 文本库: 大小写英文字母和数字
II. 随机选择其中的4个,组成验证码文本
2). 验证码图片绘制(GUI)
像素点的分布,根据文本绘制,并且具备一定的随机分布特点
3). 验证码文本保存到session中
* */
@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);
// TODO: 将验证码放到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);
}
}
③ login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录页面</h1>
<form action="LoginServlet" method="post">
<input type="text" name="username" placeholder="请输入用户名"> <br>
<input type="password" name="password" placeholder="请输入密码"> <br>
<input type="text" name="checkcode" placeholder="请输入验证码">
<img src="CheckcodeServlet" alt="" id="myImg"> <br>
<input type="submit">
</form>
</body>
<script>
/*
* TODO: 点击图片,切换验证码
* 浏览器更新机制 : 监测到网页元素发生改变,就会自动重新加载此元素
* img.src = "1.jpg"; // 第一次
* img.src = "2.jpg"; // 第二次
* 实现思路: 通过加一个没有意义的参数,改变网页元素
* */
let myImg = document.getElementById("myImg");
myImg.onclick = function (){
console.log("hello");
//每次点击都获取当前时间毫秒值
let time = new Date().getTime();
myImg.src = "CheckcodeServlet?time=" + time
}
</script>
</html>
④ LoginServlet
package com.itheima.web;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决post请求参数中文乱码问题
request.setCharacterEncoding("utf-8");
//TODO: 验证码校验 (在用户名和密码之前)
//1). 获取服务器之前生成的验证码
//2). 获取用户输入的验证码
String code_session = (String) request.getSession().getAttribute("code_session");
String checkcode = request.getParameter("checkcode");
if(!code_session.equals(checkcode)){
//验证码比对失败
request.setAttribute("errorMsg","验证码错误");
request.getRequestDispatcher("FailedServlet").forward(request,response);
return; // 中断后续运行
}
//1. 获取请求
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username +"," + password);
//2. 业务处理: 判定用户名和密码是否存在
// 理论上: 查询数据库
// 现在: 伪数据 (jack,123)
if("jack".equals(username) && "123".equals(password)){
//登录成功
request.getRequestDispatcher("SuccessServlet").forward(request,response);
}else{
//登录失败
request.setAttribute("errorMsg","用户名不存在或密码错误");
request.getRequestDispatcher("FailedServlet").forward(request,response);
}
}
}
⑤ successServlet
package com.itheima.web;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet("/SuccessServlet")
public class SuccessServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("登录成功!");
}
}
⑥ FailedServlet
package com.itheima.web;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
@WebServlet("/FailedServlet")
public class FailedServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
Object errorMsg = request.getAttribute("errorMsg");
response.getWriter().print("登录失败:" + errorMsg);
}
}