web(Cookie、Session、三大域对象)

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("用户名不存在或密码错误");
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值