0
点赞
收藏
分享

微信扫一扫

29. Filter 过滤器 以及 Listener 监听器

29. Filter 过滤器 以及 Listener 监听器

JavaWeb的三大组件

组件

作用

实现接口

Servlet

小应用程序,在JavaWeb中主要做为控制器来使用  可以处理用户的请求并且做出响应

javax.servlet.Servlet

Filter

过滤器,对用户发送的请求或响应进行集中处理,实现请求的拦截

javax.servlet.Filter

Listener

监听器,在某些框架中会使用到监听器(比如spring),在Web执行过程中,引发一些事件,对相应事件进行处理

javax.servlet.XxxListener  每个事件有一个接口

一、 概述

生活中的过滤器

净水器、空气净化器、地铁安检

web中的过滤器

当用户访问服务器资源时,过滤器将请求拦截下来,完成一些通用的操作

应用场景

如:登录验证、统一编码处理、敏感字符过滤


29. Filter 过滤器 以及 Listener 监听器_spring

1592011832218

从上图可以简单说明一下,Filter 过滤器就是用来拦截 请求 或者 响应 的。下面我们首先来写一个快速入门的案例,从代码的角度来认识一下。

二、Filter过滤器 - 快速入门

首先我们创建一个HelloServlet,用来下面提供Filter进行拦截,如下:

29. Filter 过滤器 以及 Listener 监听器_spring_02


image-20210304235335136

@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("HelloServlet 被访问了...");
}
}

好了,下面我们来开始写 Filter 过滤器。而 Filter 跟 Servlet 一样,具有两种写法,一种是 xml 配置的方式,另一种是注解的方式。

下面我们首先来写 xml 配置的方式。

2.1 xml配置

2.1.1 编写java类,实现filter接口

29. Filter 过滤器 以及 Listener 监听器_servlet_03


image-20210304235727090

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* 过滤器入门案例
* 1. 编写一个类 , 实现Filter接口,重写抽象方法
* 2. 配置web.xml / 注解
*
* @author Aron.li
* @date 2021/3/4 23:55
*/
public class MyFilter implements Filter {
public void destroy() {
}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 处理拦截器的相关拦截业务
System.out.println("filter执行了,然后放行");

// 放行拦截,执行后续Servlet程序
chain.doFilter(req, resp);
}

public void init(FilterConfig config) throws ServletException {

}

}

2.1.2  配置web.xml

29. Filter 过滤器 以及 Listener 监听器_spring_04


image-20210304235853975

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">

<!--
filter的web.xml配置
1. filter和filter-mapping的子标签filter-name必须一致(可以自定义,通常与类名相同)
2. url-pattern : 当前filter要拦截的虚拟路径
-->
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/HelloServlet</url-pattern>
</filter-mapping>

</web-app>

2.1.3 启动服务,测试 filter 的效果

启动服务后,浏览器访问 http://localhost:8082/HelloServlet

29. Filter 过滤器 以及 Listener 监听器_过滤器_05


image-20210305000354097

好了,我们已经知道 xml 如何配置 filter 了,那么下面再来看看注解的配置方式。

2.2 注解配置

2.2.1  编写java类,实现filter接口

首先我们将上面的 xml 配置进行注释,如下:

29. Filter 过滤器 以及 Listener 监听器_tomcat_06


image-20210305000516205

然后给 MyFilter 设置注解,在注解中的参数就是配置需要拦截的请求路径,如下:

29. Filter 过滤器 以及 Listener 监听器_servlet_07


image-20210305000650221

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* 过滤器入门案例
* 1. 编写一个类 , 实现Filter接口,重写抽象方法
* 2. 配置web.xml / 注解
*
* @author Aron.li
* @date 2021/3/4 23:55
*/
// 注解配置
@WebFilter("/HelloServlet")
public class MyFilter implements Filter {
public void destroy() {
}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 处理拦截器的相关拦截业务
System.out.println("注解的方式: filter执行了,然后放行");

// 放行拦截,执行后续Servlet程序
chain.doFilter(req, resp);
}

public void init(FilterConfig config) throws ServletException {

}

}

2.2.2 重新部署服务,测试 filter 的效果

29. Filter 过滤器 以及 Listener 监听器_过滤器_08


image-20210305000842746

可以看到注解的方式的 filter 过滤器也成功拦截了请求了。

2.3 Filter模板设置

2.3.1 设置 Filter 模板

上面我们已经成功编写了拦截器的示例,为了可以快速编写代码,我们还可以修改拦截器的自动生成模板,如下:

搜索 file and code template ,选择 Other 如下:

29. Filter 过滤器 以及 Listener 监听器_tomcat_09


image-20210305001334388

模板如下:

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")
@javax.servlet.annotation.WebFilter(urlPatterns = "/${Entity_Name}")
public class ${Class_Name} implements javax.servlet.Filter {

public void init(javax.servlet.FilterConfig config) throws javax.servlet.ServletException {

}

public void doFilter(javax.servlet.ServletRequest req, javax.servlet.ServletResponse resp, javax.servlet.FilterChain chain) throws javax.servlet.ServletException, java.io.IOException {
chain.doFilter(req, resp);
}

public void destroy() {

}

}

2.3.2 测试创建模板

29. Filter 过滤器 以及 Listener 监听器_java_10


image-20210305001446860

29. Filter 过滤器 以及 Listener 监听器_servlet_11


image-20210305001503238

29. Filter 过滤器 以及 Listener 监听器_过滤器_12


image-20210305001517508

三、Filter过滤器 - 工作原理

1. 用户发送请求,请求Web资源(包括html,jsp,servlet等)
2. 如果Web资源的地址,匹配filter的地址,请求将先经过filter,并执行doFilter()
3. doFilter()方法中如果调用chain.doFilter(),则放行执行下一个Web资源。
4. 访问Web资源,响应回来会再次经过filter,执行过滤器中的代码,到达浏览器端。

四、Filter过滤器 - 使用细节

4.1 生命周期

生命周期:指的是一个对象从生(创建)到死(销毁)的一个过程

// filter 过滤器的声明周期主要有三个方法: init、doFilter、destory,分别如下:

// 初始化方法
public void init(FilterConfig config);

// 执行拦截方法
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain);

// 销毁方法
public void destroy();
* 创建
服务器启动项目加载,创建filter对象,执行init方法(只执行一次)

* 运行(过滤拦截)
用户访问被拦截目标资源时,执行doFilter方法

* 销毁
服务器关闭项目卸载时,销毁filter对象,执行destroy方法(只执行一次)

* 补充:
过滤器一定是优先于servlet创建的,后于Servlet销毁

下面个声明周期的案例。

4.1.1 编写一个演示声明周期的过滤器 LifeFilter

29. Filter 过滤器 以及 Listener 监听器_spring_13


image-20210305003635786

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* #Filter的生命周期
* 0. 先于Servlet创建,后于Servlet销毁
* 1. init方法
* filter 自动启动加载的,执行一次
* (先于Servlet的init方法执行)
* 2. doFilter 方法
* (先于Servlet的service方法执行)
* 浏览器每访问一次,就会执行一次
*
* chain.doFilter(req, resp); // 放行
*
* 3. destroy 方法
* (后于Servlet的destroy方法执行)
* tomcat关闭,会随之销毁, 只执行一次
*
* @author Aron.li
* @date 2021/3/5 0:31
*/
@WebFilter(urlPatterns = "/LifeServlet")
public class LifeFilter implements Filter {

public void init(FilterConfig config) throws ServletException {
System.out.println("lifeFilter init");
}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
/*
* 此方法决定了,后续资源是否被访问到
* 1. 如果调用此方法,后续资源就会被访问到
* 2. 如果没有调用,后续资源就不会被访问到
* -> 放行
*
* 类似于请求转发
* 不仅拦截对资源的请求,还拦截资源的响应
* */
System.out.println("lifeFilter doFilter before");
chain.doFilter(req, resp);
System.out.println("lifeFilter doFilter after");
}

public void destroy() {
System.out.println("lifeFilter destroy");
}

}

4.1.2 编写提供访问的 LifeServlet

29. Filter 过滤器 以及 Listener 监听器_tomcat_14


image-20210305003841971

package com.web;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @author Aron.li
* @date 2021/3/5 0:37
*/
@WebServlet("/LifeServlet")
public class LifeServlet implements Servlet {

@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("LifeServlet init");
}

@Override
public ServletConfig getServletConfig() {
return null;
}

@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("LifeServlet service");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {
System.out.println("LifeServlet destroy");
}

}

4.1.3 启动服务,测试请求 LifeServlet,查看声明周期函数的调用

首先访问 http://localhost:8082/LifeServlet ,查看声明周期函数打印如下:

29. Filter 过滤器 以及 Listener 监听器_tomcat_15


image-20210305004139979

从上面我结果来看,filter 过滤器的声明周期 都是在 servlet 程序的前面执行。下面我们关闭 tomcat,看看结束时候的生命周期。

29. Filter 过滤器 以及 Listener 监听器_tomcat_16


image-20210305004324089

在这里我们已经知道了 Filter 和 Servlet 之间的执行顺序,下面再来看看 Filter 的拦截路径。

4.2 拦截路径

在开发时,我们可以指定过滤器的拦截路径来定义拦截目标资源的范围

* 精准匹配
用户访问指定目标资源(/show.jsp)时,过滤器进行拦截

* 目录匹配
用户访问指定目录下(/user/*)所有资源时,过滤器进行拦截

* 后缀匹配
用户访问指定后缀名(*.html)的资源时,过滤器进行拦截

* 匹配所有
用户访问该网站所有资源(/*)时,过滤器进行拦截

4.2.1 精准匹配的案例

首先我们写一个 ​​show.jsp​​ ,如下:

29. Filter 过滤器 以及 Listener 监听器_tomcat_17


image-20210305004855960

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>show jsp</h1>
</body>
</html>

下面再来创建一个 PathFilter,专门来进行精确匹配,如下:

29. Filter 过滤器 以及 Listener 监听器_过滤器_18


image-20210305005432652

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = "/show.jsp")
public class PathFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

测试访问 ​​show.jsp​​ 查看拦截的情况,如下:

29. Filter 过滤器 以及 Listener 监听器_java_19


image-20210305005524070

4.2.2 目录匹配的案例

上面通过精确匹配,我们已经匹配拦截到了 ​​show.jsp​​, 下面我们再来创建一个路径,拦截这个路径下的请求,如下:

29. Filter 过滤器 以及 Listener 监听器_过滤器_20


image-20210305005901515

下面再修改 PathFilter 为目录匹配的方式,如下:

29. Filter 过滤器 以及 Listener 监听器_spring_21


image-20210305005942404

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")

2. 目录匹配
@WebFilter(urlPatterns = "/abc/*")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = "/abc/*")
public class PathFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("目录匹配: 被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

测试请求如下:

29. Filter 过滤器 以及 Listener 监听器_spring_22


image-20210305010029599

4.2.3 后缀名匹配的案例

这里我们只要修改 PathFilter 的匹配路径就可以了,如下:

29. Filter 过滤器 以及 Listener 监听器_过滤器_23


image-20210305010311534

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")

2. 目录匹配
@WebFilter(urlPatterns = "/abc/*")

3. 后缀名匹配
@WebFilter(urlPatterns = "*.jsp")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = "*.jsp")
public class PathFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("后缀名匹配: 被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

测试如下:

29. Filter 过滤器 以及 Listener 监听器_servlet_24


image-20210305010342602

4.2.4 匹配所有的案例

匹配所有也只需要修改 PathServlet 的匹配路径即可,如下:

29. Filter 过滤器 以及 Listener 监听器_servlet_25


image-20210305072039366

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")

2. 目录匹配
@WebFilter(urlPatterns = "/abc/*")

3. 后缀名匹配
@WebFilter(urlPatterns = "*.jsp")

4. 匹配所有 (html,css,js,servlet...)
@WebFilter(urlPatterns = "/*")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = "/*")
public class PathFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("匹配所有: 被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

测试如下:

  • 首先访问 index.jsp 如下:

29. Filter 过滤器 以及 Listener 监听器_过滤器_26


image-20210305072234706

  • 再访问 LifeServlet 如下:

29. Filter 过滤器 以及 Listener 监听器_java_27


image-20210305072327860

  • 最后再试试​​/abc/hello.jsp​​ 如下:

29. Filter 过滤器 以及 Listener 监听器_tomcat_28


image-20210305072448207

可以从上面的测试中,匹配所有可以拦截所有路径的请求。

4.2.5 匹配多个路径的案例

在上面我们已经尝试了四种匹配路径的方式,在最后我们再来测试多个路径拦截。

29. Filter 过滤器 以及 Listener 监听器_过滤器_29


image-20210305073004675

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
1. 精准匹配
@WebFilter(urlPatterns = "/LifeServlet")

2. 目录匹配
@WebFilter(urlPatterns = "/abc/*")

3. 后缀名匹配
@WebFilter(urlPatterns = "*.jsp")

4. 匹配所有 (html,css,js,servlet...)
@WebFilter(urlPatterns = "/*")
*
* @author Aron.li
* @date 2021/3/5 0:52
*/
@WebFilter(urlPatterns = {"*.jsp", "/LifeServlet"})
public class PathFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务
System.out.println("多个路径匹配: 被 PathFilter 拦截了 ....");
// 拦截放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

测试如下:

  • 访问匹配​​*.jsp​​ 的请求,如下:

29. Filter 过滤器 以及 Listener 监听器_spring_30


image-20210305073336152

  • 访问匹配​​/LifeServlet​​ 的请求,如下:

29. Filter 过滤器 以及 Listener 监听器_过滤器_31


image-20210305073401834

好了,写到这里我们已经知道了拦截器的拦截路径该如何匹配了,下面我们再来看拦截器如何来拦截不同的请求,例如:request 请求、forward 请求转发。

4.3 拦截方式

在开发时,我们可以指定过滤器的拦截方式来处理不同的应用场景,比如:只拦截从浏览器直接发送过来的请求,或者拦截内部转发的请求

总共有五种不同的拦截方式,我们这里学习常见的两种

1. request(默认拦截方式)
浏览器直接发送请求时,拦截
2. forward
请求转发的时候,拦截
比如: 资源A转发到资源B时

我们可以配置 二个同时存在...

下面的案例,我们还是分为 xml 版本 和 注解版本 两种来分开演示一下。

4.3.1 xml版本

下面我们简单演示一下 xml 版本该如何配置,那么首先来创建一个拦截器:

29. Filter 过滤器 以及 Listener 监听器_java_32


image-20210305074037955

public class MethodFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截业务代码
System.out.println("被 method 拦截了");
// 放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

配置 xml 如下:

29. Filter 过滤器 以及 Listener 监听器_servlet_33


image-20210305074300734

<!--
filter的web.xml配置
1. filter和filter-mapping的子标签filter-name必须一致(可以自定义,通常与类名相同)
2. url-pattern : 当前filter要拦截的虚拟路径
-->
<filter>
<filter-name>MethodFilter</filter-name>
<filter-class>com.filter.MethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MethodFilter</filter-name>
<url-pattern>/HelloServlet</url-pattern>
<!--
dispatcher : 用来指定拦截方式的
1. REQUEST(默认) : 浏览器直接发送过来的请求
2. FORWARD: 请求转发过来的请求

可以同时设置
-->
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>

测试请求如下:

29. Filter 过滤器 以及 Listener 监听器_java_34


image-20210305074512188

在这里我们只演示该如何配置,下面我们在注解版本的案例中,来演示拦截请求转发的请求。

4.3.2 注解版本

4.3.2.1 首先创建一个注解版本的拦截器 DispatcherFilter,然后创建两个Servlet,其中为 AServlet  和 BServlet,访问 AServlet 的时候请求转发至 BServlet ,查看 DispatcherFilter 是否会拦截请求转发。

  • 拦截器 DispatcherFilter

29. Filter 过滤器 以及 Listener 监听器_spring_35


image-20210305075446687

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* @author Aron.li
* @date 2021/3/5 7:49
*/
@WebFilter(urlPatterns = "*.do")
public class DispatcherFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截
System.out.println("DispatcherFilter 拦截 *.do 请求..");
// 放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

在这里设置拦截路径 ​​*.do​​​, 下面我们将两个Servlet 的路径都加上 ​​.do​​ 路径,那么就可以被拦截了。但是请求转发的 forward请求 会被拦截么?

  • AServlet

29. Filter 过滤器 以及 Listener 监听器_java_36


image-20210305075939890

package com.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @author Aron.li
* @date 2021/3/5 7:47
*/
@WebServlet("/AServlet.do")
public class AServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问 AServlet.....");
// 请求转发至 BServlet。。。
request.getRequestDispatcher("/BServlet.do").forward(request, response);
}
}
  • BServlet

29. Filter 过滤器 以及 Listener 监听器_过滤器_37


image-20210305080009243

package com.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* @author Aron.li
* @date 2021/3/5 7:48
*/
@WebServlet("/BServlet.do")
public class BServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问 BServelet...");
}
}
  • 最后,我们测试一下,只访问 AServlet,然后 AServlet 自动转发 BServlet, 看看会不会拦截两次

29. Filter 过滤器 以及 Listener 监听器_spring_38


image-20210305080138845

可以从拦截的结果来看,默认只拦截了 request 请求,而不会去拦截 请求转发的 forward 请求。那么如果我们需要拦截 forward 请求,则需要配置 拦截方式。

4.3.2.2 配置同时拦截 forward 和 request 请求

29. Filter 过滤器 以及 Listener 监听器_java_39


image-20210305080408797

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* 拦截方式
* 1. REQUEST(默认) : 浏览器发起的请求
* 2. FORWARD : 请求转发
* 可以同时设置
*
* @author Aron.li
* @date 2021/3/5 7:49
*/
@WebFilter(urlPatterns = "*.do", dispatcherTypes = {DispatcherType.FORWARD, DispatcherType.REQUEST})
public class DispatcherFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截
System.out.println("DispatcherFilter 拦截 *.do 请求..");
// 放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

再次测试访问 AServlet, 确认是否拦截请求转发:

29. Filter 过滤器 以及 Listener 监听器_spring_40


image-20210305080518449

4.4 过滤器链

在一次请求中,若我们请求匹配到了多个filter,通过请求就相当于把这些filter串起来了,形成了过滤器链

* 需求
用户访问目标资源 show.jsp时,经过 FilterA FilterB

* 过滤器链执行顺序 (先进后出)
1.用户发送请求
2.FilterA拦截,放行
3.FilterB拦截,放行
4.执行目标资源 show.jsp
5.FilterB增强响应
6.FilterA增强响应
7.封装响应消息格式,返回到浏览器

* 过滤器链中执行的先后问题....
配置文件
谁先声明,谁先执行
<filter-mapping>
注解【不推荐】
根据过滤器类名进行排序,值小的先执行
FilterA FilterB 进行比较, FilterA先执行...

29. Filter 过滤器 以及 Listener 监听器_过滤器_41


1592017660642

下面我们来基于上面的需求来写一个案例。

4.4.1 创建访问资源 ServletA

29. Filter 过滤器 以及 Listener 监听器_servlet_42


image-20210305224731864

@WebServlet("/ServletA")
public class ServletA extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问 ServletA.....");
}
}

4.4.2 创建拦截器 FilterA

29. Filter 过滤器 以及 Listener 监听器_java_43


image-20210305224844070

public class FilterA implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("FilterA执行了");
/*
* 放行: 允许请求往后继续传递
* 1. 等价于请求转发
* 2. 如果后续还有过滤器,先执行过滤器, 知道过滤器执行完毕,才到资源里去
* */
chain.doFilter(req, resp);
}

public void destroy() {

}

}

4.4.3 创建拦截器 FilterB

29. Filter 过滤器 以及 Listener 监听器_tomcat_44


image-20210305225107452

public class FilterB implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
System.out.println("FilterB执行了");
chain.doFilter(req, resp);
}

public void destroy() {

}

}

4.4.4 在xml配置拦截器

29. Filter 过滤器 以及 Listener 监听器_过滤器_45


image-20210305225444671

<!--  过滤器链是按照xml从上到下的配置顺序进行逐步拦截的      -->
<!-- 配置过滤器FilterB -->
<filter>
<filter-name>FilterB</filter-name>
<filter-class>com.filter.FilterB</filter-class>
</filter>

<filter-mapping>
<filter-name>FilterB</filter-name>
<url-pattern>/ServletA</url-pattern>
</filter-mapping>

<!-- 配置过滤器 FilterA -->
<filter>
<filter-name>FilterA</filter-name>
<filter-class>com.filter.FilterA</filter-class>
</filter>

<filter-mapping>
<filter-name>FilterA</filter-name>
<url-pattern>/ServletA</url-pattern>
</filter-mapping>

4.4.5 访问 ServletA,查看拦截器链的执行顺序

29. Filter 过滤器 以及 Listener 监听器_java_46


image-20210305225533705

可以从结果来看,首先 FilterB 进行了拦截,然后 FilterA 拦截,最后访问到了 ServletA

五、Filter案例

5.1 统一网站编码

需求

tomcat8.5版本中已经将get请求的中文乱码解决了,但是post请求还存在中文乱码

浏览器发出的任何请求,通过过滤器统一处理中文乱码

5.1.1 需求分析

29. Filter 过滤器 以及 Listener 监听器_servlet_47


1592020764180

5.1.2 中文乱码的问题

首先我们来写注册页面、登录页面,还有对应的 Servlet,查看相关的中文乱码问题。

注册页面

29. Filter 过滤器 以及 Listener 监听器_过滤器_48


image-20210306234155527

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
</head>
<body>

<h1>注册页面</h1>
<form action="RegisterServlet" method="post">
<input type="text" name="username" placeholder="请输入要注册的用户名"> <br>
<input type="password" name="pwd" placeholder="请输入要注册的密码"> <br>
<input type="submit">
</form>

</body>
</html>

登录页面

29. Filter 过滤器 以及 Listener 监听器_过滤器_49


image-20210306234847655

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>

<h2>登录页面</h2>
<form action="LoginServlet" method="post">
<input type="text" name="username" placeholder="请输入用户名"> <br>
<input type="password" name="pwd" placeholder="请输入的密码"> <br>
<input type="submit">
</form>

</body>
</html>

注册 RegisterServlet

29. Filter 过滤器 以及 Listener 监听器_servlet_50


image-20210307000228838

@WebServlet("/RegisterServlet")
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取注册用户所需的字段参数
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
System.out.println("RegisterServlet:" + username + "," + pwd);
}
}

登录 LoginServlet

29. Filter 过滤器 以及 Listener 监听器_java_51


image-20210307000809869

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取登录参数
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
System.out.println("LoginServlet:" + username + "," + pwd);
}
}

测试请求注册,查看中文乱码的情况

输入英文注册:

29. Filter 过滤器 以及 Listener 监听器_java_52


image-20210307001123826

点击提交,之后查看 RegisterServlet 的后台打印信息,如下:

29. Filter 过滤器 以及 Listener 监听器_过滤器_53


image-20210307001225494

输入中文注册:

29. Filter 过滤器 以及 Listener 监听器_过滤器_54


image-20210307001302046

29. Filter 过滤器 以及 Listener 监听器_过滤器_55


image-20210307001324575

测试请求登录,查看中文乱码的情况

29. Filter 过滤器 以及 Listener 监听器_java_56


image-20210307001451657

29. Filter 过滤器 以及 Listener 监听器_过滤器_57


image-20210307001518275

可以看到在 RegisterServlet 和 LoginServlet 都出现了中文乱码的请求参数问题,下面我们使用拦截器来统一解决。

5.1.2 定义解决中文乱码请求的过滤器 PostFilter

  • 真实场景中,过滤器不会统一响应,因为响应的mime类型可能不同(有些返回html页面,有些返回JSON格式字符串)

29. Filter 过滤器 以及 Listener 监听器_tomcat_58


image-20210307002746257

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
* @author Aron.li
* @date 2021/3/7 0:16
*/
@WebFilter(urlPatterns = "/*") // 1. 使用 /* 拦截所有请求
public class PostFilter implements Filter {

public void init(FilterConfig config) throws ServletException {

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
// 拦截请求,设置中文编码格式,解决中文乱码问题
// 1. 获取请求 request
HttpServletRequest request = (HttpServletRequest) req;
// 2. 判断请求的方法类型
String method = request.getMethod();
if ("POST".equalsIgnoreCase(method)) {
//3. 当请求的方法为 POST,则设置编码格式
//解决请求参数的中文乱码
request.setCharacterEncoding("UTF-8");
}

// 4. 打印 req 和 request
System.out.println("req:" + req);
System.out.println("request:" + request);

// 放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

再次测试请求,查看是否还会出现中文乱码的情况:

  • 请求登录

29. Filter 过滤器 以及 Listener 监听器_过滤器_59


image-20210307002927068

29. Filter 过滤器 以及 Listener 监听器_java_60


image-20210307003002203

  • 请求注册

29. Filter 过滤器 以及 Listener 监听器_过滤器_61


image-20210307003025324

29. Filter 过滤器 以及 Listener 监听器_过滤器_62


image-20210307003050291

5.2 非法字符拦截

需求

当用户发出非法言论的时候,提示用户言论非法警告信息

5.2.1 需求分析

29. Filter 过滤器 以及 Listener 监听器_servlet_63


1587622441357

5.2.2 代码实现

非法词库

创建一个 ​​words.properties​​的配置文件,如下:

29. Filter 过滤器 以及 Listener 监听器_spring_64


image-20210307082253457

keyword=傻叉,大爷,二大爷的

注意: properties文件编码问题

29. Filter 过滤器 以及 Listener 监听器_java_65


image-20210307082502147

模拟发送留言的页面 bbs.jsp

29. Filter 过滤器 以及 Listener 监听器_servlet_66


image-20210307082937485

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="WordServlet" method="get">
<input type="text" name="word" placeholder="请发言">
<input type="submit" value="发送"> <br>
</form>
</body>
</html>

接收留言信息的 WordServlet

29. Filter 过滤器 以及 Listener 监听器_servlet_67


image-20210307083315002

package com.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
* @author Aron.li
* @date 2021/3/7 8:30
*/
@WebServlet("/WordServlet")
public class WordServlet extends HttpServlet {

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 接收留言信息
String word = request.getParameter("word");

// 响应留言信息
response.setContentType("text/html;charset=utf-8");
response.getWriter().print(word);
}
}

此时我们启动服务,留个脏话来看看效果,如下:

29. Filter 过滤器 以及 Listener 监听器_spring_68


image-20210307083724710

29. Filter 过滤器 以及 Listener 监听器_tomcat_69


image-20210307083739197

可以看到脏话的词汇直接返回到浏览器,为了避免这种情况,下面我们用过滤器就行过滤

非法字符过滤器 WordsFilter

29. Filter 过滤器 以及 Listener 监听器_spring_70


image-20210307084509519

package com.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

/**
* @author Aron.li
* @date 2021/3/7 8:38
*/
@WebFilter(urlPatterns = "/WordServlet")
public class WordsFilter implements Filter {

// 非法字符词库集合
List<String> words;

public void init(FilterConfig config) throws ServletException {
// 读取非法字符词库的单词
InputStream is = WordsFilter.class.getClassLoader().getResourceAsStream("words.properties");
Properties properties = new Properties();
try {
properties.load(is); // 加载输入流
String keyword = properties.getProperty("keyword"); // 读取属性
String[] strings = keyword.split(","); // 根据逗号 , 拆分字符串, 获取字符串数组
this.words = Arrays.asList(strings); // 将数组转为list集合

} catch (Exception e) {
e.printStackTrace();
}

}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//1. 获取用户的发言
String word = req.getParameter("word");
//2. 是否包含敏感词
for (String dirty : this.words) {
//word字符串是否包含dirty
// 小瘪三 包含 瘪三, 返回true
if(word.contains(dirty)){
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().print("发言不友善,扣分10");
return;
}
}
//如果都不包含脏话, 放行
chain.doFilter(req, resp);
}

public void destroy() {

}

}

再次测试如下:

29. Filter 过滤器 以及 Listener 监听器_java_71


image-20210307084545234

29. Filter 过滤器 以及 Listener 监听器_servlet_72


image-20210307084657080

六、 Listener

1.1 概述

生活中的监听器

我们很多商场有摄像头,监视着客户的一举一动。如果客户有违法行为,商场可以采取相应的措施。

javaweb中的监听器

在我们的java程序中,有时也需要监视某些事情,一旦被监视的对象发生相应的变化,我们应该采取相应的操作。

监听web三大域对象:HttpServletRequest、HttpSession、ServletContext  (创建和销毁)

场景

历史访问次数、统计在线人数、系统启动时初始化配置信息

监听器的接口分类

事件源

监听器接口

时机

ServletContext

ServletContextListener

上下文域创建和销毁

ServletContext

ServletContextAttributeListener

上下文域属性增删改的操作

**HttpSession **

HttpSessionListener

会话域创建和销毁

**HttpSession **

HttpSessionAttributeListener

会话域属性增删改的操作

HttpServletRequest

ServletRequestListener

请求域创建和销毁

HttpServletRequest

ServletRequestAttributeListener

请求域属性增删改的操作

1.2 快速入门

监听器在web开发中使用的比较少,见的机会就更少了,下面我们使用ServletContextListenner来学习下监听器,因为这个监听器是监听器中使用率最高的一个,且监听器的使用方式都差不多。

我们使用这个监听器可以在项目启动和销毁的时候做一些事情,例如,在项目启动的时候加载配置文件。

步骤分析

1. 创建一个普通类,实现 ServletContextListener

2. 重写抽象方法
监听ServletContext创建
监听ServletContext销毁

3. 配置
web.xml
注解

① xml版本

创建 MyServletContextListener 如下:

29. Filter 过滤器 以及 Listener 监听器_tomcat_73


image-20210307085949685

package com.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
*
* Listener
* 1. 创建一个类,实现相应接口(6个)
*
* 2. web.xml配置/注解配置
*
* 举例:
* ServletContextListener
上下文域创建和销毁

问题:
tomcat启动,加载项目时创建
tomcat关闭,销毁项目时消失

监听器: ServletContextListener
1. 创建
tomcat启动时,创建
(早于相应的域对象 ServletContext)
2. 运行
(监听对应的域对象 ServletContext)
ServletContext创建的时候, 这个监听器contextInitialized就会执行
ServletContext销毁的时候, 这个监听器contextDestroyed就会执行

3. 销毁
tomcat关闭时,销毁
(后于相应的域对象 ServletContext)
*
* @author Aron.li
* @date 2021/3/7 8:56
*/
public class MyServletContextListener implements ServletContextListener {
//当上下文域对象 初始化的时候
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("serlvetContext 创建了");
}

//当上下文域对象 销毁的时候
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("serlvetContext 销毁了");
}

}

配置 web.xml

29. Filter 过滤器 以及 Listener 监听器_java_74


image-20210307090205518

<!--  配置监听器      -->
<listener>
<listener-class>com.listener.MyServletContextListener</listener-class>
</listener>

启动 tomcat,查看监听效果如下:

29. Filter 过滤器 以及 Listener 监听器_tomcat_75


image-20210307090355698

停止 tomcat,查看监听效果如下:

29. Filter 过滤器 以及 Listener 监听器_java_76


image-20210307090434422

② 注解版本

@WebListener
public class MyServletContextListener implements ServletContextListener {
//当上下文域对象 初始化的时候
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("serlvetContext 创建了");
}
//当上下文域对象 销毁的时候
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("serlvetContext 销毁了");
}
}

监听属性增删改的监听器

import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.annotation.WebListener;

/*
* 监听: ServletContext的属性变化
* */
@WebListener
public class MyServletContextAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext 属性增加了");
}

@Override
public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext 属性被移除了");
}

@Override
public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext 属性替换了");
}
}

1.3 案例:模拟spring框架

需求:可以在项目启动时读取配置文件

1.3.1 在 xml 配置全局参数

29. Filter 过滤器 以及 Listener 监听器_spring_77


image-20210307090803792

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">

<!--全局配置参数-->
<context-param>
<param-name>configLocation</param-name>
<param-value>words.properties</param-value>
</context-param>
</web-app>

1.3.1 编写监听器读取配置信息

29. Filter 过滤器 以及 Listener 监听器_java_78


image-20210307091245950

package com.filter;

import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
* 监听: ServletContext 的创建
*
* @author Aron.li
* @date 2021/3/7 9:09
*/
@WebListener
public class MySpringListener implements ServletContextListener {

// ServletContext对象创建就会执行
@Override
public void contextInitialized(ServletContextEvent sce) {
/*
* 1. 获取servletContext对象
* a. 小的域对象可以获取大的域对象(Servlet,Filter)
* b. 监听器: xxxEvent 就会 xxx 域对象
* */
ServletContext context = sce.getServletContext();
//2. 读取全局配置参数
String configLocation = context.getInitParameter("configLocation");
System.out.println("读取全局配置参数: " + configLocation);
}

@Override
public void contextDestroyed(ServletContextEvent sce) {

}
}

1.4 案例:统计在线人数

需求

有用户使用网站,在线人数就+1;用户退出网站,在线人数就-1

# 分析:
1. 怎么判定用户在线还是离线?
a. 在线: 一个用户访问,应该给他生成一个session
假设第一个访问页面: index.jsp
(jsp底层: 默认获取session)
如果有其他页面: html之类的
(Filter进行拦截: request.getSession();)
b. 离线: 用户点击退出 , 把对应的session销毁 -> LogoutServlet
(如果用户直接X掉网页, 服务器默认等待30分钟,才会销毁session -> 时间太长了)
(长连接: 心跳包, 每隔30秒发一个请求 空包)

2. 监听session的创建和销毁
- > HttpSessionListener
1. 监听到创建 number + 1
2. 监听到销毁 number - 1

3. number 存在哪里?
-> ServletContext 域对象 (全局)

4. 何时初始化number?
-> ServletContextListener
1. 监听到创建 : 初始化number,然后存进ServletContext

1.4.1 技术分析

使用 ServletContext域对象 存储在线总人数

使用 ServletContextListener监听器,在项目启动时,初始化总人数为0

使用 HttpSessionListener监听器,用户访问,人数+1,用户退出,人数-1

使用 LogoutServlet控制器,对当前会话的session销毁

1.4.2 需求分析

29. Filter 过滤器 以及 Listener 监听器_java_79


1587779813977

1.4.3 代码实现

初始化计数的监听器: InitCountServletContextListener

29. Filter 过滤器 以及 Listener 监听器_tomcat_80


image-20210307091745955

@WebListener
public class InitCountServletContextListener implements ServletContextListener {
//监听到ServletContext创建就会执行
@Override
public void contextInitialized(ServletContextEvent sce) {
//初始化number,然后存进ServletContext
int number = 0;
sce.getServletContext().setAttribute("number",number);
}

@Override
public void contextDestroyed(ServletContextEvent sce) {

}
}

基于session计数的监听器: CountSessionListener

29. Filter 过滤器 以及 Listener 监听器_servlet_81


image-20210307092043325

package com.listener;

import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
* @author Aron.li
* @date 2021/3/7 9:18
*/
@WebListener
public class CountSessionListener implements HttpSessionListener {
// session创建,就会执行
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession(); // 获取HttpSession域对象

ServletContext servletContext = session.getServletContext(); // 获取 ServletContext
int number = (int) servletContext.getAttribute("number"); // 增加number值
number++;
servletContext.setAttribute("number", number);

System.out.println("有人上线了");
}

// session销毁,就会执行
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession(); // 获取HttpSession域对象

ServletContext servletContext = session.getServletContext(); // 获取 ServletContext
int number = (int) servletContext.getAttribute("number"); // 减少number值
number--;
servletContext.setAttribute("number", number);

System.out.println("有人下线了....");
}
}

显示在线人数的页面:count.jsp

29. Filter 过滤器 以及 Listener 监听器_servlet_82


image-20210307092320206

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<html>
<head>
<title>Title</title>
</head>
<body>
<h1>当前在线人数:</h1>
<div> ${number} </div>

<a href="LogoutServlet">退出登录</a>
</body>
</html>

用户退出: LogoutServlet

29. Filter 过滤器 以及 Listener 监听器_过滤器_83


image-20210307092421035

@WebServlet("/LogoutServlet")
public class LogoutServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 销毁session
request.getSession().invalidate();
response.getWriter().write("logout");
}
}

1.4.4 测试效果

启动服务,访问 count.jsp 如下:

29. Filter 过滤器 以及 Listener 监听器_tomcat_84


image-20210307092509074

多个浏览器访问:

29. Filter 过滤器 以及 Listener 监听器_spring_85


image-20210307092622764

退出登录:

29. Filter 过滤器 以及 Listener 监听器_java_86


image-20210307092733541

举报

相关推荐

0 条评论