跳转至
内容字体
东观体
上图东观体
OPPO Sans
江城黑体
霞鹜臻楷
代码字体
DejaVu Sans Mono
DejaVu Sans Mono
Google Sans Code
JetBrains Mono
主题切换
返回顶部

会话、过滤器和监听器

约 5037 个字 635 行代码 21 张图片 预计阅读时间 25 分钟

会话

前面学习到的HTTP协议属于无状态协议,所谓无状态就是不保存状态,即无状态协议,HTTP协议自身不对请求和响应之间的通信状态进行保存,也就是说,在HTTP协议这个级别,协议对于发送过的请求或者响应都不做持久化处理。简单理解就是:浏览器发送请求,服务器接收并响应,但是服务器不记录请求是否来自哪个浏览器,服务器没记录浏览器的特征,也就是客户端的状态

为了解决上面的问题,就需要使用到会话管理,在Web项目中,进行会话管理时使用到Cookie和Session,二者配合实现会话管理。其中Cookie是在客户端保留少量数据的技术,主要通过响应头向客户端响应一些客户端要保留的信息,而Session是在服务端保留更多数据的技术,主要通过HttpSession对象保存一些和客户端相关的信息

Cookie是一种客户端会话技术,Cookie由服务端产生,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去

基本过程如下:

服务端创建Cookie,将Cookie放入响应对象中,Tomcat容器将Cookie转化为Set-Cookie响应头,响应给客户端。而客户端在收到Cookie的响应头时,在下次请求该服务的资源会以Cookie请求头的形式携带之前收到的Cookie

Cookie是一种键值对格式的数据,从tomcat8.5开始可以保存中文,但是不推荐。由于Cookie是存储于客户端的数据,比较容易暴露,一般不存储一些敏感或者影响安全的

Cookie原理图如下:

Cookie应用场景举例

  1. 记录用户名:当在用户名的输入框中输入完用户名后,浏览器记录用户名,下一次再访问登录页面时,用户名自动填充到用户名的输入框

  2. 保存电影播放进度:在网页上播放电影的时候,如果中途退出浏览器了,下载再打开浏览器播放同一部电影的时候,会自动跳转到上次退出时候的进度,因为在播放的时候会将播放进度保存到Cookie中

在Tomcat中实现Cookie可以使用下面的方式:

  1. 通过Cookie的有参构造函数创建Cookie对象,其中传递两个参数:第一个参数表示键,第二个参数表示值
  2. 调用HttpServletResponse对象的setCookie方法,将Cookie对象作为实参传递给该方法

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建Cookie
        Cookie cookie1 =new Cookie("c1","c1_message");
        Cookie cookie2 =new Cookie("c2","c2_message");
        // 将cookie放入响应对象
        resp.addCookie(cookie1);
        resp.addCookie(cookie2);
    }
}

配置Tomcat并运行即可在浏览器的响应头中看到对应的Set-Cookie

接着创建一个ServletB通过同一个客户端向服务器端发起请求,可以看到在请求头中存在刚才在ServletA中设置的Cookie。在ServletB中想要获取到请求体携带的Cookie可以调用HttpServletRequest对象的getCookies()方法获取到对应的Cookie数组,遍历数组可以拿到对应的键值对对象,通过该对象调用getName()getValue()即可获取到对应的键和值

Note

需要注意,如果请求头中没有Cookie,那么调用getCookies方法会返回null而不是返回一个空数组,所以在迭代Cookie数组时需要判断Cookie数组引用是否为null,否则会产生NullPointerException

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求中的cookie
        Cookie[] cookies = req.getCookies();
        //迭代cookies数组
        if (null != cookies && cookies.length!= 0) {
            for (Cookie cookie : cookies) {
                System.out.println(cookie.getName()+":"+cookie.getValue());
            }
        }
    }
}

配置Tomcat并运行即可在浏览器的请求头中看到对应的Cookie

Cookie一共分为两种:

  1. 会话级Cookie,特点是:服务器端并没有明确指定Cookie的存在时间,客户端Cookie数据存在于内存中。只要当前是同一个客户端,Cookie数据就一直都在,如果客户端关闭,内存中的Cookie数据就会被释放
  2. 持久化Cookie,特点是:服务器端明确设置了Cookie的存在时间,客户端Cookie数据会被保存到硬盘上。Cookie在硬盘上存在的时间根据服务器端限定的时间来管控,不受浏览器关闭的影响,只要到达了预设的时间,该Cookie就会被释放

默认情况下Cookie的有效期是一次会话范围(即同一个客户端发起请求)内,可以通过Cookie对象的setMaxAge()方法让Cookie持久化保存到浏览器上

Note

需要注意,setMaxAge()方法默认的时间单位是秒。如果设置参数为0,表示将浏览器中保存的该Cookie删除

例如使用ServletA创建一个持久化Cookie:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建Cookie
        Cookie cookie1 =new Cookie("c1","c1_message");
        // 设置Cookie有效时间为1分钟
        cookie1.setMaxAge(60);
        Cookie cookie2 =new Cookie("c2","c2_message");
        // 将cookie放入响应对象
        resp.addCookie(cookie1);
        resp.addCookie(cookie2);
    }
}

配置Tomcat并运行即可在浏览器的响应头中看到对应的Set-Cookie

同样使用ServletB再次向服务器发起请求,但是此时先关闭一次浏览器,再请求:

访问互联网资源时不是每次都需要把所有Cookie带上。访问不同的资源时,可以携带不同的cookie,在代码中可以通过Cookie对象的setPath(String path)方法对Cookie提交的路径进行设置

此时路径的写法与重定向的路径一致,下面以绝对路径为例:

ServletA中设置Cookie的提交路径:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建Cookie
        Cookie cookie1 =new Cookie("c1","c1_message");
        // 设置cookie的提交路径
        cookie1.setPath("/web03_war_exploded/servletB");
        Cookie cookie2 =new Cookie("c2","c2_message");
        // 将cookie放入响应对象
        resp.addCookie(cookie1);
        resp.addCookie(cookie2);
    }
}

配置Tomcat并运行即可在浏览器的响应头中看到对应的Set-Cookie

接着通过访问ServletB向服务器发起请求时可以看到携带的Cookie(cookie1cookie2):

如果使用其他任意的资源请求浏览器,则看不到对应的Cookie(只有cookie2):

Session介绍和使用

Session对象属于HttpSessionHttpSession是一种保留更多信息在服务端的一种技术,服务器会为每一个客户端开辟一块内存空间,即Session对象,客户端在发送请求时,都可以使用自己的Session,这样服务端就可以通过Session对象来记录某个客户端的状态

服务端在为客户端创建Session时,会同时将Session对象的ID(即JSESSIONID)以Cookie的形式放入响应对象。后端创建完Session后,客户端会收到一个特殊的Cookie,叫做JSESSIONID,当客户端下一次请求时,就会携带JSESSIONID,后端收到后,根据JSESSIONID找到对应的Session对象通过该机制,服务端通过Session对象就可以存储一些专门针对某个客户端的信息

原理图如下:

Session应用场景举例

  1. 记录用户的登录状态:用户登录后,将用户的账号等敏感信息存入Session
  2. 记录用户操作的历史:例如记录用户的访问痕迹,用户的购物车信息等临时性的信息

使用HttpSession的步骤如下:

  1. 调用HttpServletRequest对象的getSession()方法获取到Session对象
  2. 调用Session对象的getId()方法可以获取到已经创建的Session对象的JSESSIONID,通过Session对象isNew()方法可以判断指定的Session对象是否是最新的

因为Session对象是三大域对象之一,所以也有三大域对象共有的方法

例如下面的代码,创建一个ServletA向Session对象中存储数据:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求中的参数
        String username = req.getParameter("username");
        // 获取session对象
        HttpSession session = req.getSession();
         // 获取Session的ID
        String jSessionId = session.getId();
        System.out.println(jSessionId);
        // 判断session是不是新创建的session
        boolean isNew = session.isNew();
        System.out.println(isNew);
        // 向session对象中存入数据
        session.setAttribute("username",username);

    }
}

配置Tomcat并运行即可在浏览器的响应头中看到对应的Set-Cookie

创建一个ServletB,从Session对象中获取到对应的信息:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取session对象
        HttpSession session = req.getSession();
         // 获取Session的ID
        String jSessionId = session.getId();
        System.out.println(jSessionId);
        // 判断session是不是新创建的session
        boolean isNew = session.isNew();
        System.out.println(isNew);
        // 从session中取出数据
        String username = (String)session.getAttribute("username");
        System.out.println(username);
    }
}

配置Tomcat并运行即可在浏览器的请求头中看到对应的Cookie

isNew()方法是如何判断指定的Session对象是否是最新的取决于getSession()对象的处理逻辑,流程图如下:

Session的时效性

Session之所以需要时效性,是因为用户量很大之后,Session对象相应的也要创建很多。如果只创建不释放,那么服务器端的内存迟占用高,并且因为客户端关闭行为无法被服务端直接侦测,或者客户端较长时间不操作也经常出现,类似这些的情况也会导致内存占用高,所以就需要对Session的时限进行设置

默认的Session最大闲置时间(两次使用同一个Session中的间隔时间)在tomcat/conf/web.xml配置为30分钟

可以自己在当前项目的web.xml对最大闲置时间进行重新设定:

也可以通过HttpSession对象的API对最大闲置时间进行设定:

Java
1
2
// 设置最大闲置时间
session.setMaxInactiveInterval(60);

也可以直接让Session对象失效:

Java
1
2
// 直接让session失效
session.invalidate();

三大域对象

前面介绍过什么是域对象,并且已经提过最大范围的应用域对象:ServletContext,接下来就是剩下的两个域对象:

  1. 会话域对象:HttpSession
  2. 请求域对象:HttpServletRequest

三大域对象的数据作用范围图解:

  1. 请求域对象(每一次请求或者请求转发时创建,作用范围最小):

  2. 会话域对象(每一次会话时创建,作用范围大于请求域对象,但是小于应用域对象):

  3. 应用域对象(每一次启动Tomcat时由Tomcat自动创建,作用范围最大):

三大域对象作用范围综合图解:

因为都是域对象,所以共有下面的API:

API 功能
void setAttribute(String name,String value) 向域对象中添加/修改数据
Object getAttribute(String name); 从域对象中获取数据
removeAttribute(String name); 移除域对象中的数据

创建一个ServletA,向三大域对象中存放数据:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 向请求域中放入数据
        req.setAttribute("request","request-message");
        // 请求转发的方式
        //req.getRequestDispatcher("servletB").forward(req,resp);

        // 向会话域中放入数据
        HttpSession session = req.getSession();
        session.setAttribute("session","session-message");

        // 向应用域中放入数据
        ServletContext application = getServletContext();
        application.setAttribute("application","application-message");

    }
}

创建一个ServletB,向三大域对象中取出数据:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 从请求域中获取数据
        String reqMessage =(String)req.getAttribute("request");
        System.out.println(reqMessage);

        // 从会话域中获取数据
        HttpSession session = req.getSession();
        String sessionMessage =(String)session.getAttribute("session");
        System.out.println(sessionMessage);
        // 从应用域中获取数据
        ServletContext application = getServletContext();
        String applicationMessage =(String)application.getAttribute("application");
        System.out.println(applicationMessage);
    }
}

三大域对象的使用场景:

  • 请求域对象:请求转发时,请求域可以传递数据请求域内一般放本次请求业务有关的数据,如:查询到的所有的部门信息
  • 会话域对象:同一个会话内,不用请求转发,会话域可以传递数据会话域内一般放本次会话的客户端有关的数据,如:当前客户端登录的用户
  • 应用域对象:同一个APP内,不同的客户端,应用域可以传递数据应用域内一般放本程序应用有关的数据,如:Spring框架的IOC容器

过滤器

过滤器介绍和使用

Filter即过滤器,是JAVAEE技术规范之一,作用目标资源的请求进行过滤的一套技术规范,是JavaWeb项目中最为实用的技术之一

Filter接口定义了过滤器的开发规范,所有的过滤器都要实现该接口。Filter的工作位置是项目中所有目标资源之前,容器在创建HttpServletRequestHttpServletResponse对象后,会先调用FilterdoFilter方法。FilterdoFilter方法可以控制请求是否继续,如果放行,则请求继续,否则请求到此为止,此时过滤器本身做出响应。Filter不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理

Filter是GOF中责任链模式的典型案例,Filter的常用应用包括但不限于:登录权限检查、解决网站乱码、过滤敏感字符、日志记录、性能分析等

过滤器在开发中的应用场景

日志的记录、性能的分析、乱码的处理、事务的控制、登录的控制、跨域的处理等

过滤器工作位置图解:

根据Filter接口源码:

Java
1
2
3
4
5
6
7
8
public interface Filter {
    default public void init(FilterConfig filterConfig) throws ServletException {
    }
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException;
    default public void destroy() {
    }
}

常见的API有如下三个:

API 目标
default public void init(FilterConfig filterConfig) 初始化方法,由容器调用并传入初始配置信息filterConfig对象
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 过滤方法、核心方法、过滤请求、决定是否放行等响应之前的其他处理等都在该方法中
default public void destroy() 销毁方法,容器在回收过滤器对象之前调用的方法

例如开发一个日志记录过滤器,其功能如下:

  • 用户请求到达目标资源之前,记录用户的请求资源路径
  • 响应之前记录本次请求目标资源运算的耗时
  • 可以选择将日志记录进入文件,为了方便测试,这里将日志直接在控制台打印

代码如下:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class LoggingFilter implements Filter {

    // 创建日期格式对象
    private SimpleDateFormat dateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 参数父转子
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse  response = (HttpServletResponse)servletResponse;
        // 拼接日志文本
        String requestURI = request.getRequestURI();
        String time = dateFormat.format(new Date());
        String beforeLogging = requestURI+"在"+time+"被请求了";
        // 打印日志
        System.out.println(beforeLogging);
        // 获取系统时间
        long t1 = System.currentTimeMillis();
        // 放行请求
        filterChain.doFilter(request,response);
        // 获取系统时间
        long t2 = System.currentTimeMillis();
        //  拼接日志文本
        String afterLogging = requestURI+"在"+time+"的请求耗时:"+(t2-t1)+"毫秒";
        // 打印日志
        System.out.println(afterLogging);

    }
}

在上面的代码中,doFilter方法中的请求和响应对象是以父接口的形式声明的,实际传入的实参就是HttpServletRequestHttpServletResponse子接口级别的,可以安全强转。filterChain.doFilter(request,response);这行代码的功能是放行请求,如果没有这一行代码,则请求到此为止,filterChain.doFilter(request,response);在放行时需要传入requestresponse,意味着请求和响应对象要继续传递给后续的资源,这里没有产生新的HttpServletRequest对象和HttpServletResponse对象

为了测试上面的过滤器,下面定义两个Servlet用于测试:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理器请求
        System.out.println("servletA处理请求的方法,耗时10毫秒");
        // 模拟处理请求耗时
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理器请求
        System.out.println("servletB处理请求的方法,耗时15毫秒");
        // 模拟处理请求耗时
        try {
            Thread.sleep(15);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

为了可以让过滤器生效,需要在web.xml文件中配置Filter需要在哪些资源前进行过滤,配置方式与配置Servlet基本类似:

XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <!--配置filter,并为filter起别名-->
   <filter>
       <filter-name>loggingFilter</filter-name>
       <filter-class>com.atguigu.filters.LoggingFilter</filter-class>
   </filter>

    <!--为别名对应的filter配置要过滤的目标资源-->
    <filter-mapping>
        <filter-name>loggingFilter</filter-name>
        <!--通过映射路径确定过滤资源-->
        <url-pattern>/servletA</url-pattern>
        <!--通过后缀名确定过滤资源-->
        <url-pattern>*.html</url-pattern>
        <!--通过servlet别名确定过滤资源-->
        <servlet-name>servletBName</servlet-name>
    </filter-mapping>
</web-app>

在上面的内容中,filter-mapping标签中定义了过滤器对那些资源进行过滤,子标签url-pattern通过映射路径确定过滤范围,除了可以使用url-pattern以外,还可以使用servlet-name指定过滤资源,二者的区别就是url-pattern可以写静态资源和动态资源的路径,而servlet-name中就只能写指定的Servletname属性值。一个filter-mapping下可以配置多个url-pattern,也可以定义多个servlet-name,并且二者可以同时存在

过滤过程如下图所示:

过滤器的生命周期

过滤器作为web项目的组件之一,和Servlet的生命周期类似,但又略有不同,例如没有Servletload-on-startup的配置,默认就是系统启动立刻构造

控制声明周期和执行次数如下表所示:

阶段 对应方法 执行时机 执行次数
创建对象 构造器 Web应用启动时 1
初始化方法 void init(FilterConfig filterConfig) 构造完毕 1
过滤请求 void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 每次请求 多次
销毁 default void destroy() Web应用关闭时 1次

测试代码如下:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@WebServlet("/*")
public class LifeCycleFilter implements Filter {
    public LifeCycleFilter(){
        System.out.println("LifeCycleFilter constructor method invoked");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("LifeCycleFilter init method invoked");

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("LifeCycleFilter doFilter method invoked");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("LifeCycleFilter destroy method invoked");
    }
}

接着创建一个Servlet1作为测试:

Java
1
2
3
4
5
6
7
@WebServlet(value = "/s1", name = "servlet1")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("执行Servlet1");
    }
}

对应的web.xml文件如下:

XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">

    <filter>
        <filter-name>lifeCycleFilter</filter-name>
        <filter-class>LifeCycleFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>lifeCycleFilter</filter-name>
        <servlet-name>servlet1</servlet-name>
    </filter-mapping>
</web-app>

配置Tomcat后启动查看控制台,先看到:

Text Only
1
2
LifeCycleFilter init method invoked
LifeCycleFilter constructor method invoked

接着请求Servlet1,可以看到:

Text Only
1
2
LifeCycleFilter doFilter method invoked
执行Servlet1

接着终止Tomcat可以看到:

Text Only
1
LifeCycleFilter destroy method invoked

过滤器链的使用

一个Web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链

存在过滤器链时,过滤器执行顺序如下:

  • 过滤器链中的过滤器的顺序由filter-mapping顺序决定
  • 每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的
  • 如果某个Filter是使用servlet-name进行匹配规则的配置,那么这个Filter执行的优先级要更低

过滤器链执行过程:

过滤器链测试:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Filter1  implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter1 before chain.doFilter code invoked");

        filterChain.doFilter(servletRequest,servletResponse);

        System.out.println("filter1 after  chain.doFilter code invoked");

    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Filter2 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter2 before chain.doFilter code invoked");

        filterChain.doFilter(servletRequest,servletResponse);

        System.out.println("filter2 after  chain.doFilter code invoked");

    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Filter3 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter3 before chain.doFilter code invoked");

        filterChain.doFilter(servletRequest,servletResponse);

        System.out.println("filter3 after  chain.doFilter code invoked");

    }
}

用于测试的Servlet代码如下:

Java
1
2
3
4
5
6
7
@WebServlet("/sc")
public class ServletC extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("servletC service method invoked");
    }
}

过滤器的配置文件如下:

XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <filter>
        <filter-name>filter1</filter-name>
        <filter-class>com.atguigu.filters.Filter1</filter-class>
    </filter>

    <filter>
        <filter-name>filter2</filter-name>
        <filter-class>com.atguigu.filters.Filter2</filter-class>
    </filter>

    <filter>
        <filter-name>filter3</filter-name>
        <filter-class>com.atguigu.filters.Filter3</filter-class>
    </filter>

    <!--filter-mapping的顺序决定了过滤器的工作顺序-->
    <filter-mapping>
        <filter-name>filter1</filter-name>
        <url-pattern>/servletC</url-pattern>
    </filter-mapping>

    <filter-mapping>
        <filter-name>filter2</filter-name>
        <url-pattern>/servletC</url-pattern>
    </filter-mapping>

    <filter-mapping>
        <filter-name>filter3</filter-name>
        <url-pattern>/servletC</url-pattern>
    </filter-mapping>

</web-app>

上面的测试工作流程如下图所示:

使用@WebFilter配置过滤器

与配置Servlet一样,可以使用指定的注解@WebFilter对过滤器进行配置,下面是WebFilter注解的部分源码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package jakarta.servlet.annotation;

import jakarta.servlet.DispatcherType;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebFilter {
    // ...

    WebInitParam[] initParams() default {};

    String filterName() default "";

    // ...

    String[] servletNames() default {};

    String[] value() default {};

    String[] urlPatterns() default {};

    // ...
}

以下面的过滤器配置文件web.xml内容为例:

XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<!--配置filter,并为filter起别名-->
<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>com.atguigu.filters.LoggingFilter</filter-class>
    <!--配置filter的初始参数-->
    <init-param>
        <param-name>dateTimePattern</param-name>
        <param-value>yyyy-MM-dd HH:mm:ss</param-value>
    </init-param>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <!--通过映射路径确定过滤资源-->
    <url-pattern>/servletA</url-pattern>
    <!--通过后缀名确定过滤资源-->
    <url-pattern>*.html</url-pattern>
    <!--通过servlet别名确定过滤资源-->
    <servlet-name>servletBName</servlet-name>
</filter-mapping>

转换为注解如下:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@WebFilter(
        filterName = "loggingFilter",
        initParams = {@WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH:mm:ss")},
        urlPatterns = {"/servletA","*.html"},
        servletNames = {"servletBName"}
)
public class LoggingFilter  implements Filter {
    private SimpleDateFormat dateFormat ;

    /*
    * init初始化方法,通过filterConfig获取初始化参数
    * init方法中,可以用于定义一些其他初始化功能代码
    */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 获取初始参数
        String dateTimePattern = filterConfig.getInitParameter("dateTimePattern");
        // 初始化成员变量
        dateFormat=new SimpleDateFormat(dateTimePattern);
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 参数父转子
        HttpServletRequest request =(HttpServletRequest)  servletRequest;
        HttpServletResponse  response =(HttpServletResponse)  servletResponse;
        // 拼接日志文本
        String requestURI = request.getRequestURI();
        String time = dateFormat.format(new Date());
        String beforeLogging =requestURI+"在"+time+"被请求了";
        // 打印日志
        System.out.println(beforeLogging);
        // 获取系统时间
        long t1 = System.currentTimeMillis();
        // 放行请求
        filterChain.doFilter(request,response);
        // 获取系统时间
        long t2 = System.currentTimeMillis();
        String afterLogging =requestURI+"在"+time+"的请求耗时:"+(t2-t1)+"毫秒";
        // 打印日志
        System.out.println(afterLogging);

    }
}

Note

注意,如果使用注解方式配置过滤器链时,其执行顺序是按照过滤器名称的字典顺序进行执行

监听器

监听器介绍

监听器:专门用于对域对象对象身上发生的事件或状态改变进行监听和相应处理的对象

监听器是GOF设计模式中,观察者模式的典型案例。所谓观察者模式,就是当被观察的对象发生某些改变时,观察者自动采取对应的行动的一种设计模式。监听器使用的感受类似JavaScript中的事件,被观察的对象发生某些情况时自动触发代码的执行,不同的是监听器并不监听Web项目中的所有组件,仅仅是对三大域对象做相关的事件监听

Web中定义八个监听器接口作为监听器的规范,这八个接口按照不同的标准可以形成不同的分类。对于三大域对象,监听器的分类如下:

  1. 按监听的对象划分:

    • 应用域监听器:ServletContextListenerServletContextAttributeListener
    • 会话域域监听器:HttpSessionListenerHttpSessionAttributeListenerHttpSessionBindingListenerHttpSessionActivationListener
    • 请求域监听器:ServletRequestListenerServletRequestAttributeListener
  2. 按监听的事件划分:

    • 域对象的创建和销毁监听器:ServletContextListenerHttpSessionListenerServletRequestListener
    • 域对象数据增删改事件监听器:ServletContextAttributeListenerHttpSessionAttributeListenerServletRequestAttributeListener
    • 其他监听器:HttpSessionBindingListenerHttpSessionActivationListener

应用域监听器

ServletContextListener:用于监听ServletContext对象的创建与销毁

方法名 作用
contextInitialized(ServletContextEvent sce) ServletContext创建时调用
contextDestroyed(ServletContextEvent sce) ServletContext销毁时调用

其中,ServletContextEvent对象代表从ServletContext对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext对象

ServletContextAttributeListener:用于监听ServletContext中属性的添加、移除和修改

方法名 作用
attributeAdded(ServletContextAttributeEvent scab) ServletContext中添加属性时调用
attributeRemoved(ServletContextAttributeEvent scab) ServletContext中移除属性时调用
attributeReplaced(ServletContextAttributeEvent scab) ServletContext中的属性被修改时调用

ServletContextAttributeEvent对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getServletContext() 获取ServletContext对象

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@WebListener
public class ApplicationListener implements ServletContextListener , ServletContextAttributeListener {
    // 监听初始化
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext application = sce.getServletContext();
        System.out.println("application"+application.hashCode()+" initialized");
    }
    // 监听销毁
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContext application = sce.getServletContext();
        System.out.println("application"+application.hashCode()+" destroyed");
    }

    // 监听数据增加
    @Override
    public void attributeAdded(ServletContextAttributeEvent scae) {
        String name = scae.getName();
        Object value = scae.getValue();
        ServletContext application = scae.getServletContext();
        System.out.println("application"+application.hashCode()+" add:"+name+"="+value);
    }

    // 监听数据移除
    @Override
    public void attributeRemoved(ServletContextAttributeEvent scae) {
        String name = scae.getName();
        Object value = scae.getValue();
        ServletContext application = scae.getServletContext();
        System.out.println("application"+application.hashCode()+" remove:"+name+"="+value);
    }
    // 监听数据修改
    @Override
    public void attributeReplaced(ServletContextAttributeEvent scae) {
        String name = scae.getName();
        Object value = scae.getValue();
        ServletContext application = scae.getServletContext();
        Object newValue = application.getAttribute(name);
        System.out.println("application"+application.hashCode()+" change:"+name+"="+value+" to "+newValue);
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ServletA用于向application域中放入数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 向application域中放入数据
        ServletContext application = this.getServletContext();
        application.setAttribute("k1","v1");
        application.setAttribute("k2","v2");
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ServletB用于向application域中修改和移除数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext appliation  = getServletContext();
        //  修改application域中的数据
        appliation.setAttribute("k1","value1");
        //  删除application域中的数据
        appliation.removeAttribute("k2");
    }
}

会话域监听器

HttpSessionListener:用于监听HttpSession对象的创建与销毁

方法名 作用
sessionCreated(HttpSessionEvent hse) HttpSession对象创建时调用
sessionDestroyed(HttpSessionEvent hse) HttpSession对象销毁时调用

其中,HttpSessionEvent对象代表从HttpSession对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpSession对象

HttpSessionAttributeListener:监听HttpSession中属性的添加、移除和修改

方法名 作用
attributeAdded(HttpSessionBindingEvent se) HttpSession中添加属性时调用
attributeRemoved(HttpSessionBindingEvent se) HttpSession中移除属性时调用
attributeReplaced(HttpSessionBindingEvent se) HttpSession中的属性被修改时调用

其中,HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getSession() 获取触发事件的HttpSession对象

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@WebListener
public class SessionListener implements HttpSessionListener, HttpSessionAttributeListener {
    // 监听session创建
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        System.out.println("session"+session.hashCode()+" created");
    }

    // 监听session销毁
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        System.out.println("session"+session.hashCode()+" destroyed");
    }
    // 监听数据增加
    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        String name = se.getName();
        Object value = se.getValue();
        HttpSession session = se.getSession();
        System.out.println("session"+session.hashCode()+" add:"+name+"="+value);
    }
    // 监听数据移除
    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) {
        String name = se.getName();
        Object value = se.getValue();
        HttpSession session = se.getSession();
        System.out.println("session"+session.hashCode()+" remove:"+name+"="+value);
    }
    // 监听数据修改
    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {
        String name = se.getName();
        Object value = se.getValue();
        HttpSession session = se.getSession();
        Object newValue = session.getAttribute(name);
        System.out.println("session"+session.hashCode()+" change:"+name+"="+value+" to "+newValue);
    }

}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// servletA用于创建session并向session中放数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建session,并向session中放入数据
        HttpSession session = req.getSession();

        session.setAttribute("k1","v1");
        session.setAttribute("k2","v2");
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// servletB用于修改删除session中的数据并手动让session不可用
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        //  修改session域中的数据
        session.setAttribute("k1","value1");
        //  删除session域中的数据
        session.removeAttribute("k2");
        // 手动让session不可用
        session.invalidate();
    }
}

请求域对象

ServletRequestListener:监听ServletRequest对象的创建与销毁

方法名 作用
requestInitialized(ServletRequestEvent sre) ServletRequest对象创建时调用
requestDestroyed(ServletRequestEvent sre) ServletRequest对象销毁时调用

其中,ServletRequestEvent对象代表从HttpServletRequest对象身上捕获到的事件,通过这个事件对象可以获取到触发事件的HttpServletRequest对象。另外还有一个方法可以获取到当前Web应用的ServletContext对象

ServletRequestAttributeListener:监听ServletRequest中属性的添加、移除和修改

方法名 作用
attributeAdded(ServletRequestAttributeEvent srae) ServletRequest中添加属性时调用
attributeRemoved(ServletRequestAttributeEvent srae) ServletRequest中移除属性时调用
attributeReplaced(ServletRequestAttributeEvent srae) ServletRequest中的属性被修改时调用

其中,ServletRequestAttributeEvent对象代表属性变化事件,它包含的方法如下:

方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getServletRequest () 获取触发事件的ServletRequest对象

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@WebListener
public class RequestListener implements ServletRequestListener , ServletRequestAttributeListener {
    // 监听初始化
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest request = sre.getServletRequest();
        System.out.println("request"+request.hashCode()+" initialized");
    }

    // 监听销毁
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        ServletRequest request = sre.getServletRequest();
        System.out.println("request"+request.hashCode()+" destoryed");
    }


    // 监听数据增加
    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) {
        String name = srae.getName();
        Object value = srae.getValue();
        ServletRequest request = srae.getServletRequest();
        System.out.println("request"+request.hashCode()+" add:"+name+"="+value);
    }

    //  监听数据移除
    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        String name = srae.getName();
        Object value = srae.getValue();
        ServletRequest request = srae.getServletRequest();
        System.out.println("request"+request.hashCode()+" remove:"+name+"="+value);
    }
    // 监听数据修改
    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        String name = srae.getName();
        Object value = srae.getValue();
        ServletRequest request = srae.getServletRequest();
        Object newValue = request.getAttribute(name);
        System.out.println("request"+request.hashCode()+" change:"+name+"="+value+" to "+newValue);
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//  servletA向请求域中放数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 向request中增加数据
        req.setAttribute("k1","v1");
        req.setAttribute("k2","v2");
        // 请求转发
        req.getRequestDispatcher("servletB").forward(req,resp);
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// servletB修改删除域中的数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //  修改request域中的数据
        req.setAttribute("k1","value1");
        //  删除session域中的数据
        req.removeAttribute("k2");

    }
}