0%

基于SpringBoot的Servlet Filter实践

Filter作为Servlet中的基础组件,位于javax.servlet包下。这里介绍如何在SpringBoot的环境下使用Filter

abstract.jpg

概述

Filter作为Servlet中的组件,其可以对进入Web容器的请求进行预处理,然后再将请求转交给Servlet进行处理;当Servlet处理完毕生成响应后,Filter可以再次对响应结果进行后处理,最后再返回给客户端。故其常用于校验请求的参数、设置请求/响应的Header、修改请求/响应的内容等场景当中。Filter是通过回调实现的。事实上,我们可以同时使用多个Filter。其采用责任链的设计模式以组成链式结构,使得请求在到达Servlet之前会在这个链上依次处理、传递

figure 1.jpg

实践

基于@Component注解

事实上,我们只需实现Filter接口中定义的方法,并通过@Component注解将其交由Spring管理即可。从下述代码不难看出,Filter接口定义三个方法,分别应用于初始化过滤器、销毁过滤器、处理请求。此外我们还可以使用@Order注解来设置Filter的顺序。此种方式虽然简单,但会拦截所有请求,无法拦截指定URL的请求。故我们需要在doFilter方法中筛选出我们需要拦截的请求

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
45
46
47
48
49
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Order(5) // 数字越小,优先级越高。该注解默认为Integer.MAX_VALUE
@Component
@Slf4j
public class MyFilter1 implements Filter {

/**
*
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter 1: 初始化过滤器");
}

@Override
public void destroy() {
log.info("MyFilter 1: 销毁过滤器 ");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 过滤器前置处理
log.info("MyFilter 1: 前置处理");
HttpServletRequest req = (HttpServletRequest) request;
String uri = req.getRequestURI();
String name = req.getParameter("name");

// 转发请求到下一个过滤器
chain.doFilter(request, response);

// 过滤器后置处理
log.info("MyFilter 1: 后置处理");
if( uri.endsWith("test1") && "Aaron".equals(name) ) {
// 修改响应数据
ServletOutputStream outputStream = response.getOutputStream();
String str = " Bye~~~~";
outputStream.write(str.getBytes("utf-8"));
outputStream.flush();
}
}
}

测试结果如下所示

figure 2.jpg

基于@WebFilter、@ServletComponentScan注解

我们只需在我们实现的Filter上添加@WebFilter注解,然后通过该注解指定Filter需要拦截的URL。但该方式下无法指定Filter的执行顺序,即使用@Order注解无效

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
45
46
47
48
49
50
51
52
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

// 该Filter url匹配规则:
// 1. 全路径匹配:/testFilter/test2
// 2. 部分路径匹配: /hello/*
@WebFilter(urlPatterns = {"/testFilter/test2", "/hello/*"} )
@Slf4j
public class MyFilter2 implements Filter {

private String minAge;

@Override
public void destroy() {
log.info("MyFilter 2: 销毁过滤器 ");
}

/**
*
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter 2: 初始化过滤器");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 过滤器前置处理
log.info("MyFilter 2: 前置处理");
HttpServletRequest req = (HttpServletRequest) request;
String id = req.getParameter("id");

// 转发请求到下一个过滤器
chain.doFilter(request, response);

// 过滤器后置处理
log.info("MyFilter 2: 后置处理");
if( id!=null && Integer.parseInt(id)<18 ) {
// 修改响应数据
ServletOutputStream outputStream = response.getOutputStream();
String str = " <Tag: 未成年>";
outputStream.write(str.getBytes("utf-8"));
outputStream.flush();
}
}
}

然后在启动类上增加@ServletComponentScan注解即可

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan // 扫描@WebFilter注解自动注册
@SpringBootApplication
public class SpringBoot1Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(SpringBoot1Application.class);
app.run(args);
}
}

效果如下所示

figure 3.jpg

基于Java Config类

通过Java Config类通过FilterRegistrationBean配置Filter,此种方式功能强大、配置灵活。下面即是一个普通的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
45
46
47
48
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Slf4j
public class MyFilter3 implements Filter {

private Integer minAge;

/**
*
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String minAgeStr = filterConfig.getInitParameter("minAge");
minAge = Integer.valueOf(minAgeStr);
log.info("MyFilter 3: 初始化过滤器, minAge: {}", minAge);
}

@Override
public void destroy() {
log.info("MyFilter 3: 销毁过滤器 ");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 过滤器前置处理
log.info("MyFilter 3: 前置处理");
HttpServletRequest req = (HttpServletRequest) request;
String age = req.getParameter("age");

// 转发请求到下一个过滤器
chain.doFilter(request, response);

// 过滤器后置处理
log.info("MyFilter 3: 后置处理");
if( age!=null && Integer.parseInt(age)>=minAge ) {
// 修改响应数据
ServletOutputStream outputStream = response.getOutputStream();
String str = " <Tag: 高寿人士>";
outputStream.write(str.getBytes("utf-8"));
outputStream.flush();
}
}
}

现在我们通过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
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyFilterConfig {

@Value("${my.filter3.minAge}")
private String minAge;

@Bean
public FilterRegistrationBean registerMyFilter3() {
FilterRegistrationBean<MyFilter3> filterRegistration = new FilterRegistrationBean<>();
filterRegistration.setName("myFilter3");
// 数字越小,优先级越高
filterRegistration.setOrder(3);
// 该Filter url匹配规则
filterRegistration.addUrlPatterns( "/testFilter/test3" );
filterRegistration.addUrlPatterns( "/hello/*" );
filterRegistration.addInitParameter("minAge", minAge);
filterRegistration.setFilter( new MyFilter3() );
return filterRegistration;
}

}

其中,在项目的application.properties中我们添加了如下自定义参数配置项

1
2
# Filter 3 最小年龄阈值
my.filter3.minAge=70

测试效果如下所示

figure 4.jpg

请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝