04【Cookie、Session】

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 04【Cookie、Session】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

04【Cookie、Session】

一、Cookie

1.1 Cookie概述

1.1.1 协议的状态

我们的协议按状态可划分为有状态无状态

  • 有状态:有状态简单来说就是有存储数据的功能,并且可以通过存储的数据来判断多次请求的关系,当请求端发送数据到服务端后,服务端能通过协议本身来判断该请求和上一个请求的关系;

在这里插入图片描述

  • 无状态:无状态就是没有数据存储的功能,当请求端发送数据到服务端后,服务端不能仅通过协议本身来判断该请求和上一个请求的关系;

我们之前学习过的HTTP协议就是无状态的;

无状态指的是:当一个客户端向服务器端发出请求,然后Web服务器返回响应,连接就被关闭了,在服务器端不保留连接的有关信息。可以看出,这种协议的结构是要比有状态的协议更简单的,一般来说实现起来也更简单,不需要使用状态机制。

1.1.2 Cookie的传递流程

HTTP协议是无状态的,无状态意味着,服务器无法给不同的客户端响应不同的信息,这样一些交互业务就无法支撑了。因此Cookie应运而生。

  • cookie的传递会经过下边这4步:
    • 1)Client发送HTTP请求给Server,Server判断本次请求是否有携带Cookie
    • 2)没有携带Cookie,则Server会在本次响应头中携带Set-Cookie响应头,Cookie信息就存在这个响应头中
    • 3)Client读取本次响应头中的Set-Cookie数据,创建一个Cookie并将Cookie的值存储到浏览器中,之后请求Server会将Cookie的数据读出来放在请求体头中(请求头的名称就叫Cookie)
    • 4)Server从请求头中获取Cookie信息,来判断是否是上一个人;

在这里插入图片描述

Cookie实际上是一小段的文本信息(key-val键值对格式)。客户端向服务器发起请求,如果服务器需要记录该用户状态,就用response向客户端发送一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再次向网站发送请求时,浏览器会把请求的网址和Cookie一起提交给服务器。服务器检查该Cookie,以此来辨别用户状态。

类似与我们去银行办理存蓄业务,第一次给你先办张银行卡,里面储存了身份证、密码、手机号码等个人信息。当你下次再来这个银行,银行及其能够直接识别出是你的卡,从而能够直接办理业务。

1.2 Cookie的操作

1.2.1 创建Cookie

  • Cookie相关方法:
Cookie类的方法 作用
Cookie(String name,String value) 通过构造方法创建一个Cookie 参数:键和值,都是String类型
String getName() 得到Cookie的键
String getValue() 得到Cookie的值
  • 在本次响应中添加Cookie
HttpServletResponse对象 作用
addCookie(Cookie cookie) 将服务器创建的Cookie通过响应发送给浏览器 参数:创建好的Cookie对象
  • 示例代码:
package com.dfbz.demo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author lscl
 * @version 1.0
 * @intro: 创建一个Cookie
 */
@WebServlet("/demo01")
public class Demo01Servlet_CreateCookie extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //创建Cookie对象
        Cookie cookie = new Cookie("user", "zhangsan");

        //写到浏览器端
        response.addCookie(cookie);

        //在网页给个提示
        response.setContentType("text/html;charset=utf-8");

        PrintWriter out = response.getWriter();

        out.print("向浏览器写入了一个Cookie信息");
    }
}

访问:http://localhost:8080/demo01:

  • 查询响应头

在这里插入图片描述

  • 查看浏览器的Cookie信息:

在这里插入图片描述

1.2.2 Cookie的销毁

Cookie创建后,是存储在客户端磁盘中(每个浏览器的存储位置不一样),在默认情况下,浏览器关闭之后,Cookie就销毁了;

Cookie设置过期时间 说明
void setMaxAge(int expiry) 设置会话过期的时间,单位是秒 正数:设置秒数 负数:无效,浏览器关闭就失效 零:删除Cookie
  • 示例代码:
package com.dfbz.demo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/demo02")
public class Demo02 extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        //创建Cookie对象
        Cookie man = new Cookie("user", "zhangsan");

        // 设置cookie的有效时间  单位: 秒
        man.setMaxAge(60 * 10);         //10分钟

        //写到浏览器端
        response.addCookie(man);

        //在网页给个提示
        response.setContentType("text/html;charset=utf-8");

        PrintWriter out = response.getWriter();

        out.print("向浏览器写入了一个Cookie信息");
    }
}

访问:http://localhost:8008/demo02

  • 运行效果

在这里插入图片描述

  • 查看本次响应头信息:

在这里插入图片描述

1.2.3 Cookie的获取

HttpServletRequest对象 作用
Cookie[] getCookies() 服务器得到浏览器端发送过来的所有的Cookie信息,返回的是一个Cookie的对象数组
  • 写入Cookie:
package com.dfbz.demo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro: 创建Cookie
 */
@WebServlet("/demo03")
public class Demo03Servlet_WriteCookie extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 创建Cookie对象
        Cookie userCookie = new Cookie("user", "lisi");
//        Cookie userCookie = new Cookie("user", URLEncoder.encode("李四","UTF-8"));

        // 添加到本次响应中
        response.addCookie(userCookie);

        // 创建一个Cookie
        Cookie addressCookie = new Cookie("address", "qinghai");
//        Cookie addressCookie = new Cookie("address", URLEncoder.encode("青海","UTF-8"));

        // 添加到本次响应中
        response.addCookie(addressCookie);    //写入

        response.getWriter().print("ok!");
    }
}

访问:http://localhost:8080/demo03

  • 抓包查看响应报文:

在这里插入图片描述

  • 读取Cookie:
package com.dfbz.demo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author lscl
 * @version 1.0
 * @intro: 读取Cookie
 */
@WebServlet("/demo04")
public class Demo04Servlet_ReadCookie extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 设置响应头以及编码
        response.setContentType("text/html;charset=utf-8");

        PrintWriter out = response.getWriter();

        //读取浏览器发送过来的所有Cookie信息
        Cookie[] cookies = request.getCookies();        //如果没有cookie,返回null

        //判断数组是否为空
        if (cookies != null) {
            //遍历数组
            for (Cookie cookie : cookies) {
                out.print("name:" + cookie.getName() + ",value:" + cookie.getValue() + "<hr>");
            }
        } else {
            out.print("没有Cookie");
        }
    }
}

访问:http://localhost:8080/demo04

  • 抓包查看请求报文:

在这里插入图片描述

1.2.4 Cookie中使用特殊字符的情况

在cookie值中使用分号(;)、逗号(,)、等号(=)、空格和中文等情况需要编码处理

类与方法 说明
java.net.URLEncoder.encode(“字符串”,“utf-8”) 把字符串使用utf-8进行编码
java.net.URLDecoder.decode(“字符串”,“utf-8”) 把字符串使用utf-8进行解码
  • 使用步骤
  1. 在创建cookie之前将数据编码
  2. 将编码后的数据存入cookie
  3. 获取到cookie之后,解码数据,获取正常内容

测试将Demo03中的拼音改为汉字:

Cookie userCookie = new Cookie("user", "李四");

Cookie addressCookie = new Cookie("address", "青海");

再次访问:http://localhost:8080/demo03

在这里插入图片描述

如果存储的是特殊字符以及中文,那么我们需要进行编码:

Cookie userCookie = new Cookie("user", URLEncoder.encode("李四","UTF-8"));

Cookie addressCookie = new Cookie("address", URLEncoder.encode("青海","UTF-8"));

存值的时候进行了编码,那么取值的时候应该要解码:

for (Cookie cookie : cookies) {
    out.print("name:" + cookie.getName() + ",value:" + URLDecoder.decode(cookie.getValue(), "UTF-8") + "<hr>");
}

重启服务器,再次访问:http://localhost:8080/demo03

查看响应头:

在这里插入图片描述

访问:http://localhost:8080/demo04

查看请求头:

在这里插入图片描述

1.2.5 Cookie的携带路径

  • 设置路径的方法:
Cookie设置路径的方法 功能
cookie.setPath(路径); 用于设置Cookie访问的路径 访问这个路径或路径的子目录都可以访问Cookie 其它的路径无法访问

Tips:Cookie的默认的路径为当前Servlet的同级目录;

  • 创建一个Cookie工具类:
package com.dfbz.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
/**
 * @author lscl
 * @version 1.0
 * @intro: 根据CookieName获取在本次请求中获取Cookie的Value
 */
public class CookieUtils {
    /**
     * 根据cookieName获取Cookie的value
     * @param request
     * @param name
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String name) {

        // 得到所有的Cookie
        Cookie[] cookies = request.getCookies();
        // 判断数组是否为空
        if (cookies!=null) {
            for (Cookie cookie : cookies) {
                // 判断名字是否相等
                if (cookie.getName().equals(name)) {

                    // 返回此cookie的值
                    return cookie.getValue();
                }
            }
        }
        //数组为空或没有找到名字
        return null;
    }
}
  • 创建一个Cookie,使用默认携带路径:
package com.dfbz.demo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro: 使用默认Cookie携带路径
 */
@WebServlet("/user/findAll")
public class Demo05Servlet_CookiePath extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 创建一个Cookie,默认的携带路径为 当前Servlet的同级路径  (/user)
        Cookie cookie=new Cookie("province","JiangXi");

        // 等价于
//        cookie.setPath("/user");
        
        // Cookie的默认携带路径是当前的servlet的同级路径,如果将携带路径设置为"",那么该携带路径也为当前的servlet同级目录
//        cookie.setPath("");
        
        response.addCookie(cookie);

        response.getWriter().write("ok!");
    }
}

Tips:如果将携带路径设置为””(空字符串),那么该携带路径也为当前的servlet同级目录

  • 能够携带的路径:
访问地址 能否访问到Cookie
http://localhost:8080/user/findById 可以
http://localhost:8080/user/findById/abc 可以
http://localhost:8080/user 可以
http://localhost:8080/demo06 不可以
http://localhost:8080/ 不可以
  • 读取Cookie测试:
package com.dfbz.demo;

import com.dfbz.utils.CookieUtils;

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 lscl
 * @version 1.0
 * @intro: 读取Cookie信息
 */
//@WebServlet("/user/findById")           // 可以读取到
//@WebServlet("/user/")                       // 可以读取到
//@WebServlet("/user/demo06")                       // 可以读取到
@WebServlet("/demo06")                       // 不可以读取到
public class Demo06Servlet_GetCookie extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 根据CookieName获取Cookie值
        String value = CookieUtils.getCookieValue(request, "province");

        response.getWriter().write("value: " + value);
    }
}

2.2.6 Cookie的删除

  • Cookie删除的相关方法:
Cookie的删除 说明
setMaxAge(0) 设置生命周期为0,表示删除Cookie的信息
  • 示例:删除指定的Cookie信息,注意Cookie的访问路径要和当初签发的相同,否则Cookie将不会被删除
package com.dfbz.demo;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author lscl
 * @version 1.0
 * @intro: 删除Cookie
 */
@WebServlet("/demo07")
public class Demo07Servlet_DeleteCookie extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //创建Cookie对象,与要删除的键同名
        Cookie cookie = new Cookie("province", "");

        //设置路径(必须要和当初签发cookie的路径一致)
        cookie.setPath("/user");

        //设置过期时间
        cookie.setMaxAge(0);

        //写入浏览器
        response.addCookie(cookie);

        response.getWriter().write("ok!");
    }
}

二、Session

2.1 Session概述

2.1.1 Session介绍

如果把用户名、密码等重要隐私都存到客户端的Cookie中,还是有泄密风险。为了更安全,把机密信息保存到服务器上,这就是 Session;Session是服务器上维护的客户档案,可以理解为服务器端数据库中有一张user表,里面存放了客户端的用户信息。SessionID就是这张表的主键ID;

Session与Cookie不同,session是属于服务器端的会话技术,数据是保存在服务器的内存中。每个会话中保存它自己的数据,其它会话不能访问。不同的会话之间不能实现数据的共享。

2.1.2 Session与Cookie的区别

  • 1)Cookie属于客户端会话技术,数据保存在浏览器端文件中(磁盘),Cookie中键和值都是String类型

  • 2)Session属于服务器端的会话技术,数据保存服务器内存中,Session中键是String,值是Object类型

2.2 HttpSession接口的使用

  • 创建会话的方法:
创建session 描述
HttpSession request.getSession() 作用:通过请求对象创建一个会话对象 如果当前用户会话不存在,创建会话。如果会话已经存在,这个方法返回已经存在的会话对象。

Tips:用户第1次访问,使用request.getSession()时创建一个会话对象HttpSession。

  • HttpSession相关API:
HttpSession接口方法 作用
String getId() 得到会话的ID,在服务器上唯一的32位的十六进制数
long getCreationTime() 表示会话创建的时间,返回long类型。表示1970-1-1到这个时间之间相差的毫秒数
long getLastAccessedTime() 表示会话上次访问的时间
boolean isNew() 判断当前是否是一个新的会话,是的返回true
ServletContext getServletContext() 通过会话得到上下文对象
  • 示例代码:
package com.dfbz.demo;

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;
import java.io.PrintWriter;
import java.sql.Timestamp;

/**
 * @author lscl
 * @version 1.0
 * @intro: session 基本API
 */
@WebServlet("/demo01")
public class Demo01Servlet_SessionApi extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=utf-8");
        
        PrintWriter out = response.getWriter();

        //得到会话对象
        HttpSession session = request.getSession();

        //得到会话的ID,在服务器上唯一的32位的十六进制数
        out.print("会话ID:" + session.getId() + "<hr>");

        //表示会话创建的时间,返回long类型。
        out.print("会话创建时间:" + new Timestamp(session.getCreationTime()) + "<hr>");

        //表示会话上次访问的时间
        out.print("上次访问的时间:" + new Timestamp(session.getLastAccessedTime()) + "<hr>");

        //判断当前是否是一个新的会话,是的返回true
        out.print("是否新的会话:" + session.isNew() + "<hr>");

        //通过会话得到上下文对象
        out.print("上下文对象:" + session.getServletContext() + "<hr>");
    }
}

访问:http://localhost:8080/demo01

在这里插入图片描述

  • 会话域对象的方法:

session也是一个域对象,也有域对象的几个方法,如下:

HttpSession的方法 作用
Object getAttribute(“名字”) 从会话域中得到一个值
void setAttribute(“名字”,Object数据) 向会话域中添加一对键和值
void removeAttribute(“名字”) 从会话域中删除键和值

小案例:

1)在一个SetServlet中,向Session中添加一个用户:张三,另一个GetServlet中,从Session中取出用户并输出在网页上。

2)使用一个浏览器存,另一个浏览器取,看能不能取出来。

  • SetServlet
package com.dfbz.demo;

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 lscl
 * @version 1.0
 * @intro: 向会话域中存值
 */
@WebServlet("/set")
public class Demo02SetServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //创建会话对象
        HttpSession session = request.getSession();

        //向会话域中添加键和值
        session.setAttribute("country", "China");

        response.getWriter().write("ok!");
    }
}
  • GetServlet
package com.dfbz.demo;

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;
import java.io.PrintWriter;


/**
 * @author lscl
 * @version 1.0
 * @intro: 从会话域中取出值
 */
@WebServlet("/get")
public class Demo03GetServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {

        PrintWriter out = response.getWriter();

        // 得到会话对象
        HttpSession session = request.getSession();

        // 取出来
        out.print(session.getAttribute("country"));
    }
}

2.4 Session的原理分析:

在这里插入图片描述

1)第1次访问用户没有会话ID,调用getSession方法,服务器创建一个会话对象

2)每个会话都有一个唯一的ID,通过Cookie发送给浏览器。

3)浏览器得到会话ID,下次访问再通过Cookie发送给服务器,相当于带了密码条给服务器。

4)服务器通过Cookie中的ID,与服务器中会话ID进行比较,访问同一个会话域中数据

2.4.2 分析上述案例

1)访问:http://localhost:8080/set

客户端第一次访问,服务器创建一个session会话,之后将此次会话的ID以Cookie的方式写到客户端,此Cookie的携带路径默认为整个项目都携带(不是当前servlet的访问路径的同级目录);

我们通过浏览器抓包工具查看响应头信息:

在这里插入图片描述

2)访问:http://localhost:8080/get

在上一步的操作中,服务端已经将JSESSIONID以Cookie的形式写回客户端了,下次客户端访问服务端的任意请求都会携带此Cookie,服务端根据sessionid查询原来的会话,查询到了则返回该会话,如果查询不到则创建一个新的会话,并将此次新的会话的sessionid通过cookie的形式写回客户端

在这里插入图片描述

2.4.3 Session的分析

  • 问:浏览器关闭以后,还能不能得到之前会话域中的信息?
  • 答:不能,因为会话ID已经丢失,再次访问会话ID不同的。得到不之前会话中的数据。

  • 问:如果浏览器关闭,服务器上的会话信息是否还存在?
  • 答:还是存在,直接会话过期之前都是存在的。

  • 如何让浏览器关闭还可以访问之前session中的数据?
  • 答:将Cookie中的Sessionid持久化;

2.5 持久化JSESSIONID

我们通过刚刚分析的session原理就能够明白,要想找到服务器的会话,必须要携带对应的会话ID,如果会话ID没有携带,那么服务器会创建一个新的会话;而**Cookie在默认情况下,浏览器关闭就失效了;**那么上一次会话的信息也就找不到了,再次获取session时,服务器会创建一个新的会话给客户端(上一次的会话并没有销毁,只是找不到了)

  • 修改Demo02SetServlet代码:
package com.dfbz.demo;


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

/**
 * @author lscl
 * @version 1.0
 * @intro: sessionID持久化
 */
@WebServlet("/demo04")
public class Demo04Servlet_Persistent extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //创建会话对象
        HttpSession session = request.getSession();

        //向会话域中添加键和值
        session.setAttribute("country","China");

        // 获取此次会话的id
        String id = session.getId();

        Cookie cookie=new Cookie("JSESSIONID",id);

        // 设置过期时间10分钟
        cookie.setMaxAge(600);

        // 写回到前端
        response.addCookie(cookie);
    }
}

查看JSESSIONID的过期时间:

在这里插入图片描述

可以关闭浏览器,再次访问:http://localhost:8080/get

发现依然可以获取到数据;

2.6 会话的过期时间

默认情况下,关闭浏览器后,上一次会话就找不到了,原因是因为cookie销毁了,JSESSIONID没有了,所以才导致服务器端创建了一个新的会话,其实上一次的会话并没有销毁,那么问题来了,Session在服务器上默认的销毁时间是多久?如何查看?

session中的方法 说明
int getMaxInactiveInterval() 得到服务器上会话最大的非活动时间间隔,默认是1800秒(30分钟)
  • 时间间隔的含义:如果你在这段时间内再次发送请求给服务器,服务器将会重新计时。

2.6.1 设置会话存活的时间

1)代码设置

HttpSession的方法 功能描述
void setMaxInactiveInterval(int 秒) 设置会话最大非活动时间时隔,单位是秒
  • 示例代码:
package com.dfbz.demo;


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;
import java.io.PrintWriter;

/**
 * @author lscl
 * @version 1.0
 * @intro: 会话存在的时间
 */
@WebServlet("/demo05")
public class Demo05Servlet_SessionExpire extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=utf-8");

        PrintWriter out = response.getWriter();

        // 创建会话
        HttpSession session = request.getSession();

        // 设置过期时间,10秒
        session.setMaxInactiveInterval(10);

        // 当前会话存活的时间是
        out.print("会话存在的时间:" + session.getMaxInactiveInterval() + "<hr>");
        out.print("会话ID: " + session.getId());
    }
}

先访问:http://localhost:8080/set 存入数据到session

在访问:http://localhost:8080/demo04 将session的过期时间设置为10s

最后访问:http://localhost:8080/get 查看10s后,session的值就不存在了

2)设置xml

除了通过代码来设置session的过期时间之外,我们还可以通过配置web.xml文件来设置项目会话过期的时间

  • 添加web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 配置会话过期的时间;单位: 分钟 如果设置为零或负数,则表示会话将永远不会超时。 -->
    <session-config>
        <session-timeout>5</session-timeout>
    </session-config>
</web-app>

疑问:设置web.xml的会话配置,并且在代码中设置会话过期的时间,以哪个为准?

  • 就近原则,以代码为准,代码会覆盖前面配置

3)立刻失效

HttpSession方法 功能描述
invalidate() 会话立刻失效,一般用于用户退出,注销

修改demo04:

package com.dfbz.demo;


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;
import java.io.PrintWriter;

/**
 * 会话存在的时间
 */
@WebServlet("/demo04")
public class Demo04 extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {

        response.setContentType("text/html;charset=utf-8");

        PrintWriter out = response.getWriter();

        //创建会话
        HttpSession session = request.getSession();

        // 让session立即失效
        session.invalidate();

        out.print("会话ID: " + session.getId()+"已经失效!");
    }
}

访问:http://localhost:8080/set 存入数据到session

访问:http://localhost:8080/get 可以正常查询数据

访问:http://localhost:8080/demo04 session失效

访问:http://localhost:8080/get 查询不到数据

2.7 Session的生命周期

  • 何时创建?
    • request.getSession()判断是否要创建session
      • 请求头中的Cookie是否有携带会话ID
        • 有携带:
          • 根据会话ID去服务器里面查询对应的Session
            • 查询到了:返回对应的session
            • 查询不到:创建一个新的会话,并且在本次的响应头中添加Set-Cookie(内容就是本次的会话ID)
        • 没有携带:创建一个新的会话,并且在本次的响应头中添加Set-Cookie(内容就是本次的会话ID)
  • 何时销毁?
    • 1)默认30分钟后销毁(在30分钟期间,如果获取了对应的会话,那么会过期时间会重新续期为30分钟),可以通过session.setMaxInactiveInterval()设置过期时间
    • 2)调用session.invalidate()方法时立即销毁
    • 3)服务器关闭时session销毁
  • 为什么浏览器关闭session就”销毁”了呢?
    • 浏览器关闭是存储sessionid的cookie被销毁了,导致下次来到服务器端找不到上一次的会话,因此服务器会创建一个新的session,并将新session的id以cookie的形式写回到客户端;

2.8 Session的持久化

我们都知道session是存储在服务器的内存中的,当服务器关闭之后,session就销毁了,session的持久化指的是将session的内容持久化到磁盘上,进行永久保存,想要session的内容能够持久化必须保证对象实现Serializable接口;

session的持久化也叫钝化与活化

  • 钝化:从内存到磁盘
  • 活化:从磁盘到内存

在这里插入图片描述

在web目录下创建META-INF目录,然后创建Context.xml文件:

<Context>
    <!-- maxIdleSwap:session中的对象多长时间不使用就钝化(单位为分钟) -->
    <!-- directory:钝化后的对象的文件写到磁盘的哪个目录下  配置钝化的对象文件默认在work/catalina/localhost/钝化文件 -->
    <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
        <Store className="org.apache.catalina.session.FileStore" directory="d:/aaa" />
    </Manager>
</Context>

在这里插入图片描述

创建Person对象(必须要实现Serializable接口):

/**
 * 实现Serializable接口
 */
public class Person implements Serializable {

    private String id;
    private String name;
}
  • CreateServlet:
package com.dfbz.demo;

import com.dfbz.entity.Person;

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 lscl
 * @version 1.0
 * @intro:
 */
@WebServlet("/create")
public class Demo07_CreateServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=utf8");

        Person person = new Person("1", "张三");

        // 将对象存入session
        request.getSession().setAttribute("person", person);

        response.getWriter().println("create访问成功...会话ID" + request.getSession().getId() + "<hr>");
        response.getWriter().println("person: " + person);
    }
}
  • QueryServlet:
package com.dfbz.demo;

import com.dfbz.entity.Person;

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.io.PrintWriter;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@WebServlet("/query")
public class Demo08_QueryServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf8");

        Person person = (Person) request.getSession().getAttribute("person");

        PrintWriter writer = response.getWriter();

        // 和之前钝化的id是同一个
        writer.println("query访问成功...会话ID" + request.getSession().getId() + "<hr>");

        writer.println("person: " + person + "<hr>");
    }
}
  • DeleteServlet:
package com.dfbz.demo;

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.io.PrintWriter;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@WebServlet("/delete")
public class Demo09_DeleteServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=utf8");

        PrintWriter writer = response.getWriter();

        writer.println("delete访问成功..." + request.getSession().getId());

        // 销毁session(钝化的文件也会被删除)
        request.getSession().invalidate();
    }
}

访问:http://localhost:8080/create

等待一分钟,观察:D:/aaa目录下是否有xxx.session文件产生,等待文件生成后关闭服务器(session销毁)

再次访问:http://localhost:8080/query 发现session中保存的person依旧存在

Tips:开启session持久化后,如果有创建session,那么默认情况下服务器关闭会将session都钝化到文件中;

三、综合案例

3.1 搭建项目

1)执行SQL脚本:

CREATE TABLE `account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `money` double(255, 0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO account VALUES (1, 'zhangsan', 'admin', 1000);
INSERT INTO account VALUES (2, 'lisi', '123456', 1500);
INSERT INTO account VALUES (3, 'wangwu', '111', 5000);

2)拷贝jar包:

在这里插入图片描述

3)jdbc.properies:

jdbc.username=root
jdbc.password=admin
jdbc.url=jdbc:mysql://localhost:3306/xb
jdbc.driverClassName=com.mysql.jdbc.Driver

4)准备DataSourceUtils工具类:

package com.dfbz.utils;

import com.alibaba.druid.pool.DruidDataSource;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * 数据源的工具类
 */
public class DataSourceUtils {

    private static DataSource ds;

    /**
     * 在静态代码块中创建数据源对象
     */
    static {
        // 创建druid数据源
        DruidDataSource dataSource = new DruidDataSource();

        Properties prop = new Properties();
        try {
            // 加载配置文件
            prop.load(DataSourceUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        dataSource.setUsername(prop.getProperty("jdbc.username"));
        dataSource.setPassword(prop.getProperty("jdbc.password"));
        dataSource.setUrl(prop.getProperty("jdbc.url"));
        dataSource.setDriverClassName(prop.getProperty("jdbc.driverClassName"));

        ds=dataSource;
    }

    /**
     * 得到数据源
     */
    public static DataSource getDataSource() {
        return ds;
    }


    /**
     * 从连接池中得到连接对象
     */
    public static Connection getConnection() {
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }


    /**
     * 释放资源
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        //关闭结果集
        if (rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        //关闭语句对象
        if (stmt!=null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        //关闭连接对象
        if (conn!=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭连接
     */
    public static void close(Connection conn, Statement stmt) {
        close(conn, stmt, null);
    }
}

5)实体类:

package com.dfbz.entity;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Account {
    private Integer id;
    private String username;
    private String password;
    private Double money;

    // 省略get/set/toString...
}

6)验证码工具类:

package com.dfbz.util;

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.Date;
import java.util.Random;

/**
 * @author lscl
 * @version 1.0
 * @intro: 生成验证码
 */
@WebServlet("/generateCode")
public class GenerateImageCodeServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;

    private static final char[] CH = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".toCharArray();
    private static final int IMAGE_WIDTH = 73;
    private static final int IMAGE_HEIGHT = 28;
    private static final int LINE_NUM = 30;
    private static final int RANDOM_NUM = 4;
    Random random = new Random();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("image/jpg");//设置相应类型,告诉浏览器输出的内容为图片
        response.setHeader("Pragma", "No-cache");//设置响应头信息,告诉浏览器不要缓存此内容
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expire", new Date().getTime());

        BufferedImage bi = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_BGR);
        Graphics g = bi.getGraphics();
        g.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
        g.setColor(getRandomColor(110, 133));
        g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));
        // 绘制干扰线
        for (int i = 1; i <= LINE_NUM; i++) {
            int x = random.nextInt(IMAGE_WIDTH);
            int y = random.nextInt(IMAGE_HEIGHT);
            int xl = random.nextInt(13);
            int yl = random.nextInt(15);
            g.drawLine(x, y, x + xl, y + yl);
        }

        // 绘制随机字符
        StringBuilder sb = new StringBuilder();
        String str = null;
        for (int i = 0; i < RANDOM_NUM; i++) {
            g.setFont(new Font("Fixedsys", Font.CENTER_BASELINE, 18));
            g.setColor(new Color(random.nextInt(101), random.nextInt(111), random.nextInt(121)));
            str = CH[random.nextInt(CH.length)] + "";
            g.drawString(str, 13 * i, 16);
            g.translate(random.nextInt(3), random.nextInt(3));
            sb.append(str);
        }
        g.dispose();
        request.getSession().setAttribute("safeCode", sb.toString());
        ImageIO.write(bi, "JPG", response.getOutputStream());
    }

    /**
     * 获得颜色
     */
    private Color getRandomColor(int fc, int bc) {
        if (fc > 255)
            fc = 255;
        if (bc > 255)
            bc = 255;
        int r = fc + random.nextInt(bc - fc - 16);
        int g = fc + random.nextInt(bc - fc - 14);
        int b = fc + random.nextInt(bc - fc - 18);
        return new Color(r, g, b);
    }
}

7)静态页面:

  • 登录页面:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>

<form action="/login" method="post">
    用户名:<input type="text" name="username">
    <hr>
    密码:<input type="password" name="password">
    <hr>
    验证码:<input type="text" name="checkCode">
        <a href="javascript:location.reload()">
            <img src="generateCode" alt="">
        </a>
    <hr>
    <input type="submit" value="登录">
</form>
</body>
</html>

在这里插入图片描述

  • 转账页面:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>转账页面</title>
</head>
<body>
<!--这个页面必须要登录才可以访问-->
<form action="/transfer" method="post">
    请输入收款方账号: <input type="text" placeholder="请输入收款方账号" name="username">
    <hr>
    请输入转账金额: <input type="text" placeholder="请输入转账金额" name="money">
    <hr>
    <input type="submit" value="转账">
</form>
</body>
</html>

在这里插入图片描述

3.2 代码实现

3.2.1 用户登录

  • LoginServlet:
package com.dfbz.servlet;

import com.dfbz.entity.Account;
import com.dfbz.util.DataSourceUtils;

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;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author lscl
 * @version 1.0
 * @intro: 用户登录
 */
@WebServlet("/login")
public class LoginServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 收集前端的参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String checkCode = request.getParameter("checkCode");

        // 获取session对象
        HttpSession session = request.getSession();

        response.setContentType("text/html;charset=utf8");
        PrintWriter writer = response.getWriter();

        // 判断验证码是否正确
        if (!session.getAttribute("safeCode").equals(checkCode)) {
            writer.println("<h1>验证错误!登录失败!</h1>");
            writer.println("<a href='/login.html'>重新登录</a>");
            return;
        }


        // 代码走到这里说明验证码正确

        // 根据用户名查询用户
        Account account = findByUsername(username);

        if(account == null || !account.getPassword().equals(password)){
            writer.println("<h1>用户名或密码错误!</h1>");
            writer.println("<a href='/login.html'>重新登录</a>");
            return;
        }

        // 代码走到这里说明验证码/用户名/密码都正确

        // 存入登录成功的用户信息存入session
        session.setAttribute("loginAccount", account);

        // 重定向到转账页面
        response.sendRedirect(request.getContextPath()+"/transfer.html");
    }

    /**
     * 根据账户名查询账户
     *
     * @param username
     * @return
     */
    public Account findByUsername(String username) {

        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            // 1. 从连接池中获取一个连接
            connection = DataSourceUtils.getConnection();

            // 2. 获取一个数据库连接
            ps = connection.prepareStatement("select * from account where username=?");
            ps.setString(1, username);

            // 3. 执行查询获取结果集
            rs = ps.executeQuery();

            if (rs.next()) {

                // 能查询到记录说明传递进来的username肯定等于查询行中的username
                Integer id = rs.getInt("id");
                String password = rs.getString("password");
                Double money = rs.getDouble("money");

                return new Account(id, username, password, money);
            }

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            // 释放连接
            DataSourceUtils.close(connection,ps,rs);
        }

        return null;
    }
}

3.2.2 转账

  • TransferServlet
package com.dfbz.servlet;

import com.dfbz.entity.Account;
import com.dfbz.util.DataSourceUtils;

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;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@WebServlet("/transfer")
public class TransferServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.setContentType("text/html;charset=utf8");
        PrintWriter out = resp.getWriter();


        // 1. 获取session
        HttpSession session = req.getSession();

        // 2. 获取session中的account信息
        Account loginAccount = (Account) session.getAttribute("loginAccount");


        if (loginAccount == null) {
            // 说明用户没有登录
            out.println("<h1>请先登录</h1>");
            out.println("<a href='/login.html'>去登录</a>");
            return;
        }

        // 获取前端传递过来的收款账号
        String receiveName = req.getParameter("username");

        // 获取前端传递过来的转账金额
        Double money = Double.parseDouble(req.getParameter("money"));

        // 进行转账
        Boolean flag = transfer(loginAccount.getUsername(), receiveName, money);

        if (flag) {
            resp.getWriter().write("<h1>转账成功!</h1>");

        } else {
            resp.getWriter().write("转账失败!");
        }

        resp.getWriter().write("<a href='/transfer.html'>去转账</a>");
    }

    /**
     * 转账
     *
     * @param transferName 转账方
     * @param receiveName  收款方
     * @param money        转账的金额
     */
    public Boolean transfer(String transferName, String receiveName, Double money) {

        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;


        try {
            connection = DataSourceUtils.getConnection();
            connection.setAutoCommit(false);                // 开启手动提交

            // 转账SQL
            ps = connection.prepareStatement("update account set money=money+? where username=?");

            ps.setDouble(1, money);
            ps.setString(2, receiveName);

            // 执行加钱操作
            ps.executeUpdate();


            ps = connection.prepareStatement("update account set money=money-? where username=?");
            ps.setDouble(1, money);
            ps.setString(2, transferName);

            // 执行扣款操作
            ps.executeUpdate();

            connection.commit();                // 提交事务

            return true;           // 转账成功
        } catch (SQLException e) {
            e.printStackTrace();

            try {
                if (connection != null) {
                    connection.rollback();      // 出现异常回滚事务
                }
            } catch (SQLException e2) {
                e2.printStackTrace();
            }

            return false;                       // 转账失败
        } finally {
            // 释放连接
            DataSourceUtils.close(connection, ps, rs);
        }

    }
}

3.2.3 用户注销

  • LogoutServlet
package com.dfbz.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;


/**
 * @author lscl
 * @version 1.0
 * @intro: 用户注销
 */
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        HttpSession session = request.getSession();

        // 销毁session
        session.invalidate();

        // 重定向到登录页面
        response.sendRedirect(request.getContextPath() + "/login.html");
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131641.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!