Session

今日目标

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

一 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技术实现

1591321065800.png

方法介绍

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

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

package com.itheima03;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;

@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 {

        //获取session对象(tomcat创建session对象)
        HttpSession session = request.getSession();
        session.setAttribute("sick", "有点虚");

        //tomcat底层自动实现了
//        String id = session.getId();
//        Cookie cookie = new Cookie("JSESSIONID", id);
//        response.addCookie(cookie);
    }

}
package com.itheima03;

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("/Session02Servlet")
public class Session02Servlet 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对象(tomcat底层会根据浏览器发送JSESSIONID,取出之前的session对象)
        HttpSession session = request.getSession();
        Object sick = session.getAttribute("sick");
        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)    
             <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.itheima03.session;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;

@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 {

        //获取session对象
        HttpSession session = request.getSession();
        session.setAttribute("sick", "有点虚");

        //tomcat自动实现
//        String id = session.getId();
//        Cookie cookie = new Cookie("JSESSIONID", id);
//        response.addCookie(cookie);

        // 手动实现
         String id = session.getId();
        Cookie cookie = new Cookie("JSESSIONID", id);
        cookie.setMaxAge(60*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 需求分析

1591781837951.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="zh-CN">
<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="img"> <br>
            <input type="submit">
        </form>
</body>
<script>
    document.getElementById("img").onclick = function () {
        /*
        * 浏览器检测到网页元素被修改,自动刷新
        *   1). 通过给请求地址添加一个无意义的参数来欺骗浏览器更新
        *   2). 用当前时间毫秒值当成参数,可以一直更新
        * */
        // console.log("xx");
        var time = new Date().getTime();
        this.src = "CheckcodeServlet?time=" + time
    }
</script>
</html>

④ LoginServlet

package com.itheima.login;

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 {
        //TODO: 验证码校验
            //获取用户的验证码
        String checkcode = request.getParameter("checkcode");
            //获取服务器之前保存的验证阿妈
        String code_session = (String) request.getSession().getAttribute("code_session");
        if(!code_session.equals(checkcode)){

            request.setAttribute("errorMsg", "验证码错误");
            request.getRequestDispatcher("FailedServlet").forward(request, response);

            return;//校验失败,不再继续执行
        }

        //1. 获取请求
            //解决post请求中文乱码
        request.setCharacterEncoding("utf-8");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //2. 业务处理
            // 判断用户名和密码是否存在 (伪数据)
        if("jack".equals(username) && "123".equals(password)){
            //登录成功
            request.setAttribute("username", username);
            request.getRequestDispatcher("SuccessServlet").forward(request, response);
        }else{
            //登录失败
            request.setAttribute("errorMsg", "用户名不存在或密码错误");
            request.getRequestDispatcher("FailedServlet").forward(request, response);
        }

        //3. 响应结果
    }

}

⑤ successServlet

package com.itheima.login;

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("/SuccessServlet")
public class SuccessServlet 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 {
        Object username = request.getAttribute("username");
        response.setContentType("text/html;charset=utf-8");//响应中文乱码问题
        response.getWriter().println("登录成功,欢迎你:" + username);
    }

}

FailedServlet

package com.itheima.login;

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("/FailedServlet")
public class FailedServlet 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 {

        Object errorMsg = request.getAttribute("errorMsg");
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().println(errorMsg);
    }

}