26. 会话技术-Session的使用

2021-11-19 16:11:38 浏览数 (1)

26. 会话技术-Session的使用

一、 Session

1.1 概述

session是服务器端的会话技术

代码语言:javascript复制
# 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技术实现

下面来使用一个上医院看病的示例,来简单理解 Session

1591321065800

方法介绍

代码语言:javascript复制
1. 获取session对象: 
  HttpSession  session = request.getSession()
  1). 通过请求对象创建一个会话对象,如果当前用户会话不存在,创建会话。  
  2). 如果会话已经存在,这个方法返回已经存在的会话对象。
2. 获取session的id
  String sessionId = session.getId();
3.  使当前session失效
  ression.invalidate(); 

“记得把项目中的index.jsp干掉(影响到 JSESSIONID的cookie) ”

编写示例代码

index.html: 编写一个页面,模拟两个请求
  • 第一次请求至服务端:第一次去医院,生成一个session,并且保存信息
  • 第二次请求至服务端:再次去医院,查看之前保存的session信息。查询session id 是否与 上一次保存的 session ID 一致

image-20210217165529664

代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="Session01Servlet">第一次访问, 创建Session</a> <br>
    <a href="Session02Servlet">第二次访问, 查看Session信息</a>
</body>
</html>
Session01Servlet:创建Session

image-20210217165651306

代码语言:javascript复制
package com.lijw;

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;

/**
 * @author Aron.li
 * @date 2021/2/17 16:56
 */
@WebServlet("/Session01Servlet")
public class Session01Servlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. (挂号) 获取session对象
        HttpSession session = request.getSession();
        //2. (看病) 存数据
        session.setAttribute("sick","有点虚");

        //3. tomcat自动实现,将sessionId通过cookie返回给浏览器
//        String id = session.getId();
//        Cookie cookie = new Cookie("JSESSIONID", id);
//        response.addCookie(cookie);
    }

}
Session02Servlet:读取Session信息

image-20210217165806174

代码语言:javascript复制
package com.lijw;

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;

/**
 * @author Aron.li
 * @date 2021/2/17 16:57
 */
@WebServlet("/Session02Servlet")
public class Session02Servlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. (挂号) 获取session对象
        HttpSession session = request.getSession();
        Object sick = session.getAttribute("sick");
        System.out.println("value:"   sick   ",id:"    session.getId());
    }
}
测试
  • 在 index.html 执行第一次请求,创建 session

image-20210217165909891

image-20210217170010048

  • 在 index.html 执行第二次信息,查看 session 的 ID

image-20210217170111939

image-20210217170137124

可以通过获取的 session ID 信息,我们可以知道 session 是同一个的。

1.3 Session细节

代码语言:javascript复制
# 找不到当前会话中的session原因分析 (通俗版)
 问题: 什么时候找不到班长原来的病历本?
1. 班长的原因
 0). 只要班长把病例本编号弄丢了 (id弄丢了)
 1). 用户清除cookie(清除浏览记录)
 2). 用户关闭浏览器, 保存id的cookie默认会话级别,自动销毁了
   -> 通过持久化cookie,达到session持久化 (下一个)
2. 医院的原因
 0). 医院把病历本弄丢了
 1). 手动销毁session
  session.invalidate();
 2). 自动销毁session
     默认30分钟内不访问,自动销毁 (可以改)
     a. tomcat/conf/web.xml 默认设置 
      (对发布在此tomcat上所有项目生效)
     b. 在当前项目中修改 web.xml (会覆盖tomcat默认设置)
        <session-config>
                <session-timeout>30</session-timeout>
            </session-config>
    3). 服务器非正常关闭
     突然断电, 数据来不及保存
     正常关闭: session数据会会从内存保存硬盘上
      -> session 钝化和活化
代码语言:javascript复制
# 找不到当前会话中的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的持久化

代码语言:javascript复制
#浏览器关闭后,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响应给浏览器; 
代码语言:javascript复制
@WebServlet("/Session01Servlet")
public class Session01Servlet 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. (挂号) 获取session对象
        HttpSession session = request.getSession();
        //2. (看病) 存数据
        session.setAttribute("sick","有点虚");

        //3. tomcat自动实现,将sessionId通过cookie返回给浏览器
//        String id = session.getId();
//        Cookie cookie = new Cookie("JSESSIONID", id);
//        response.addCookie(cookie);


        //4. 我们手动实现(设置持久级别),覆盖tomcat的自动实现
        String id = session.getId();
        Cookie cookie = new Cookie("JSESSIONID", id);
        cookie.setMaxAge(60*30); //30分钟
        response.addCookie(cookie);
    }

}

1.5 session的钝化和活化

代码语言:javascript复制
# 之前提到, 当服务器正常关闭,重启后,还可以再获取session(跟之前的一样)
这是因为tomcat已实现以下二个功能
1. 钝化(序列化: ObjectOutputStream) 保存
  当服务器正常关闭时,session中的数据,会序列化到硬盘 (持久化)
   序列化的目的: 将内存中对象或数据结构 保存 到硬盘 (编码: 看得懂 -> 看不懂)
    内存: 临时性存储设备, 断电了数据就消失
    硬盘: 持久性存储设备, 断电了数据依然在
    
2. 活化(反序列化: ObjectInputStream) 读取
  当服务器开启后,从磁盘文件中,将数据反序列化到内存中
   反序列化的目的: 将硬盘上的数据读取到内存,形成对象或数据结构 (解码: 看不懂 -> 看得懂)

备注: 钝化和活化的本质是序列化技术, 所以保存的存储数据类型需要实现serializable接口

我们使用的idea工具有坑:

代码语言:javascript复制
1. 我们正常关闭tomcat,tomcat确实将session钝化到磁盘(下图的位置中的sessions.ser)
2. 坑: 但是在idea重启tomcat时,会默认删除之前保存的sessions.ser文件,造成tomcat没有活化数据
3. 解决: 设置idea重启时,不清除session会话(下图)

“支持钝化 ”

下面我们来演示一下 idea 工具的坑 到底是什么 坑!

1.5.1 首先查看 Idea 中操作 tomcat 的克隆空间(临时空间)

image-20210217221007671

根据这个路径,我们可以打开我们电脑的文件夹,如下:

image-20210217221059089

1.5.2 停止 tomcat ,查看钝化的效果,生成 session.ser 文件,将 session 信息保存在硬盘中

image-20210217221217785

停止 tomcat 后,那么将会将 session 保存如下:

image-20210217221540189

这个 SESSIONS.ser 就是 tomcat 钝化后的文件,提供后续启动 tomcat 的时候读取 session 数据,进行活化。

1.5.3 Idea默认启动的时候会删除之前钝化保存的文件,导致 tomcat 再次读取 session 数据 活化失败

image-20210217221824738

1.5.4 我们可以设置idea重启时,不清除session数据

image-20210217222001702

1.6 URL重写(了解)

代码语言:javascript复制
# URL重写是为了解决cookie禁用问题
1. 问题: 浏览器是默认启用cookie,但是用户也可以禁用浏览器的Cookie(浏览器自带功能: 不允许浏览器保存cookie), 由于Session基于Cookie技术实现,所以一旦禁用了之后,Session功能就会出现问题

2. 解决: url重写技术
   response.encodeURL(path)
   会在url,拼接JSESSIONID
   
3. 备注: 开发中,一般我们是不关注禁用cookie的用户,若用户禁用了cookie,会给很多功能的实现带来很大的麻烦
1.6.1 首先禁止浏览器使用 cookie

image-20210217232943160

image-20210217233029543

此时点击 第二次访问,查看 Session 信息,由于没有 Session ID,导致没有 Session 信息,如下:

image-20210217233322018

1.6.2 修改第一次访问的请求 Servlet

index.html

image-20210217233653491

UrlOverrideServlet

image-20210217234101372

代码语言:javascript复制
package com.lijw;

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;

/**
 * @author Aron.li
 * @date 2021/2/17 23:37
 */
@WebServlet("/UrlOverrideServlet")
public class UrlOverrideServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    /*
     * 正常session 有个前提:
     *   1. 浏览器需要携带 Jsessionid 过来
     *   2. 默认情况下: Jsessionid是用cookie保存的
     *   3. 用户在浏览器中设置: 禁用cookie  -> 浏览器不再保存cookie
     *
     *   解决:
     *       jsessionid 作为参数放在url后面
     *
     *      url = http://localhost:8081/Session02Servlet
     *
     *     重写url
     *           http://localhost:8081/Session02Servlet;jsessionid = ?
     *
     *          参数分割符 ?  , 还有分号
     *           url?name=value  ->  value = request.getParameter(name)
     *           url;name=value   不能上面的api获取, tomcat可以获取就行了
     * */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 创建session
        HttpSession session = request.getSession();
        session.setAttribute("sick", "很虚");
        String id = session.getId();
        System.out.println(id);

        //重写URL,携带sessionId参数
        String url = "Session02Servlet";
        String newUrl = response.encodeURL(url); //重写url,拼接jsessionid
        System.out.println(newUrl); //  Session02Servlet;jsessionid = ?

        //将重写后的URL,作为超链接显示在浏览器上
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().print("<a href='" newUrl "'>第二次去医院</a>" );
    }
}
1.6.3 再次测试禁用cookie的情况下,获取session数据

image-20210217234224356

image-20210217234305695

image-20210217234318230

image-20210217234336678

在后台成功读取到了 session 数据。

1.7 Session特点

代码语言:javascript复制
# session是服务器端的会话技术
 作用: 在一次会话的多次请求之间共享数据
  从浏览器第一次向服务器发起请求建立会话, 直到其中一方断开为止会话结束
1. session存储数据在服务器
2. session存储任意类型的数据(Object)
3. session存储大小和数量没有限制(在服务器内存)
4. session存储相对安全

cookie和session的对比

1591287669163

cookie和session的选择

代码语言:javascript复制
1. cookie将数据保存在浏览器端,数据相对不安全.
  建议敏感的或大量的数据不要放在cookie中,而且数据大小是有限制的
     成本低,对服务器要求不高
   
2. session将数据保存在服务器端内存,数据相对安全.
  数据的大小要比cookie中数据灵活很多
     成本较高,对服务器压力较大

二、 3大域对象总结

request < session < ServletContext

2.1 域对象方法

代码语言:javascript复制
# 域对象方法都一致
1. 设置数据
  void setAttribute(String name, Object o) 
2. 获取数据
  Object getAttribute(String name) 
3. 删除数据
  void removeAttribute(String name)
  
# 小域对象可以获取大域对象

# 不同域对象: 生命周期不一样

2.2 生命周期

2.2.1 ServletContext域对象

代码语言:javascript复制
* 何时创建
  服务器正常启动,项目加载时,创建
* 何时销毁
  服务器关闭或项目卸载时,销毁
* 作用范围
  整个web项目(共享数据)

2.2.2 HttpSession域对象

代码语言:javascript复制
* 何时创建
  用户第一次调用request.getSession()方法时,创建【不太标准..】
  用户访问携带的jsessionid与服务器里的session不匹配时,就会创建的
* 何时销毁
  1. 服务器非正常关闭
  2. 未活跃状态30分钟
  3. 手动销毁 
* 作用范围
  一次会话中,多次请求间(共享数据)

# 会话的定义: 双方建立连接,连接期间的多次请求响应,直到一方断开连接为止
 (B/S) 从浏览器第一次访问这个服务器,期间多次请求响应,直到浏览器关闭为止 -> 狭义的一次会话
  cookie和session默认都是会话级别,都可以设置持久级别

2.2.3 HttpServletRequest域对象

代码语言:javascript复制
* 何时创建
  服务器接收到请求时,创建      
* 何时销毁
  服务器做出响应后,销毁
* 作用范围
  一次请求中,多次请求转发间(共享数据)

2.3 小结

  • 能用小的不用大的:request(一次请求)<session(一次会话)<servletContext(应用全局) “因为生命周期长的域对象销毁时间比较晚,占用服务器内存时间太长 ”
  • 常用的场景:
    • 用户登录状态
    • 验证码
    • 购物车
    • request:一次请求中(请求转发共享)
    • session:存放当前会话的私有数据
    • servletContext:若需要所有的servlet都能访问到,才使用这个域对象. “一般情况下,web阶段很少使用这个域对象,在框架spring的学习中会涉及到 ”

三、 用户登录-验证码案例

3.1 用户登录(验证码)

需求

用户访问带有验证码的登录页面,输入用户名,密码以及验证码实现登录功能。

3.1.1 需求分析

1591781837951

3.1.2 代码实现

1.编写生成验证码的Servlet

image-20210218232805983

代码语言:javascript复制
package com.lijw;

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;

/**
 * @author Aron.li
 * @date 2021/2/18 23:24
 */
@WebServlet("/CheckcodeServlet")
public class CheckcodeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    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());
    }
}

在网页测试请求,看看验证码的效果如下:

image-20210218232906641

2.编写用户登录的页面 login.html

image-20210218233852836

代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2>登录页面</h2>
    <hr>
    <form action="LoginServlet" method="post">

        <input type="text" name="username" placeholder="请输入用户名"> <br>
        <input type="password" name="password" placeholder="请输入密码" style="margin-top: 5px;"> <br>

        <div style="display: flex; margin: 5px 0;">
            <input type="text" name="code" placeholder="请输入验证码" >
            <img src="CheckcodeServlet" alt="" id="myimg" style="height: 25px; margin-left: 5px; ">
        </div>

        <input type="submit">

    </form>

    <script >
        var img = document.getElementById("myimg");
        img.onclick = function () {
            /*
            *   浏览器特点:
            *       动态修改了网页中的元素属性,浏览器自动加载
            *
            *   原理:
            *       通过在url中添加一个没有实际作用的参数来欺骗浏览器
            *           修改网页属性,浏览器自动加载
            * */
            let time = new Date().getTime() // 获取当前系统时间毫秒值
            img.src = "CheckcodeServlet?time="   time
        }
    </script>
</body>
</html>

在浏览器访问页面效果如下:

image-20210218233938755

3.编写处理登录业务的 LoginServlet

image-20210218234843686

代码语言:javascript复制
package com.lijw;

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;

/**
 * @author Aron.li
 * @date 2021/2/18 23:40
 */
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //解决请求参数的中文乱码
        request.setCharacterEncoding("UTF-8");
        //解决响应中文乱码
        response.setContentType("text/html;charset=utf-8");

        //1. 接收验证码 code,判断验证码是否正确
        //1.1 从session中获取图片的验证码
        String code_session = (String)request.getSession().getAttribute("code_session");
        //1.2 获取请求的验证码参数
        String code = request.getParameter("code");
        //1.3 判断验证码是否正确
        if (!code.equalsIgnoreCase(code_session)) {
            // 如果验证码不正确,则什么都不处理,直接返回
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().print("验证码错误,请重新输入");
            return;
        }

        //1. 获取请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        //2. 业务处理
        if("jack".equalsIgnoreCase(username) && "123".equalsIgnoreCase(password)){
            //登录成功
            response.getWriter().write("登录成功");
        }else{
            //登录失败
            response.getWriter().write("登录失败");
        }

    }
}
4.测试用户登录
  • 登录成功的情况

image-20210218234941346

image-20210218234955269

  • 登录失败的情况

image-20210218235031275

0 人点赞