JaveWeb—过滤器(Filter)

介绍

概念

客户端发送请求(request)至服务端,在request到达servlet之前,过滤器(Filter)可以对请求进行过滤,作预处理;servlet返回响应(response)后,过滤器还可以在response返回给客户端前,对response作处理。

作用

可以实现URL级别的权限控制、黑名单过滤、防范sql注入攻击等。

Filter接口

Servlet API提供了一个Filter接口,编写的过滤器必须实现该接口。

Filter的生命周期

Filter的生命周期即Filter对象从被创建到被销毁的过程。
Filter的生命周期主要体现在以下方法中:

  • 构造方法
    • 服务器一启动就被执行,说明服务器一启动就创建Filter对象
    • 在整个生命周期过程中只执行一次,说明Filter是单例的
  • init()方法
    public voic init(FilterConfig filterConfig) throws ServletException {}
    • 服务器一启动就执行,对过滤器做一些初始化的操作
    • 在整个生命周期过程中只执行一次
    • 该方法中有一个参数:FilterConfig filterConfig
  • doFilter()方法
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException.ServletException {}
    • 在请求被拦截的资源时被调用,在这个方法中可以对request和response做处理
    • 在整个生命周期过程中可以被调用多次,每次请求被拦截时都会调用
    • 该方法中有三个参数:ServletRequest request, ServletResponse response, FilterChain chain
      • request和response和Servlet中的作用一样
      • chain用来放行请求
  • destroy()方法
    public void destroy() {}
    • 在服务器关闭时执行
    • 在整个生命周期过程中只被执行一次

FilterConfig

  1. 与普通的Servlet程序一样,Filter程序也很可能需要访问Servlet容器。Servlet规范将代表ServletContext对象和Filter的配置参数信息都封装到一个称为FilterConfig的对象中。

  2. FilterConfig接口则用于定义FilterConfig对象应该对外提供的方法,以便在Filter程序中可以调用这些方法来获取ServletContext对象,以及获取在web.xml文件中为Filter设置的fillter名称和初始化参数。

  1. FilterConfig接口定义的各个方法:
    • String getFilterName():获取Filter的名称。即web.xml文件中过滤器的<filter-name>元素的设置值。
    • String getInitParameter(String name): 返回在web.xml文件中为Filter所设置的某个名称的初始化的参数值。如果不存在返回null.
    • Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
    • public ServletContext getServletContext():返回Servlet上下文对象的引用。

FilterChain

  1. 在一个 Web 应用程序中可以注册多个 Filter 程序,每个 Filter 程序都可以对一个或一组 Servlet 程序进行拦截。如果有多个 Filter 程序都对某个 Servlet 程序的访问过程进行拦截,当针对该 Servlet 的访问请求到达时,Web 容器将把这多个 Filter 程序组合成一个 FilterChain(过滤器链)。
  2. Filter 链中的各个 Filter 的拦截顺序与它们在 web.xml 文件中的<filter-mapping>配置顺序一致,上一个 Filter.doFilter 方法中调用 FilterChain.doFilter 方法将激活下一个 Filter的doFilter 方法,最后一个 Filter.doFilter 方法中调用的 FilterChain.doFilter 方法将激活目标 Servlet 的 service 方法。
  3. 只要 Filter 链中任意一个 Filter 没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法都不会被执行。

重要知识点

多个过滤器的执行顺序

多个过滤器的执行顺序由web.xml中配置的<filter-mapping>决定的,在前的先拦截,在后的后拦截,但是<filter-mapping>一定要声明在<filter>之后,即要先注册Filter再映射Filter。

<url-pattern>的配置规则

  1. 精确匹配:需设置一个完整的路径
    例如:<url-pattern>/index.jsp</url-pattern>,则只有访问index.jsp的时候才拦截请求
  1. 模糊匹配:通过通配符表示一系列路径
    • 前缀匹配。例如:<url-pattern>/pages/*</url-pattern>,则只有访问pages目录下的资源就会拦截请求
    • 后缀匹配。例如:<url-pattern>*.jsp</url-pattern>,则拦截所有的jsp页面

代码示例

  1. 项目结构
  1. 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
package com.lzumetal.javaweb.filter;

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

public class FilterTest01 implements Filter {

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

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
System.out.println("FilterTest01 执行前---");
filterChain.doFilter(request, response); //放行
System.out.println("FilterTest01 执行后---");
}

@Override
public void destroy() {

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.lzumetal.javaweb.filter;

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

public class FilterTest02 implements Filter {

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

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
System.out.println("FilterTest02 执行前---");
filterChain.doFilter(request, response); //放行
System.out.println("FilterTest02 执行后---");
}

@Override
public void destroy() {

}
}
  1. Servlet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.lzumetal.javaweb.servlet;

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;

@WebServlet("/helloServlet")
public class HelloServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("执行HelloServlet的目标方法");
}
}
  1. web.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
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>


<!-- 注册Filter -->
<filter>
<filter-name>FilterTest01</filter-name>
<filter-class>com.lzumetal.javaweb.filter.FilterTest01</filter-class>
</filter>
<!-- 注册Filter -->
<filter>
<filter-name>FilterTest02</filter-name>
<filter-class>com.lzumetal.javaweb.filter.FilterTest02</filter-class>
</filter>

<!-- 映射Filter -->
<filter-mapping>
<filter-name>FilterTest02</filter-name>
<!-- 映射要拦截的路径,这里配置为过滤所有 -->
<url-pattern>/*</url-pattern>
</filter-mapping>


<!-- 映射Filter -->
<filter-mapping>
<filter-name>FilterTest01</filter-name>
<!-- 映射要拦截的路径,这里配置为过滤所有 -->
<url-pattern>/*</url-pattern>
</filter-mapping>


</web-app>
  1. 启动项目,在浏览器中访问:http://localhost:8080/javaweb_war/helloServlet ,可以看到控制台上的输出为:
1
2
3
4
5
FilterTest02 执行前---
FilterTest01 执行前---
执行HelloServlet的目标方法
FilterTest01 执行后---
FilterTest02 执行后---
  1. 从上面打印的日志还可以得出一个结论:过滤器的排序靠前的,filterChain.doFilter()前的代码先执行,filterChain.doFilter()后的代码后执行

SpringBoot项目中添加自定义Filter

在SpringBoot项目中如果要使用Filter,则需要

  1. 实现 javax.servlet.Filter。
  2. 自定义Filter上添加注解@WebFilterjavax.servlet.annotation.WebFilter);或者通过@Bean注解配置的方式注入自定义FilterBean。

@WebFilter注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.lzumetal.springboot.filter;

import com.lzumetal.springboot.utils.IPUtil;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j
@WebFilter(urlPatterns = "/*", filterName = "blackIpFilter") //urlPatterns 设置过滤器要过滤的URL规则,filterName 设置过滤器的名称。
public class BlackIpFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String ipAddr = IPUtil.getIpAddr(request);
String requestURI = request.getRequestURI();
log.info("{}|访问ip|{}", requestURI, ipAddr);
filterChain.doFilter(servletRequest, servletResponse);
}

}

@Bean注解注入

如果使用@bean注解,则不再需要在Filter上加@WebFilter注解。

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
package com.lzumetal.springboot.filter.config;

import com.lzumetal.springboot.filter.BlackIpFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class FilterConfiguration {


@Bean
public FilterRegistrationBean blackIpFilter() {
FilterRegistrationBean<BlackIpFilter> filterRegistrationBean = new FilterRegistrationBean<>();
BlackIpFilter blackIpFilter = new BlackIpFilter();
filterRegistrationBean.setFilter(blackIpFilter);
filterRegistrationBean.addUrlPatterns("/*");//配置过滤规则
filterRegistrationBean.addInitParameter("name","hahaha");//设置init参数
filterRegistrationBean.setName("blackIpFilter");//设置过滤器名称
filterRegistrationBean.setOrder(1);//执行次序
return filterRegistrationBean;
}

}

设置Filter执行顺序

如果要设置自定义Filter的执行顺序,只能使用@Bean注解注入的方式配置Filter,并且设置FilterRegistrationBean的order属性。

1
2
3
4
5
6
7
8
9
10
11
@Slf4j
public class RoleFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
log.info("获取用户角色...");
filterChain.doFilter(servletRequest, servletResponse);
}

}

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
package com.lzumetal.springboot.filter.config;

import com.lzumetal.springboot.filter.BlackIpFilter;
import com.lzumetal.springboot.filter.RoleFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class FilterConfiguration {


@Bean
public FilterRegistrationBean blackIpFilter() {
FilterRegistrationBean<BlackIpFilter> filterRegistrationBean = new FilterRegistrationBean<>();
BlackIpFilter blackIpFilter = new BlackIpFilter();
filterRegistrationBean.setFilter(blackIpFilter);
filterRegistrationBean.addUrlPatterns("/*");//配置过滤规则
filterRegistrationBean.addInitParameter("name","hahaha");//设置init参数
filterRegistrationBean.setName("blackIpFilter");//设置过滤器名称
filterRegistrationBean.setOrder(2);//执行次序
return filterRegistrationBean;
}

@Bean
public FilterRegistrationBean roleFilter() {
FilterRegistrationBean<RoleFilter> filterRegistrationBean = new FilterRegistrationBean<>();
RoleFilter roleFilter = new RoleFilter();
filterRegistrationBean.setFilter(roleFilter);
filterRegistrationBean.addUrlPatterns("/*");//配置过滤规则
filterRegistrationBean.addInitParameter("key","value001");//设置init参数
filterRegistrationBean.setName("roleFilter");//设置过滤器名称
filterRegistrationBean.setOrder(1);//执行次序
return filterRegistrationBean;
}

}

执行结果:

1
2
INFO 15800 --- [nio-8080-exec-1] c.lzumetal.springboot.filter.RoleFilter  : 获取用户角色...
INFO 15800 --- [nio-8080-exec-1] c.l.springboot.filter.BlackIpFilter : /testFilter|访问ip|0:0:0:0:0:0:0:1

------ 本文完 ------