JavaWeb-Servlet+Filter+Listener+Session

JavaWeb

在这里插入图片描述


一、Servlet

  • Servlet 是 JavaEE 规范之一。规范就是接口。
  • Servlet 是 JavaWeb三大组件之一。三大组件分别是:Servlet程序、Filter过滤器、Listener监听器。
  • Servlet 是 运行在服务器上的一个 java程序,它可以接收客户端发送过来的请求,并响应数据给客户端。

1.1、Servlet 概述


1.1.1、Servlet类的继承体系:

在这里插入图片描述

一般在实际项目开发中,都是使用继承 HttpServlet 类的方式去实现Servlet程序

  1. 编写一个类去继承 HttpServlet 类
  2. 根据业务需要重写 doGet 或 doPost 等分发处理方法
  3. 到web.xml 中配置 自定义Servlet 程序的访问地址
1.1.2、Servlet接口 五种方法介绍 以及生命周期
  1. 执行 Servlet 构造器方法
  2. 执行 init 初始化方法. (第一、二步,是在第一次访问的时候创建Servlet程序会调用)
  3. 执行 service 方法 (第三步每次访问都会调用)
  4. 执行 destroy() 销毁方法(第四步、在web工程停止的时候调用)

在这里插入图片描述

1.1.3、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">
    <!--servlet 标签给 Tomcat 配置 Servlet程序-->
    <servlet>
        <!--servlet-name 标签 Servlet程序起一个别名(一般是类名)-->
        <servlet-name>HelloServlet</servlet-name>
        <!--servlet-class 是 Servlet 程序的全类名-->
        <servlet-class>com.hgw.javaweb.HelloServlet</servlet-class>
        <!--init-param 是初始化参数-->
        <init-param>
            <!--参数名-->
            <param-name>username</param-name>
            <!--参数值 -->
            <param-value>hgw</param-value>
        </init-param>
    </servlet>
    
    <!--servlet-mapping 标签给 Servlet 程序配置访问地址-->
    <servlet-mapping>
        <!--servlet-name 标签的作用是告诉服务器,我们当前配置的地址给哪个 Servlet 程序使用-->
        <servlet-name>HelloServlet</servlet-name>
        <!--url-pattern 标签访问地址
            / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径
            /hello 表示地址为:http://ip:port/工程路径/hello
        -->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
      <!--context-param 是上下文参数(它属于整个web工程)-->
     <context-param>
         <param-name>username</param-name>
         <param-value>context</param-value>
     </context-param>
</web-app>

1.2、ServletConfig 接口

在这里插入图片描述

1.3、ServletContext 接口

在这里插入图片描述

1.4、HttpServletRequest 接口

在这里插入图片描述

1.5、HttpServletResponse 接口

在这里插入图片描述


二、Filter

2.1、简介


Filter也称之为过滤器,它是Servlet技术中最实用的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

它主要用于对用户请求进行预处理,也可以对HttpServletResponse 进行后处理。使用Filter 的完整流程:Filter 对用户请求进行预处理,接着将请求交给Servlet 进行处理并生成响应,最后Filter 再对服务器响应进行后处理。

Filter功能:

  • 在HttpServletRequest 到达 Servlet 之前,拦截客户的 HttpServletRequest 。 根据需要检查 HttpServletRequest ,也可以修改HttpServletRequest 头和数据。
  • 在HttpServletResponse 到达客户端之前,拦截HttpServletResponse 。 根据需要检查 HttpServletResponse ,也可以修改HttpServletResponse头和数据。

大概流程图如下

Filter

2.2、如何实现拦截


​ Filter 接口中有一个 doFilter 方法,当开发人员编写好Filter,并配置对哪个Web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下的目的:

  1. 调用目标资源之前,让一段代码执行
  2. 是否调用目标资源(即是否让用户访问Web资源)

​ web服务器在调用doFilter方法时,会传递一个 filterChain 对象进来,filterChain对象是 filter 接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需要决定死否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。

2.3、Filter 开发两步走


  1. 编写java类实现Filter接口,并实现其doFilter方法。
  2. 在 web.xml 文件中使用 <filter><filter-mapping> 元素对编写的filter类进行注册,并设置它所能拦截的资源。

web.xml配置各节点介绍:

<!--filter标签用于配置一个Filter过滤器-->
<filter>
    <!--用于为过滤器指定一个名字,该元素的内容不能为空。 -->
    <filter-name>AdminFilter</filter-name>
    <!--元素用于指定过滤器的完整的限定类名。 -->
    <filter-class>com.atguigu.filter.AdminFilter</filter-class>
  
		<!--元素用于为过滤器指定初始化参数,它的子元素 -->
    <init-param>
      <!--指定参数的名字-->
        <param-name>username</param-name>
      <!--指定参数的值-->
        <param-value>root</param-value>
    </init-param>
</filter>

<!--元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径 -->
<filter-mapping>
    <!--子元素用于设置filter的注册名称。该值必须是在<filter>元素中声明过的过滤器的名字 -->
    <filter-name>AdminFilter</filter-name>
    <!--设置 filter 所拦截的请求路径(过滤器关联的URL样式) 
        / 表示请求地址为:http://ip:port/工程路径/ 映射到Idea 的 web目录
        /admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
    -->
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

2.4、Filter链


​ 在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。

web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给改方法。在doFilter方法中,开发人员如果调用了 FilterChain 对象的 doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

2.5、Filter的生命周期


public void init(FilterConfig filterConfig) throws ServletException;	// 初始化

和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作**(filter对象只会创建一次,init方法也只会执行一次)**。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, javax.servlet.FilterChain filterChain) throws IOException, ServletException

​ 这个方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain 参数用于访问后续过滤器。

public void destroy();	// 销毁

​  Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁。在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。

2.6、FilterConfig接口


​ 用户在配置filter时,可以使用为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得以下内容:

String getFilterName();//得到filter的名称。 
String getInitParameter(String name);//返回在部署描述中指定名称的初始化参数的值。如果不存在返回null. 
Enumeration getInitParameterNames();//返回过滤器的所有初始化参数的名字的枚举集合。 
public ServletContext getServletContext();//返回Servlet上下文对象的引用。

2.7、Filter使用案例


2.7.1、使用Filter验证用户是否登录
package com.atguigu.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;

public class AdminFilter implements Filter {

    public AdminFilter() {
        System.out.println("1 Filter构造方法AdminFilter()");
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("2 Filter的init()初始化");

        System.out.println("filter-name的值是: " + filterConfig.getFilterName());
        System.out.println("初始化参数username的值是: " + filterConfig.getInitParameter("username"));
        System.out.println("初始化参数url的值是: " + filterConfig.getInitParameter("url"));

        System.out.println(filterConfig.getServletContext());
    }

    /**
     * doFilter方法,专门用于拦截请求。可以做权限检查
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, javax.servlet.FilterChain filterChain) throws IOException, ServletException {
        System.out.println("3 Filter的doFilter()方法");
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpSession session = httpServletRequest.getSession();

        Object user = session.getAttribute("user");
        // 如果等于null,说明还没有登录
        if (user == null) {
            servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
            return;
        }else {
            // 让程序继续往下访问用户的目标资源
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }

    @Override
    public void destroy() {
        System.out.println("4 Filter的destroy()销毁方法");
    }
}
<?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">

    <!--filter标签用于配置一个Filter过滤器-->
    <filter>
        <!--给filter起一个别名-->
        <filter-name>AdminFilter</filter-name>
        <!--配置filter的全类名-->
        <filter-class>com.atguigu.filter.AdminFilter</filter-class>

        <init-param>
            <param-name>username</param-name>
            <param-value>root</param-value>
        </init-param>
        <init-param>
            <param-name>url</param-name>
            <param-value>jdbc:mysql://localhost3306/test</param-value>
        </init-param>
    </filter>
    <!--filter-mapping配置Filter过滤器的拦截路径-->
    <filter-mapping>
        <!--filter-name表示当前的拦截路径给哪个filters使用-->
        <filter-name>AdminFilter</filter-name>
        <!--url-pattern配置拦截路径
            / 表示请求地址为:http://ip:port/工程路径/ 映射到Idea 的 web目录
            /admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
        -->
        <url-pattern>/admin/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>LoginServlet</servlet-name>
        <servlet-class>com.atguigu.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>LoginServlet</servlet-name>
        <url-pattern>/loginServlet</url-pattern>
    </servlet-mapping>
</web-app>

三、Listener

3.1、Listener 的定义与作用

监听器Listener 就是在 application、session、request 三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。

Listener 是 Servlet 的监听器,可以监听客户端的请求,服务端的操作等。

3.2、Listener 的分类与使用

主要有以下三类:

3.2.1、ServletContext 监听
  • ServletContextListener:用于对Servlet整个上下文进行监听(创建、销毁)。
public void contextInitialized(ServletContextEvent sce);//上下文初始化
public void contextDestroyed(ServletContextEvent sce);//上下文销毁

public ServletContext getServletContext();//ServletContextEvent事件:取得一个ServletContext(application)对象
  • ServletContextAttributeListener:对Servlet上下文属性的监听(增删改属性)。
public void attributeAdded(ServletContextAttributeEvent scab);//增加属性
public void attributeRemoved(ServletContextAttributeEvent scab);//属性删除
public void attributeRepalced(ServletContextAttributeEvent scab);//属性替换(第二次设置同一属性)

//ServletContextAttributeEvent事件:能取得设置属性的名称与内容
public String getName();//得到属性名称
public Object getValue();//取得属性的值
3.2.2、Session监听

Session属于http协议下的内容,接口位于javax.servlet.http.*包下。

  • **HttpSessionListener**接口:对Session的整体状态的监听。
public void sessionCreated(HttpSessionEvent se);//session创建
public void sessionDestroyed(HttpSessionEvent se);//session销毁

//HttpSessionEvent事件:
public HttpSession getSession();//取得当前操作的session
  • **HttpSessionAttributeListener**接口:对session的属性监听。
public void attributeAdded(HttpSessionBindingEvent se);//增加属性
public void attributeRemoved(HttpSessionBindingEvent se);//删除属性
public void attributeReplaced(HttpSessionBindingEvent se);//替换属性

//HttpSessionBindingEvent事件:
public String getName();//取得属性的名称
public Object getValue();//取得属性的值
public HttpSession getSession();//取得当前的session

session的销毁有两种情况:

  1. session超时,web.xml配置:
<session-config>
    <session-timeout>120</session-timeout><!--session120分钟后超时销毁-->
</session-config>
  1. 手工使session失效
public void invalidate();//使session失效方法。session.invalidate();
3.2.3、Request监听
  • ServletRequestListener:用于对Request请求进行监听(创建、销毁)。
public void requestInitialized(ServletRequestEvent sre);//request初始化
public void requestDestroyed(ServletRequestEvent sre);//request销毁

//ServletRequestEvent事件:
public ServletRequest getServletRequest();//取得一个ServletRequest对象
public ServletContext getServletContext();//取得一个ServletContext(application)对象
  • ServletRequestAttributeListener:对Request属性的监听(增删改属性)。
public void attributeAdded(ServletRequestAttributeEvent srae);//增加属性
public void attributeRemoved(ServletRequestAttributeEvent srae);//属性删除
public void attributeReplaced(ServletRequestAttributeEvent srae);//属性替换(第二次设置同一属性)

//ServletRequestAttributeEvent事件:能取得设置属性的名称与内容
public String getName();//得到属性名称
public Object getValue();//取得属性的值
3.2.4、在web.xml中配置

Listener配置信息必须在Filter和Servlet配置之前,Listener的初始化(ServletContentListener初始化)比Servlet和Filter都优先,而销毁比Servlet和Filter都慢。

<listener>
    <listener-class>com.listener.class</listener-class>
</listener>

3.3、Listener 应用实例


1、利用HttpSessionListener统计最多在线用户人数

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class HttpSessionListenerImpl implements HttpSessionListener {

    public void sessionCreated(HttpSessionEvent event) {
        ServletContext app = event.getSession().getServletContext();
        int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
        count++;
        app.setAttribute("onLineCount", count);
        int maxOnLineCount = Integer.parseInt(app.getAttribute("maxOnLineCount").toString());
        if (count > maxOnLineCount) {
            //记录最多人数是多少
            app.setAttribute("maxOnLineCount", count);
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //记录在那个时刻达到上限
            app.setAttribute("date", df.format(new Date()));
        }
    }
    //session注销、超时时候调用,停止tomcat不会调用
    public void sessionDestroyed(HttpSessionEvent event) {
        ServletContext app = event.getSession().getServletContext();
        int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
        count--;
        app.setAttribute("onLineCount", count);    
        
    }
}

四、会话

4.1、HTTP协议与状态保持


​ HTTP协议本身是无状态的,这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。

​ 然而聪明(或者贪心?)的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用,就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为,另一方面在服务器端则出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议也添加了文件上载、cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。

​ Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份Session通过在服务器端记录信息确定用户身份

一次会话指的是:就好比打电话,A给B打电话,接通之后,会话开始,直到挂断电话,该次会话就结束了,而浏览器访问服务器,就跟打电话一样,浏览器A给服务器发送请求,访问web程序,该次会话就已经接通,其中不管浏览器发送多少请求(就相当于接通电话后说话一样),都视为一次会话,直到浏览器关闭,本次会话结束。其中注意,一个浏览器就相当于一部电话,如果使用火狐浏览器,访问服务器,就是一次会话了,然后打开google浏览器,访问服务器,这是另一个会话,虽然是在同一台电脑,同一个用户在访问,但是,这是两次不同的会话。

知道了什么是会话后,思考一个问题,一个浏览器访问一个服务器就能建立一个会话,如果别的电脑,都同时访问该服务器,就会创建很多会话,就拿一些购物网站来说,我们访问一个购物网站的服务器,会话就被创建了,然后就点击浏览商品,对感兴趣的商品就先加入购物车,等待一起付账,这看起来是很普通的操作,但是想一下,如果有很多别的电脑上的浏览器同时也在访问该购物网站的服务器,跟我们做类似的操作呢?服务器又是怎么记住用户,怎么知道用户A购买的任何商品都应该放在A的购物车内,不论是用户A什么时间购买的,不能放入用户B或用户C的购物车内的呢?所以就有了cookie和session这两个技术,就像第一行说的那样,cookie和session用来跟踪用户的整个会话,

Cookie和Session之间的区别和联系

假如一个咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:

1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。但是http协议本身是无状态的

2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。也就是cookie。 顾客就相当于浏览器,cookie如何工作,下面会详细讲解

3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。

由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择

4.2、Cookie


上面已经介绍了为什么要使用Cookie,以及Cookie的一些特点,比如保存在**客户端**,用来记录用户身份信息的,现在来看看如何使用Cookie。

借着上面会员卡的例子来说,采用的是第二种方案,其中还需要解决的问题就是:如何分发会员卡,会员卡的内容,客户如何使用会员卡,会员卡的有效日期,会员卡的使用范围

4.2.1、Cookie是如何创建的?创建后如何发送给客户端进行保存?

由服务器进行创建,也就相当于咖啡店来创建会员卡,在创建会员卡的同时,就会将会员卡中的内容也给设置了

Cookie cookie = new Cookie(key,value);	//以键值对的方式存放内容
response.addCookie(cookie);		// 发送回浏览器端,让浏览器保存
// 注意:一旦cookie创建好了,就不能在往其中增加别的键值对,但是可以修改其中的内容
cookie.setValue();		// 将key对应的value值修改
4.2.2、Cookie在客户端是如何工作的,工作原理是什么?

cookie工作机制

这个过程就相当于,咖啡店创建好了会员卡,并且设置了其中的内容,交到了客户手中,客户下次过来时,就带着会员卡过来,就知道你是会员了,然后咖啡店就拿到你的会员卡对其进行操作

4.2.3、Cookie的范围?

会员卡的使用范围?比如星巴克在北京有一个分店,在上海也有一个分店,我们只是在北京的星巴克办理了会员卡,那么当我们到上海时,就不能使用该会员卡进行打折优惠了。而cookie也是如此,可以设置服务器端获取cookie的访问路径,而并非在服务器端的web项目中所有的servlet都能访问该cookie。

  • cookie默认路径:当前访问的servlet父路径。

    • 例如:http://localhost:8080/test01/a/b/c/SendCookieServlet
      • 默认路径:/test01/a/b/c
      • 也就是说,在该默认路径下的所有Servlet都能够获取到cookie,/test01/a/b/c/MyServlet 这个MyServlet就能获取到cookie。修改cookie的访问路径
  • setPath("/");  在该服务器下,任何项目,任何位置都能获取到cookie,

    • 用途:保证在tomcat下所有的web项目可以共享相同的cookie
    • 例如:tieba , wenku , beike 多个项目共享数据。例如用户名。
  •  setPath("/test01/"); 在test01项目下任何位置都能获取到cookie。

setPath("/路径");:指定cookie的范围

4.2.4、总结Cookie

  • cookie的内容

    • cookie的内容主要包括:名字,值,过期时间,路径和域。

      • 域可以指定某一个域比如.google.com,相当于总店招牌,比如宝洁公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com,可以用飘柔来做比。

      • 路径就是跟在域名后面的URL路径,比如/或者/foo等等,可以用某飘柔专柜做比。路径与域合在一起就构成了cookie的作用范围。

      • 过期时间

        • 如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。

        • 如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。

  • 工作流程:

    1. servlet创建cookie,保存少量数据,发送浏览器。
    2. 浏览器获得服务器发送的cookie数据,将自动的保存到浏览器端。
    3. 下次访问时,浏览器将自动携带cookie数据发送给服务器。
  • cookie操作

    1. 创建cookie:new Cookie(name,value)
    2. 发送cookie到浏览器:HttpServletResponse.addCookie(Cookie)
    3. servlet接收cookie:HttpServletRequest.getCookies() 浏览器发送的所有cookie
  • cookie特点

  1. 每一个cookie文件大小:4kb , 如果超过4kb浏览器不识别
  2. 一个web站点(web项目):发送20个
  3. 一个浏览器保存总大小:300个
  4. cookie 不安全,可能泄露用户信息。浏览器支持禁用cookie操作。
  5. 默认情况生命周期:与浏览器会话一样,当浏览器关闭时cookie销毁的。—临时cookie
  • cookie api

    • getName() 获得名称,cookie中的key

    • getValue() 获得值,cookie中的value

    • setValue(java.lang.String newValue) 设置内容,用于修改key对应的value值。

    • setMaxAge(int expiry) 设置有效时间【】

    • setPath(java.lang.String uri) 设置路径【】,即可cookie的范围

    • setDomain(java.lang.String pattern)

      • 设置域名 , 一般无效,有浏览器自动设置,setDomain(“.gulimalll.cn”)

        • www.gulimalll.cnbbs.gulimalll.cn 都可以访问
        • a.b.gulimalll.cn 无法访问
      • 作用:设置cookie的作用范围,域名+路径在一起就构成了cookie的作用范围,上面单独设置的setPath有用,是因为有浏览器自动设置该域名属性,但是我们必须知道有这么个属性进行域名设置的

    • isHttpOnly() 是否只是http协议使用。只能servlet的通过getCookies()获得,javascript不能获得。

    • setComment(java.lang.String purpose) (了解)  //对该cookie进行描述的信息(说明作用),浏览器显示cookie信息时能看到

    • setSecure(boolean flag) (了解)  是否使用安全传输协议。为true时,只有当是https请求连接时cookie才会发送给服务器端,而http时不会,但是服务端还是可以发送给浏览端的。

    • setVersion(int v) (了解)  参数为0(传统Netscape cookie规范编译)或1(RFC 2109规范编译)。

注意:cookie不能发送中文,如果要发送中文,就需要进行特别处理。

  • JDK提供工具,进行编码

    • URLEncoder:编码
    • URLDecoder:解码
    //发送cookie
    Cookie cookie = new Cookie(URLEncoder.encode("疫情"),URLEncoder.encode("快走"));
    response.addCookie(cookie);
    
    //获得cookie中文内容
    URLDecoder.decoder(request.getCookie().getName);  //获取key
    URLDecoder.decoder(request.getCookie().getValue);  //获取value  
    
4.2.5、Cookie按钮
  • 记住用户名
    • 登录时,在服务器端获取到用户名,然后创建一个cookie,将用户名存入cookie中,发送回浏览器端,然后浏览器下次在访问登录页面时,先拿到cookie,将cookie中的信息拿出来,看是否保存了该用户名,如果保存了,那么直接用他,如果没有,则自己手写用户名。
  • 历史记录
    • 比如购物网站,都会有我们的浏览记录的,实现原理其实也是用cookie技术,每浏览一个商品,就将其存入cookie中,到需要显示浏览记录时,只需要将cookie拿出来遍历即可。

4.3、Session


同样,会员卡的例子的第三种方法,发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是服务器端保持状态。 这就是session的用法,在服务器端来保持状态,保存一些用户信息。

功能作用:服务器用于共享数据技术

Session

4.3.1、session原理分析
  1. 首先浏览器请求服务器访问web站点时,程序需要为客户端的请求创建一个session的时候,服务器首先会检查这个客户端请求是否已经包含了一个session标识、称为SESSIONID,
    • 如果已经包含了一个sessionid则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用,
    • 如果客户端请求不包含session id,则服务器为此客户端创建一个session并且生成一个与此session相关联的session id,sessionid 的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个sessionid将在本次响应中返回到客户端保存,保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是JSESSIONID。
4.3.2、获取session
request.getSession();  //如果没有将创建一个新的,等效getSession(true);
request.getSession(boolean);  //true:没有将创建,false:没有将返回null

有些人不理解,为什么是通过request来获取session,可以这样理解,在获取session时,需要检测请求中是否有session标识,所以需要用request来获取

4.3.3、session属性操作:
  • xxxAttribute(...)
    用来存放一些信息,然后才能共享信息
    • setAttrubute(key,value);
    • getAttribute(key);
4.3.4、session生命周期

常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session

恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,一般是30分钟,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间

  • 自己来控制session的有效时间

    • session.invalidate() 将session对象销毁

    • setMaxInactiveInterval(int interval) 设置有效时间,单位秒

    • 在web.xml中配置session的有效时间

      <session-config>
        	<session-timeout>30</session-timeout>   单位:分钟
      <session-config>
      

所以,讨论了这么就,session的生命周期就是:

  • 创建:第一次调用getSession()
  • 销毁:
    1. 超时,默认30分钟
    2. 执行api:session.invalidate()将session对象销毁、setMaxInactiveInterval(int interval) 设置有效时间,单位秒
    3. 服务器非正常关闭
      • 自杀,直接将JVM马上关闭
      • 如果正常关闭,session就会被持久化(写入到文件中,因为session默认的超时时间为30分钟,正常关闭后,就会将session持久化,等30分钟后,就会被删除),位置: D:\java\tomcat\apache-tomcat-7.0.53\work\Catalina\localhost\test01\SESSIONS.ser
4.3.5、session id 的URL重写

当浏览器将cookie禁用,基于cookie的session将不能正常工作,每次使用request.getSession() 都将创建一个新的session。达不到session共享数据的目的,但是我们知道原理,只需要将session id 传递给服务器session就可以正常工作的。

  • 解决:通过URL将session id 传递给服务器:URL重写

    • 手动方式: url;jsessionid=…

    • api方式:

      encodeURL(java.lang.String url) //进行所有URL重写
      encodeRedirectURL(java.lang.String url) //进行重定向 URL重写 
      

    这两个用法基本一致,只不过考虑特殊情况,要访问的链接可能会被Redirect到其他servlet去进行处理,这样你用上述方法带来的session的id信息不能被同时传送到其他servlet.这时候用encodeRedirectURL()方法就可以了

    • 如果浏览器禁用cooke,api将自动追加session id ,如果没有禁用,api将不进行任何修改。
    • 注意:如果浏览器禁用cookie,web项目的所有url都需进行重写。否则session将不能正常工作
4.3.6、总结

  • 什么是cookie,什么是session?

    • cookie是一种在客户端记录用户信息的技术,因为http协议是无状态的,为了解决这个问题而产生了cookie。记录用户名等一些应用
    • session是一种在服务端记录用户信息的技术,一般session用来在服务器端共享数据,
  • cookie的工作原理?session的工作原理?

    • cookie工作原理,cookie是由服务器端创建发送回浏览器端的,并且浏览器每次请求服务器都会将cookie带过去,以便服务器知道该用户是哪一个。其cookie中是使用键值对来存储信息的,并且一个cookie只能存储一个键值对。所以在获取cookie时,是会获取到所有的cookie,然后从cookies其中遍历。
    • session的工作原理就是依靠cookie来做支撑,第一次使用request.getSession()时session被创建,并且会为该session创建一个独一无二的sessionid存放到cookie中,然后发送会浏览器端,浏览器端每次请求时,都会带着这个sessionid,服务器就会认识该sessionid,知道了sessionid就找得到哪个session。以此来达到共享数据的目的。 这里需要注意的是,session不会随着浏览器的关闭而死亡,而是等待超时时间。
4.3.7、理解 javax.servlet.http.HttpSession

​ HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。

​ 首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域,cookie的生存时间等。

​ 一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用,Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。

​ 复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。

​ cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。

​ cookie的路径对于web应用程序来说是一个非常重要的选项,Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。

4.3.7.1、HttpSession常见问题

session在何时被创建

​ 一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <%@page session=“false”%> 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的session对象的来历。

由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。

session何时被删除

​ 综合前面的讨论,session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session)

如何做到在浏览器关闭时删除session

​ 严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。

有个HttpSessionListener是怎么回事

​ 你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有HttpSessionBindingListener,HttpSessionActivationListener和HttpSessionAttributeListener。

存放在session中的对象必须是可序列化的吗

​ 不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。

如何才能正确的应付客户端禁止cookie的可能性

​ 对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL

开两个浏览器窗口访问应用程序会使用同一个session还是不同的session

​ 对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。

  • 不同的浏览器则不是同一个
  • 一个浏览器打开不同的窗口都是同一个
如何防止用户打开两个浏览器窗口操作导致的session混乱

​ 这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。

为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue

​ 这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变,需要向其他服务器进程复制新的session值。

为什么session不见了

​ 排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的,虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过;浏览器插件的可能性次之,笔者也遇到过3721插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。

​ 出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。

在分布式微服务的情况下如何解决Session不共享不同步的问题
  • 问题

    • 分布式的情况下,Session不同步
    • 不同服务,Session不能共享问题
  • 解决:

    • 分布式的情况下,Session不同步

      • Session复制

      • 客户端存储

      • Hash 一致性

      • 统一存储【本项目使用】,将Session存储到缓存中

        • 在 gulimall 项目中我们通过SpringSession整合,将会员服务登录之后将Session存储进Redis。

          # 配置SpringSession
          spring.session.store-type=redis
          
    • 不同服务,Session不能共享问题

      • 第一次使用Sesssion的时候,分配cookie的时候扩大域(指定域名为父域名)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值