Cookie
会话概述
什么是会话?
日常生活中:从拨通电话到挂断电话之间的一连串你问我答的过程就是一个会话。
B/S架构中:从浏览器第一次给服务器发送请求时,建立会话;直到有一方断开,会话结束。
一次会话:包含多次请求响应。
会话技术
**问题:**Http是一个无状态协议,同一个会话的连续两个请求相互独立,彼此并不了解
作用:用于存储浏览器与服务器在请求和响应过程中产生的数据,在一次会话中(多次请求响应), 共享数据
客户端会话技术:cookie
服务器端会话技术:session
Cookie
概述
Cookie作用:在一次会话的多次请求之间共享数据,将数据保存到客户端(浏览器)
使用场景例如:jd购物车
快速入门
1. 设置数据到cookie中
// 1.创建cookie对象,设置数据
Cookie cookie = new Cookie(String name,String value);
// 2.通过response,响应(返回)cookie
response.addCookie(cookie);
2. 从cookie中获取数据
// 1.通过request对象,接收cookie数组
Cookie[] cookies = request.getCookies();
// 2.遍历数组
获取name值: String name = cookie.getName();
获取value值: String value = cookie.getValue();
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/xxx/SetCookie">添加商品到购物车</a> <br>
<a href="/xxx/GetCookie">查看购物车</a> <br>
</body>
</html>
@WebServlet("/GetCookie")
public class GetCookie 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 {
//获取浏览器发送的所有cookie
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.println(name+ "--" + value);
}
}
}
@WebServlet("/SetCookie")
public class SetCookie 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 {
System.out.println("用户添加小米手机到购物车");
//1. 创建一个Cookie对象
Cookie cookie = new Cookie("phone","xiaomi");
//2. 将cookie响应给浏览器
response.addCookie(cookie);
}
}
google浏览器中查看Cookie
工作原理
基于HTTP协议:
1. 服务器发送Cookie给浏览器是通过 : 响应(响应头 set-cookie)
Set-Cookie: phone=xiaomi
Set-Cookie: computer=lenovo
2. 浏览器发送Cookie给服务器是通过: 请求(请求头 cookie)
Cookie: phone=xiaomi; computer=lenovo
Cookie详情
服务器可以发送多个Cookie
// 1. 创建多个cookie对象
Cookie cookie1 = new Cookie("name","lucy");
Cookie cookie2 = new Cookie("age","18");
// 2. 通过response响应多个
response.addCookie(cookie1);
response.addCookie(cookie2);
@WebServlet("/MultipleCookie")
public class MultipleCookie extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 创建多个cookie对象
Cookie cookie1 = new Cookie("name","lucy");
Cookie cookie2 = new Cookie("age","18");
// 2. 通过response响应多个
response.addCookie(cookie1);
response.addCookie(cookie2);
}
}
Cookie存储中文
* tomcat8之前的版本,不支持中文
* tomcat8以后的版本,支持中文...
但是按照 Rfc6265Cookie规范,在cookie值中不能使用分号(;)、逗号(,)、等号(=)以及空格
* 解决
java.net.URLEncoder.encode(字符串","utf-8") 把字符串使用utf-8进行编码
java.net.URLDecoder.decode(字符串","utf-8") 把字符串使用utf-8进行解码
@WebServlet("/MessyServlet")
public class MessyServlet 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 {
/*
* tomcat8在cookie中使用中文是没问题的(tomcat8以前是有乱码问题)
*
* (不论哪个版本的tomcat)
* 但是按照 Rfc6265Cookie规范,在cookie值中不能使用分号(;)、逗号(,)、等号(=)以及空格
*
* 解决: URL编码
* 服务器发送cookie前, 先对value进行编码 -> (空格 转换成 + )
* 服务器接收到cookie时, 对value进行解码
*
* java.net.URLEncoder.encode(字符串","utf-8") 把字符串使用utf-8进行编码
java.net.URLDecoder.decode(字符串","utf-8") 把字符串使用utf-8进行解码
* */
String value = "张三 三";
value = URLEncoder.encode(value,"utf-8");
Cookie cookie = new Cookie("name", value);
//如果cookie中有非法字符, 报错 IllegalArgumentException(非法参数异常)
response.addCookie(cookie);
}
}
@WebServlet("/DecodeServlet")
public class DecodeServlet 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 {
//获取浏览器发送的所有cookie
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
//对编码过的value进行解码
value = URLDecoder.decode(value,"utf-8");
System.out.println(name+ "--" + value);
}
}
}
Cookie的域名作用(domain)
1. cookie信息中的域名作用是标记这个cookie的归属
在浏览器中,既保存了域名为localhost的cookie,又保存baidu的cookie
那么访问的网站是 http://localhost:8080, 浏览器的请求只会携带硬为localhost的cookie
2. 默认情况下,cookie的域名是发送此cookie的服务器域名
url : 协议://域名:端口/资源位置
Cookie的路径作用(path)
0. 在项目中,cookie的路径默认为项目的虚拟路径
url-> http://localhost:8080/项目虚拟路径/Servlet的虚拟路径
比如: 项目虚拟路径 = /xxx
1. 第一个作用: cookie信息中的path和name共同决定了cookie的唯一性
a. PathServlet被浏览器每访问一次, cookie就会发送一次
b. 如果服务器再次发送一个同 path+name的cookie,会覆盖浏览器的那个cookie
(新覆盖旧)
c. 服务器再次发送一个同 path, 异name的cookie , 不会覆盖
d. 服务器再次发送一个异path, 同name的cookie, 不会覆盖
2. 第二个作用: cookie的path还决定了cookie允许被访问的范围
特点:Cookie在其设置的有效路径和其子路径下能够被访问到;
举例: 有一个cookie,路径为 /xxx/aaa
只有访问 http://localhost:8080/xxx/aaa 以及其子路径,才会携带这个cookie
问题:
1. 访问http://localhost:8080/xxx/PathServlet -> PathServlet
会携带这个cookie吗 -> 不会
2. 访问http://localhost:8080/xxx/aaa -> AaaServlet
会携带这个cookie吗 -> 会
3. 访问http://localhost:8080/xxx/aaa/MyServlet -> MyServlet
会携带这个cookie吗 -> 会
#API : cookie.setPath(虚拟路径);
路径要以 / 开头
@WebServlet("/PathServlet")
public class PathServlet 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 {
//第一个作用
// 抽取方法的快捷键:ctrl + alt + m
// method(response);
/*
第二个作用: cookie的path还决定了cookie允许被访问的范围
特点:Cookie在其设置的有效路径和其子路径下能够被访问到;
cookie的路径默认为当前项目的虚拟路径 /xxx
举例: 有一个cookie,路径为/xxx
只有访问 http://localhost:8080/xxx 以及其子路径,才会携带这个cookie
举例2: 有一个cookie,路径为 /xxx/aaa
只有访问 http://localhost:8080/xxx/aaa 以及其子路径,才会携带这个cookie
问题:
1. 访问http://localhost:8080/xxx/PathServlet -> PathServlet
会携带这个cookie吗 -> 不会
2. 访问http://localhost:8080/xxx/aaa -> AaaServlet
会携带这个cookie吗 -> 会
3. 访问http://localhost:8080/xxx/aaa/MyServlet -> MyServlet
会携带这个cookie吗 -> 会
应用:
百度文档: http://www.baidu.com:80/doc
-> cookie("book","java") , path= /doc
百度地图 http://www.baidu.com:80/map
只有百度文档才需要book这个cookie,访问百度地图,不会携带book这个cookie过去
*/
Cookie cookie = new Cookie("computer", "apple");
cookie.setPath("/xxx/aaa");
response.addCookie(cookie);
}
private void method(HttpServletResponse response) {
/*
* 1. 路径的第一个作用: path + name -> cookie的唯一性
*
* a. PathServlet被浏览器每访问一次, cookie就会发送一次
* b. 如果服务器再次发送一个同 path+name的cookie,会覆盖浏览器的那个cookie
* (新覆盖旧)
* c. 服务器再次发送一个同 path, 异name的cookie , 不会覆盖
* d. 服务器再次发送一个异path, 同name的cookie, 不会覆盖
* */
// Cookie cookie = new Cookie("phone", "xiaomi"); 1号
// Cookie cookie = new Cookie("phone", "huawei");//2号, 会覆盖1号
// Cookie cookie = new Cookie("computer", "lenovo");//3号,不会覆盖
Cookie cookie = new Cookie("computer", "apple");//4号, 重置路径,不会覆盖
cookie.setPath("/aaa");
response.addCookie(cookie);
}
}
Cookie的存活时间
# 浏览器中cookie的信息
1. 创建时间: 浏览器接收到此cookie的时间
2. 到期时间: cookie销毁的时间
# cookie的存活时间有两种
1. 会话级别(默认,浏览器关闭,cookie销毁 )
浏览器中的cookie显示(浏览会话结束时: 浏览器关闭)
原因: 浏览器将cookie保存内存中(临时的)
cookie在一个会话中(浏览器从打开到关闭: 访问服务器)共享数据
2. 持久级别(需要手动设置)
cookie.setMaxAge(int second); -- 单位是秒
正数:指定存活时间,持久化浏览器的磁盘中,到期后自动销毁
零:立即销毁
原因: 浏览器将cookie保存在硬盘上(持久的)
cookie在可以在多个会话中(浏览器从打开到关闭多次: 访问服务器)共享数据
@WebServlet("/MaxAgeCookie")
public class MaxAgeCookie extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.创建cookie对象
Cookie cookie = new Cookie("product", "xxx");
// 2.设置cookie存活时间
cookie.setMaxAge(30);// 存活30秒,到期自动销毁
//3. response响应cookie
response.addCookie(cookie);
}
}
Cookie的删除
# 目标:删除Cookie
1. 用户在浏览器中手动删除cookie(清除浏览记录): 用户未必知道或者配合
2. 从服务端远程操控删除cookie(服务端)
# 远程删除实现步骤:
0. 核心思想: 服务端发送一个同 path+name的cookie,cookie的存活时间为0
原理: 新cookie先覆盖浏览器保存的cookie,但是因为时间0,新cookie马上死掉
1. 创建与要删除的cookie同name的cookie,将其值设置成什么都无所谓;
2. 设置这个cookie的路径(与原cookie的路径一致);
3. 将这个cookie的最大存活时间设置成0;
4. 将这个新的cookie响应给浏览器,置换原来的cookie,新cookie也马上销毁;
@WebServlet("/DeleteServlet")
public class DeleteServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 创建同name+path的cookie,value写啥都无所谓
Cookie cookie = new Cookie("name", "xxx");
cookie.setPath("/xxx");//默认也是项目虚拟路径,可以不写
//2. 设置存活时间为0
cookie.setMaxAge(0);
//3. 发送此cookie到浏览器
response.addCookie(cookie);
}
}
Cookie特点
1. cookie存储数据在客户端(浏览器)
2. cookie的存储数据(name和value)只能是字符串
3. cookie单个大小不能超过4KB
4. 同一个域名下cookie数量不能超过50个
5. cookie的path和name决定了它的唯一性
6. cookie存储的数据不太安全
信息保存在用户的电脑上,都相对不安全
示例:用户上次访问记录
需求
访问一个Servlet,如果是第一次访问,则提示:您好,欢迎您的到来。
如果不是第一次访问,则提示:欢迎回来,您上次访问时间为: xxxx。
需求分析
1. 需求分析
a. 如果用户是第一次访问某个网站, 提示:您好,欢迎您的到来。
b. 如果用户是再次访问这个网站, 提示:欢迎回来,您上次访问时间为: xxxx。
2. 实现
1. 用户每次访问,服务端都会返回一个cookie("last_time",现在时间)
2. 用户再次访问,携带cookie过来,服务端就知道用户上次访问时间
3. 如果用户访问,没有携带此cookie,说明之前没有访问过
a. 获取浏览器发送过来的cookie
b. 判断用户是否存在这样的一个cookie,name=last_time
c. 如果用户没有这个cookie, 就说明是第一次访问
d. 如果用户有这个cookie,说明是再次访问, 可以获取cookie保留的上次访问时间
e. 获取当前时间,存到cookie(last_time, 当前时间), 存活时间100年,响应回去
代码实现
LastTimeServlet
@WebServlet("/LastTimeServlet")
public class LastTimeServlet 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 {
response.setContentType("text/html;charset=utf-8");//响应中文乱码
// a. 获取浏览器发送过来的cookie
Cookie[] cookies = request.getCookies();
// b. 判断用户是否存在这样的一个cookie,name=last_time
Cookie cookie = CookieUtils.getCookie(cookies, "last_time");
if(cookie == null){
// c. 如果用户没有这个cookie, 就说明是第一次访问
response.getWriter().print("您好,欢迎您的到来");
}else{
// d. 如果用户有这个cookie,说明是再次访问, 可以获取cookie保留的上次访问时间
String value = cookie.getValue();
response.getWriter().print("欢迎回来,您上次访问时间为:" + value);
}
//e. 获取当前时间,存到cookie(last_time, 当前时间), 存活时间100年,响应回去
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
String time = sdf.format(date);
//要注意变量名!!!
Cookie newCookie = new Cookie("last_time", time);
newCookie.setMaxAge(60*60*24*365*30);//30年 int类型 42亿
response.addCookie(newCookie);
}
}
CookieUtils
public class CookieUtils {
/**
* 获取指定name的单个cookie
* @param cookies 浏览器发送过来的所有cookie
* @param name 指定cookie的名字
*/
public static Cookie getCookie(Cookie[] cookies,String name){
// 防止空指针异常, 用户如果第一次访问,没有cookie
if(cookies != null && cookies.length > 0){
for (Cookie cookie : cookies) {
// 每一个cookie的name
String name2 = cookie.getName();
//判断是否有一个cookie的name与指定name一致
if(name.equalsIgnoreCase(name2)){
//如果有返回当前cookie
return cookie;
}
}
}
//其余皆为没有指定name的cookie情况
return null;
}
}
示例:商品浏览记录
需求
做一个商品页面,当访问后,在页面上点击查看商品浏览记录后,可以查看到以前浏览过的商品信息
代码实现
goods.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/xxx/GoodsInfoServlet?goods=xiaomi">小米手机</a> <br>
<a href="/xxx/GoodsInfoServlet?goods=huawei">华为手机</a> <br>
<a href="/xxx/GoodsInfoServlet?goods=chuizi">vivo手机</a> <br>
</body>
</html>
GoodsInfoServlet
@WebServlet("/GoodsInfoServlet")
public class GoodsInfoServlet 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 {
response.setContentType("text/html;charset=utf-8");//避免中文乱码
//1. 获取请求参数(用户当前正在查看的商品)
String goods = request.getParameter("goods");
//2. 获取用户发送的cookie,里面是否包含name=goods_history的cookie
Cookie[] cookies = request.getCookies();
Cookie historyCookie = CookieUtils.getCookie(cookies, "goods_history");
if(historyCookie == null){
//3. 没有这个cookie(说明用户之前没有浏览记录)
//3.1 创建一个name=goods_history的cookie,将当前浏览数据存进去
//3.2 并设置cookie的存活时间,并响应
Cookie cookie = new Cookie("goods_history", goods);
cookie.setMaxAge(60*60*24*30);//一个月
response.addCookie(cookie);
}else{
//4. 有这个cookie(说明用户之前有浏览记录: xiaomi)
//4.1 获取此cookie,将当前浏览数据(huawei)追加到value里面(xiaomi_huawei)
//补丁: 判断当前商品在cookie的value中是否存在
//4.2 并设置cookie的存活时间,并响应
String value = historyCookie.getValue();
String[] array = value.split("_");//下划线
List<String> list = Arrays.asList(array);
if(!list.contains(goods)){
//不包含: 追加
value = value + "_" + goods; // 下划线cookie中可用
historyCookie.setValue(value); // 重置value
historyCookie.setMaxAge(60*60*24*30);//一个月
response.addCookie(historyCookie); // 会覆盖已有cookie
}else{
//包含: 不追加
}
}
//5. 响应:
response.getWriter().print("<div>你正在浏览"+goods+"手机</div>");
response.getWriter().print("<a href='/xxx/goods.html'>继续浏览</a><br>");
response.getWriter().print("<a href='/xxx/GoodsHistoryServlet'>查看浏览记录</a>");
}
}
GoodsHistoryServlet
@WebServlet("/GoodsHistoryServlet")
public class GoodsHistoryServlet 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 {
response.setContentType("text/html;charset=utf-8");
//1. 获取用户发送的cookie,里面是否包含name=goods_history的cookie
Cookie[] cookies = request.getCookies();
Cookie historyCookie = CookieUtils.getCookie(cookies, "goods_history");
if(historyCookie == null){
//2. 如果没有,响应: 没有任何浏览记录
response.getWriter().print("没有任何浏览记录");
}else{
//3. 如果有,获取此cookie中的value(xiaomi_huawei), 按下划线切割字符串并遍历
String value = historyCookie.getValue();
String[] array = value.split("_");
response.getWriter().print("你浏览过以下商品: <br>");
for (int i = 0; i < array.length; i++) {
String element = array[i]; // element是每条浏览记录
response.getWriter().print("商品" + i + ":" + element + "<br>");
}
}
}
}
Session
概述
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上来说, 小域对象可以获取大域对象
工作原理
Session基于Cookie技术实现
方法介绍
1. 获取session对象:
HttpSession session = request.getSession()
1). 通过请求对象创建一个会话对象,如果当前用户会话不存在,创建会话。
2). 如果会话已经存在,这个方法返回已经存在的会话对象。
2. 获取session的id
String sessionId = session.getId();
3. 使当前session失效
ression.invalidate();
@WebServlet("/SetServlet")
public class SetServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取session对象
HttpSession session = request.getSession();
//2. 获取session的id(一个session对象有一个唯一的id)
String id = session.getId();
//3. 域对象存储数据
session.setAttribute("xxx","xxx");
System.out.println("SetServlet id:" + id);
//4. tomcat自动会把id通过cookie响应给浏览器
}
}
@WebServlet("/GetServlet")
public class GetServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取session对象(但是如果浏览器给了id,获取的就是id对应的session)
HttpSession session = request.getSession();
//2. 域对象获取数据
Object xxx = session.getAttribute("xxx");
String id = session.getId();
System.out.println(id + ":" + xxx);
}
}
Session细节
# 找不到当前会话中的session原因分析 (通俗版)
1). session的id存在cookie中,而cookie如果没有设置存活时间,默认会话级别: 用户把浏览器关闭就会销毁
2). 用户清除浏览器的记录(这里面包含了cookie)
2. 1). 服务器非正常关闭(断电)
正常关闭服务器: 钝化和活化用来保障病不丢
2). 手动删除: session.invalidate() 让session销毁
3). 默认30分该用户不访问,服务器会自动销毁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,下次启动时活化
session的持久化
#浏览器关闭后,session的持久化方案
1. 问题: 从以上的分析得知, 浏览器关闭之后,就找不到原来的session了
2. 原因:
1. 浏览器关闭,服务器中的session是在的
2. 但是前端的JESSIONID这个cookie消失了
3. 浏览器提交请求没有这个id,服务器自然就找不到之前的session了
3. 解决: 浏览器关闭,session依然找到的
1. 在Servlet中手动创建JESSIONID;
2. 手动设置name为JESSIONID的cookie存活时间;
3. 将JESSIONID响应给浏览器;
@WebServlet("/SetServlet")
public class SetServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取session对象
HttpSession session = request.getSession();
//2. 获取session的id(一个session对象有一个唯一的id)
String id = session.getId();
//3. 域对象存储数据
session.setAttribute("xxx","xxx");
session.setAttribute("xxx","xxx");
System.out.println("SetServlet id:" + id);
//4. tomcat自动会把id通过cookie响应给浏览器
/*
5. session持久化: 浏览器关闭,cookie依然在
*/
Cookie cookie = new Cookie("JSESSIONID", id);
cookie.setMaxAge(60*30);//30分钟
response.addCookie(cookie);
}
}
session的钝化和活化
# 之前提到, 当服务器正常关闭,重启后,再获取session跟之前的一样
这是因为tomcat已实现以下二个功能
1. 钝化(序列化: ObjectOutputStream)
当服务器正常关闭时,session中的数据,会序列化到硬盘 (持久化)
序列化的目的: 将内存中对象或数据结构 保存 到硬盘 (编码: 看得懂 -> 看不懂)
内存: 临时性存储设备, 断电了数据就消失
硬盘: 持久性存储设备, 断电了数据依然在
2. 活化(反序列化 : ObjectInputStream)
当服务器开启后,从磁盘文件中,反序列化到内存中
反序列化的目的: 将硬盘上的数据读取到内存,形成对象或数据结构 (解码: 看不懂 -> 看得懂)
备注: 钝化和活化的本质是序列化技术, 所以保存的存储数据类型需要实现serializable接口
我们使用的idea工具有坑:
1. 我们正常关闭tomcat,tomcat确实将session钝化到磁盘(下图的位置中的sessions.ser)
2. 坑: 但是在idea重启tomcat时,会默认删除之前保存的sessions.ser文件,造成tomcat没有活化数据
3. 解决: 设置idea重启时,不清除session会话(下图)
支持钝化
我们可以设置idea重启时,不清除session数据
URL重写
# URL重写是为了解决cookie禁用问题
1. 问题: 浏览器是默认启用cookie,但是用户也可以禁用浏览器的Cookie(浏览器自带功能: 不允许浏览器保存cookie), 由于Session基于Cookie技术实现,所以一旦禁用了之后,Session功能就会出现问题
2. 解决: url重写技术
response.encodeURL(path)
会在url,拼接JSESSIONID
3. 备注: 开发中,一般是不关注禁用cookie的用户,若用户禁用了cookie,会给很多功能的实现带来很大的麻烦
@WebServlet("/UrlServlet")
public class UrlServlet 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 {
HttpSession session = request.getSession();
//存数据
session.setAttribute("xxx","xx");
session.setAttribute("xxx","xxx");
/*
问题:
1. tomcat会自动将session的id通过cookie发送给浏览器
2. 现在用户的浏览器禁用了cookie:通过cookie传输session的id这套机制就失效了
流程:
在请求的url上手动添加JSESSIONID这个参数
a. 本来 url = /xxx/GetServlet
b. 添加之后 url = /xxx/GetServlet;jsessionid=xx
xx 这里是session的id
原理: url重写
1. 之前JSESSIONID是通过cookie传输的, 现在JSESSIONID是通过url传输的
2. JSESSIONID=? 是 url中的参数部分
url格式 协议://ip:port/资源位置?参数
但是注意, 这里重写之后的url中的参数是用分号隔开(这也是参数分隔符),相当于之前的?
3. 这个JSESSIONID参数tomcat会获取,并根据这个id获取到对应的session对象
url重写方法:
String url = response.encodeURL(oldUrl);
*/
String oldUrl = "/xxx/GetServlet";
String url = response.encodeURL(oldUrl);
System.out.println(url);
response.setContentType("text/html;charset=utf-8");
//先写一个全字符串,删除变量位置,两个双引,两个加号,中间写变量
response.getWriter().print("<a href='"+ url + "'>取出UrlServlet存的数据</a> <br>");
}
}
Session特点
# session是服务器端的会话技术
作用: 在一次会话的多次请求之间共享数据
1. session存储数据在服务器
2. session存储任意类型的数据(Object)
3. session存储大小和数量没有限制(在服务器内存)
4. session存储相对安全
cookie和session的对比
cookie和session的选择
1. cookie将数据保存在浏览器端,数据相对不安全.建议敏感的或大量的数据不要放在cookie中,而且数据大小是有限制的
成本低,对服务器要求不高
2. session将数据保存在服务器端内存,数据相对安全.数据的大小要比cookie中数据灵活很多
成本较高,对服务器压力较大
三大域对象总结
request、session、ServletContext
域对象方法
# 域对象方法都一致
1. 设置数据
void setAttribute(String name, Object o)
2. 获取数据
Object getAttribute(String name)
3. 删除数据
void removeAttribute(String name)
# 小域对象可以大域对象
生命周期
ServletContext域对象
* 何时创建
服务器正常启动,项目加载时,创建
* 何时销毁
服务器关闭或项目卸载时,销毁
* 作用范围
整个web项目(共享数据)
HttpSession域对象
* 何时创建
用户第一次调用request.getSession()方法时,创建【不太标准..】
用户访问携带的jsessionid与服务器里的session不匹配时,就会创建的
* 何时销毁
1. 服务器非正常关闭
2. 未活跃状态30分钟
3. 手动销毁
* 作用范围
一次会话中,多次请求间(共享数据)
# 会话的定义: 双方建立连接,这次连接期间的多次请求响应,直到一方断开连接为止
(B/S) 从浏览器第一次访问这个服务器,期间多次请求响应,直到浏览器关闭为止 -> 狭义的一次会话
cookie和session默认都是会话级别,都可以设置持久级别
HttpServletRequest域对象
* 何时创建
服务器接收到请求时,创建
* 何时销毁
服务器做出响应后,销毁
* 作用范围
一次请求中,多次请求转发间(共享数据)
总结
-
能用小的不用大的:request(一次请求)<session(一次会话)<servletContext(应用全局)
因为生命周期长的域对象销毁时间比较晚,占用服务器内存时间太长
-
常用的场景:
-
request:一次请求中(请求转发共享)
-
session:存放当前会话的私有数据
- 用户登录状态
- 验证码
- 购物车
-
servletContext:若需要所有的servlet都能访问到,才使用这个域对象.
-
示例:商品购物车
需求
有一个商品页面,可以点击超链接将商品添加到购物车,还有一个超链接,点击它的时候可以查看购物车中商品信息
需求分析
代码实现
buycar.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>购物页面</h1>
<a href="/xxx/AddServlet?product=xiaomi">添加小米手机到购物</a> <br>
<a href="/xxx/AddServlet?product=huawei">华为手机</a> <br>
<a href="/xxx/AddServlet?product=chuizi">vivo手机</a> <br>
</body>
</html>
AddServlet
@WebServlet("/AddServlet")
public class AddServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取请求参数
String product = request.getParameter("product");
//2. 从session中获取购物车数据,拿到map
Map<String,Integer> map = (Map<String, Integer>) request.getSession().getAttribute("car");
if(map == null){
//3. 如果map为null,表示用户之前没有添加任何商品
map = new HashMap<>();
map.put(product,1);
}else{
//4. 如果map不为null,表示用户之前添加过商品
//4.1判断是否有此商品
if(map.containsKey(product)){//如果map有这个key,返回true
//之前添加过此商品, 已有数量+1
Integer count = map.get(product);
count++;
map.put(product,count);
}else{
//没有添加过
map.put(product,1);
}
}
//5. 将map重新存入到session中
request.getSession().setAttribute("car",map);
//6. 响应:两个超链接
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("<a href='/xxx/buycar.html'>继续购买</a><br>");
response.getWriter().print("<a href='/xxx/CarServlet'>查看购物车</a><br>");
}
}
CarServlet
@WebServlet("/CarServlet")
public class CarServlet 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 {
response.setContentType("text/html;charset=utf-8");
//1.从session中获取购物车数据,拿到map
Map<String,Integer> map = (Map<String, Integer>) request.getSession().getAttribute("car");
//2. 接下来判断null
if(map == null){
response.getWriter().print("购物车没有任何商品");
}else{
response.getWriter().print("当前购物车中有<br>");
Set<String> keySet = map.keySet();
for (String key : keySet) {
Integer value = map.get(key);
response.getWriter().print(key + ":" + value + "<br>");
}
}
}
}
示例:用户登录(验证码)
需求
用户访问带有验证码的登录页面,输入用户名,密码以及验证码实现登录功能。
需求分析
代码实现
导入验证码Servlet
login.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
post请求方式参数大小无限制,相对安全
1. 上传文件
2. 提交表单
-> 问题: 请求参数中文乱码
-->
<h1>登录页面</h1>
<form action="/xxx/loginServlet" method="post">
用户<input type="text" name="username"> <br>
密码<input type="password" name="password"> <br> <br>
验证码 <input type="text" name="checkcode">
<img src="/xxx/CheckcodeServlet" id="myimg"><br> <br>
<input type="submit" value="登录">
</form>
<script >
document.getElementById("myimg").onclick = function () {
//获取系统当前时间毫秒值
let time = new Date().getTime()
//通过一个无意义的参数,让浏览器更新
this.src = "/xxx/CheckcodeServlet?time=" + time
}
</script>
</body>
</html>
LoginServlet
@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 {
//post请求中文乱码
request.setCharacterEncoding("utf-8");
//a. 获取服务器中保存的验证码(session)
String code_session = (String) request.getSession().getAttribute("code_session");
//b. 获取用户输入的验证码
String checkcode = request.getParameter("checkcode");
if(!code_session.equalsIgnoreCase(checkcode)){
//验证码错误:
request.getRequestDispatcher("/CheckFailedServlet").forward(request,response);
return; //不再往下执行了
}
//1. 获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
//2. 业务处理(判断用户名和密码是否存在)
//要登录,必先注册,注册的数据一般放在数据库中
// 伪数据, 只有一个用户已注册: 用户名jack, 密码123
if("jack".equalsIgnoreCase(username) && "123".equalsIgnoreCase(password)){
//a. 账号密码正确 -> 请求转发SuccessServlet
User user = new User();
user.setUsername(username);
user.setPassword(password);
request.setAttribute("user",user);//域对象设置数据
request.getRequestDispatcher("/successServlet").forward(request,response);
}else{
//b. 账号密码不正确 -> 请求转发FaliedServlet
request.getRequestDispatcher("/failedServlet").forward(request,response);
}
}
}
successServlet
@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 {
System.out.println("登录成功");
//注意, 如果没有设置,获取是null
User user = (User) request.getAttribute("user");//域对象获取数据
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("恭喜"+user.getUsername()+",登录成功");
}
}
CheckFailedServlet
@WebServlet("/CheckFailedServlet")
public class CheckFailedServlet 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 {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("验证码错误");
}
}
CheckFailedServlet
@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 {
System.out.println("登录失败");
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("用户名不存在或密码错误");
}
}