文章目录
- 04【Cookie、Session】
- 一、Cookie
- 1.1 Cookie概述
- 1.1.1 协议的状态
- 1.1.2 Cookie的传递流程
- 1.2 Cookie的操作
- 1.2.1 创建Cookie
- 1.2.2 Cookie的销毁
- 1.2.3 Cookie的获取
- 1.2.4 Cookie中使用特殊字符的情况
- 1.2.5 Cookie的携带路径
- 2.2.6 Cookie的删除
- 二、Session
- 2.1 Session概述
- 2.1.1 Session介绍
- 2.1.2 Session与Cookie的区别
- 2.2 HttpSession接口的使用
- 2.4 Session的原理分析:
- 2.4.2 分析上述案例
- 2.4.3 Session的分析
- 2.5 持久化JSESSIONID
- 2.6 会话的过期时间
- 2.6.1 设置会话存活的时间
- 1)代码设置
- 2)设置xml
- 2.7 Session的生命周期
- 2.8 Session的持久化
- 三、综合案例
- 3.1 搭建项目
- 1)执行SQL脚本:
- 2)拷贝jar包:
- 3)jdbc.properies:
- 4)准备DataSourceUtils工具类:
- 5)实体类:
- 6)验证码工具类:
- 7)静态页面:
- 3.2 代码实现
- 3.2.1 用户登录
- 3.2.2 转账
- 3.2.3 用户注销
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进行解码 |
- 使用步骤
- 在创建cookie之前将数据编码
- 将编码后的数据存入cookie
- 获取到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)
- 根据会话ID去服务器里面查询对应的Session
- 没有携带:创建一个新的会话,并且在本次的响应头中添加Set-Cookie(内容就是本次的会话ID)
- 有携带:
- 请求头中的Cookie是否有携带会话ID
- 何时销毁?
- 1)默认30分钟后销毁(在30分钟期间,如果获取了对应的会话,那么会过期时间会重新续期为30分钟),可以通过
session.setMaxInactiveInterval()
设置过期时间 - 2)调用
session.invalidate()
方法时立即销毁 - 3)服务器关闭时session销毁
- 1)默认30分钟后销毁(在30分钟期间,如果获取了对应的会话,那么会过期时间会重新续期为30分钟),可以通过
- 为什么浏览器关闭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");
}
}