Filter 介绍

过滤器,顾名思义就是对事物进行过滤的,在Web中的过滤器,当然就是对请求进行过滤,对web服务器管理的所有web资源,我们使用过滤器,就可以对请求进行拦截,然后做相应的处理,实现许多特殊功能。如登录控制,权限管理,过滤敏感词汇等.

Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过 Filter 技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,Filter接口源代码:

1
2
3
4
5
6
public abstract interface Filter{
public abstract void init(FilterConfig paramFilterConfig) throws ServletException;
public abstract void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain
paramFilterChain) throws IOException, ServletException;
public abstract void destroy();
}

Filter接口中有一个doFilter方法,当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的: - 调用目标资源之前,让一段代码执行。 - 是否调用目标资源(即是否让用户访问web资源)。

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

过滤器原理

当我们使用过滤器时,过滤器会对游览器的请求进行过滤,过滤器可以动态的分为3个部分

1.放行之前的代码,2.放行,3.放行后的代码

  • 第一部分代码会对游览器请求进行第一次过滤,然后继续执行
  • 第二部分代码就是将游览器请求放行,如果还有过滤器,那么就继续交给下一个过滤器
  • 第三部分代码就是对返回的Web资源再次进行过滤处理

我们使用过滤器,也就是说,不止请求会经过过滤器,我们的响应也会经过过滤器。

img
img

使用过滤器

我们创建Filter,只需要继承Filter接口就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
import javax.servlet.*;
import java.io.IOException;

@WebFilter("/*")
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("对request进行过滤");
//下面这行代码就是放行
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("对response进行过滤");
}
}

我们实现了一个 doFilter 方法,这个方法就是我们写过滤代码的地方,具体逻辑就是和上面介绍的过滤器原理一样的。

我简单介绍下上面的代码,WebFilter("/*") 表示对所有请求进行过滤,而在doFilter中的放行代码,也就是filterChain.doFilter(servletRequest,servletResponse);这行代码就是对拦截进行放行

过滤器并不会管资源是否存在,而只会对配置的拦截路径进行拦截。拦截不仅会对请求进行拦截,而且还会对相应进行拦截。

配置过滤器拦截路径

配置Filter的拦截路径有2种方式,一种是注解,一种是xml方式,我们分别进行讲解

注解方式

我们如果使用注解来进行配置,那么我们就需要使用 @WebFilter ,直接看该注解的源码

img
img
  • filterName:该filter的名字
  • initParams:初始化参数
  • displayName:filter显示名称
  • servletNames:指定对哪些servlet进行过滤
  • asyncSupported:是否支持异步模式
  • urlPatterns:指定拦截路径
  • value:指定拦截路径

urlPatterns和value是一样的。urlPatterns和value只能配置一个,不能两个都配置,两个都配置就会报错。

对于使用@WebFilter,里面的多个参数用 , 进行分隔。

如果我们仅仅需要配置一个拦截路径,那么我们可以直接简写@WebLister(“拦截路径”),如@WebFilter(“/*”)就是拦截所有请求。

1
2
@WebFilter(value = {"/login"}, filterName = "filtersss")
public class CharSetFilter implements Filter {

多个参数配置,需要分隔

xml 方式

xml方式可以说是和Servlet使用xml配置方式一样了

1
2
3
4
5
6
7
8
<filter>
<filter-name>myFilter</filter-name>
<filter-class>com.ergoutree.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

把注解换成了xml标签来配置,里面属性都是一样的,这个和Servlet的配置方式基本一样

过滤器生命周期

Filter 的生命周期和 Servlet 也十分相似

创建一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import javax.servlet.*;
import java.io.IOException;

public class LifeCycleFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}

@Override
public void destroy() {
}
}

Filter有3个阶段,分别是初始化,拦截和过滤,销毁。

  • 初始化阶段:当服务器启动时,我们的服务器(Tomcat)就会读取配置文件,扫描注解,然后来创建我们的Filter。
  • 拦截和过滤阶段:只要请求资源的路径和拦截的路径相同,那么过滤器就会对请求进行过滤,这个阶段在服务器运行过程中会一直循环。
  • 销毁阶段:当服务器(Tomcat)关闭时,服务器创建的Filter也会随之销毁。

Filter的三个阶段就对应着Filter的3个方法,init 方法会在 Filter 创建时调用,doFilter 方法会在请求和拦截匹配时调用,destroy 方法会在 Filter 销毁时调用

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
@WebFilter(value = {"/login"}, filterName = "filtersss")
public class CharSetFilter implements Filter {
private static final Logger LOGGER = Logger.getLogger(CharSetFilter.class.getName());
private static final String CHARSET = "UTF-8";

public void init(FilterConfig config) throws ServletException {
System.out.println("LoginOccasion过滤器初始化完成 - " + new Date());
}

public void destroy() {
System.out.println("LoginOccasion过滤器被销毁 - " + new Date());
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
// 1. 预处理 - 在请求到达Servlet之前执行
System.out.println("LoginOccasion过滤器: 预处理开始");

// 获取客户端IP地址
String ipAddress = request.getRemoteAddr();
System.out.println("请求来自IP: " + ipAddress + " - " + new Date());

request.setCharacterEncoding(CHARSET);
response.setCharacterEncoding(CHARSET);
response.setContentType("text/html; charset=" + CHARSET);

if(request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
System.out.println("访问的URL: " + httpRequest.getRequestURL());
System.out.println("HTTP方法: " + httpRequest.getMethod());
}

// 记录请求开始时间
long startTime = System.currentTimeMillis();

// 2. 将请求传递给下一个过滤器或目标Servlet
chain.doFilter(request, response);

// 3. 后处理 - 在响应返回客户端之前执行
long endTime = System.currentTimeMillis();
System.out.println("请求处理耗时: " + (endTime - startTime) + "毫秒");
System.out.println("LoginOccasion过滤器: 后处理完成");
}
}

FilterConfig和FilterChain

FilterConfigFilterConfig这2个对象是由服务器(Tomcat)在创建和调用Filter对象时所传入的,这2个对象十分有用,FilterConfig对象可以读取我们配置的初始参数,FilterChain可以实现多个Filter之间的连接。

FilterConfig

img
img
img
img

里面的方法就4个,下面我们分别进行讲解

  • getFilterName():获取filter的名称
  • getServletContext():获取ServletContext
  • getInitparamter(String var1):获取配置的初始参数的值
  • getInitParamterNames():获取配置的所有参数名称

实际例子

我们在init方法中使用FilterConfig来读取配置的数据库的信息,然后输出。

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
import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;

public class MyFilterConfig implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("-----------获取全部key:value------------");
//得到所有配置参数的名字
Enumeration<String> names = filterConfig.getInitParameterNames();
while (names.hasMoreElements()) {
//得到每一个名字
String name = names.nextElement();
System.out.println(name+" = "+filterConfig.getInitParameter(name));
}
System.out.println("-----------end.....------------");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}

@Override
public void destroy() {
}
}

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
<filter>
<filter-name>myFilterConfig</filter-name>
<filter-class>com.clucky.filter.MyFilterConfig</filter-class>
<init-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/equip_employ_manage?serverTimezone=GMT</param-value>
</init-param>
<init-param>
<param-name>username</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>root</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>myFilterConfig</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
img
img

我们使用FilterConfig提供的方法就成功实现了功能,FilterConfig就是用来读取配置文件的。

FilterChain

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

img
img
img
img

我们查看类图,可以发现 FilterChain 就只有一个方法,其实这个方法就是用来对拦截进行放行的,如果有多个拦截器,那么就会继续调用下一个Filter进行拦截。doFilter 方法需要传入个参数,一个是 ServletRequest ,一个是 ServletResponse 参数,这个直接传入进行。

Tomcat 在调用过滤器时,默认就会传入 Request 和 Response,这个参数封装了请求和响应,我们直接使用就行。ServletResquest 和 ServletResponse 可以直接强转成 HttpServletRequest 和 HttpServletResponse,然后使用相应的方法。

将ServletRequest转成HttpServletRequest

img
img

应用实例

我们前面一直都是一个Filter,现在我们来配置2个Filter,通过FilterChain来进行多个过滤。

第一个 Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")
public class Filter01 implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("调用过滤器01对请求进行过滤~~~~");
//放行,如果还有过滤器,那么就执行下一个过滤器
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("调用过滤器01对响应进行过滤~~~~");
}

@Override
public void destroy() {
}
}

第二个Filterl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")
public class Filter02 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("调用过滤器02对请求进行过滤~~~~");
//放行,如果还有过滤器,那么就执行下一个过滤器
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("调用过滤器02对响应进行过滤~~~~");
}

@Override
public void destroy() {
}
}

启动服务器,访问8080,查看控制台输出。

img
img

我们可以看见Filter01先进行过滤,然后交给Filter02,然后访问资源,然后Filter02对响应进行过滤,然后Filter01对响应进行过滤。图示如下:

img
img

多个 Filter 的执行顺序

上面我们配置了2个过滤器,那么我们怎么知道那个过滤器先执行呢?

  • 如果我们是在web.xml中配置的过滤器,那么过滤器的执行顺序就是在web配置的顺序,配置在上面那么就会先执行。
  • 如果我们是使用@WebFilter进行配置的,那么执行顺序就是字符比较顺序来执行,例如有2个过滤器,一个是AFilter,一个是BFilter,那么AFilter就会先执行。
  • 如果注解和xml混用,那么在web.xml中配置的会先执行。