Session

今日目标

  1. 1. session【重点】
  2. 它也是一个域对象
  3. 2. 三大域对象总结
  4. servletContext
  5. session
  6. request
  7. 3. 综合案例
  8. 商品购物车
  9. 用户登录(验证码)

一 Session

1.1 概述

session是服务器端的会话技术

  1. # session的作用
  2. 在一次会话的多次请求之间共享数据,将数据保存到服务器端
  3. # HttpSession是一个域对象
  4. HttpSession是一个接口
  5. 域对象可以看成是map(存储多个键值对), cookie是一个entry(只能存一个键值对)
  6. 1. 域对象的方法
  7. a. 存储数据
  8. void setAttribute(String name,Object value)
  9. b. 获取数据
  10. Object getAttribute(String name)
  11. c. 删除数据
  12. void removeAttribute(String name)
  13. 2. 生命周期: 一次会话的多次请求之间
  14. pageContext(JSP) < request < session < servletContext
  15. api上来说, 小域对象可以获取大域对象

1.2 工作原理

Session基于Cookie技术实现

1649747752574.png

方法介绍

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会话(下图)

支持钝化

1599722347364.png

1599722513899.png

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

在重启之前,就应该要勾起

1587263085478.png

1.6 Session特点

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

cookie和session的对比

1591287669163.png

cookie和session的选择

1. cookie将数据保存在浏览器端,数据相对不安全,而且大小有限制.但是成本低,对服务器要求不高
        建议敏感的或大量的数据不要放在cookie中
           比如: 游客的购物车数据, 浏览记录之类的
2. session将数据保存在服务器端内存,数据相对安全,但是成本较高,对服务器压力较大
        用户相关数据,登录信息,账户信息,验证码等

二 综合案例

2.1 用户登录(验证码)

需求

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

2.1.1 需求分析

1649752833064.png

2.1.2 代码实现

① 打开之前实现的login项目

② 导入验证码Servlet

1587277039412.png

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