点击上方蓝色“ 程序猿DD ”,选择“设为星标”
回复“ 资源 ”获取独家整理的学习资料!
作者 | 码农小胖哥
来源 | 公众号「码农小胖哥」
针对Java开发者的持续交付完整实施指南 | 内含福利
有些时候我们需要在 Spring Boot Servlet Web 应用中声明一些自定义的 Servlet Filter 来处理一些逻辑。比如简单的权限系统、请求头过滤、防止 XSS 攻击等。本篇将讲解如何在 Spring Boot 应用中声明自定义 Servlet Filter 以及定义它们各自的作用域和顺序。
可能有人说声明 Servlet Filter 不就是实现 Filter 接口嘛,没有什么好讲的!是的这个没错,但是很多时候我们并不想我们声明的 Filter 作用于全部的请求。甚至当一个请求经过多个 Filter 时需要按照既定的顺序执行。接下来我会一一讲解如何实现以上的功能。
在 Spring Boot 中 只需要声明一个实现 javax.servlet.Filter
接口的 Spring Bean
就可以了。如下:
@Configuration
public class FilterConfig {
@Bean
public Filter requestFilter() {
return (request, response, chain) -> {
//todo your business
};
}
@Bean
public Filter responseFilter() {
return (request, response, chain) -> {
//todo your business
};
}
}
非常简单不是吗?但是这种方式无法保证顺序,而且作用于所有的请求,即拦截的 Ant
规则为 /*
。所以需要我们改进
如果需要实现顺序化,可以借助于 Spring
提供的 @Order
注解或者 Ordered
接口。这里有一个坑:如果使用 @Order
注解一定要注解标注到具体的类上。为了方便 JavaConfig
风格的声明。我们可以实现 OrderedFilter
接口,该接口是 Filter
接口和 Ordered
接口的复合体,最终上面的配置如下:
@Configuration
public class FilterConfig {
@Bean
public OrderedFilter responseFilter() {
return new ResponseFilter("/foo/*");
}
@Bean
public OrderedFilter requestFilter() {
return new RequestFilter("/foo/*");
}
}
Filter 执行的规则是 数字越小越先执行 。跟之前 Bean 实例化的优先级是一致的。
实现了顺序化之后我们来看看如何实现自定义 Filter 的作用域。我们先说一下思路:
通过 ServletRequest
对象来获取请求的 URI
,然后对 URI 进行 ANT 风格匹配,关于 ANT 风格可以参考我的这一篇 文章
[1]
。匹配通过执行具体的逻辑,否则跳过该 Filter
。
这里非常适合抽象一个基类来把该流程固定下来,留一个抽象方法作为函数钩子,只需要继承基类实现该抽象方法钩子就可以了。为了保证顺序执行基类我们依然实现了 OrderedFilter
接口,我们来定义基类:
package cn.felord.springboot.filters;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
/**
* The type Abstract filter bean.
*
* @author Felordcn
* @since 11 :19
*/
public abstract class AbstractFilterBean implements OrderedFilter {
private Set<String> urlPatterns = new LinkedHashSet<>();
public AbstractFilterBean(String... urlPatterns) {
Collections.addAll(this.urlPatterns, urlPatterns);
}
/**
* 各自逻辑的函数钩子
*
* @param request the request
* @param response the response
*/
public abstract void internalHandler(ServletRequest request, ServletResponse response);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 进行ant匹配 true 执行具体的拦截逻辑 false 跳过
if (this.antMatch(request)) {
this.internalHandler(request, response);
}
chain.doFilter(request, response);
}
private boolean antMatch(ServletRequest request) {
Set<String> urlPatterns = getUrlPatterns();
if (!CollectionUtils.isEmpty(urlPatterns)) {
//进行Ant匹配处理
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String uri = httpServletRequest.getRequestURI();
Optional<String> any = urlPatterns.stream().filter(s -> {
AntPathMatcher antPathMatcher = new AntPathMatcher();
return antPathMatcher.match(s, uri);
}).findAny();
return any.isPresent();
}
// 如果 没有元素 表示全部匹配
return true;
}
public Set<String> getUrlPatterns() {
return urlPatterns;
}
}
我们来实现一个具体的 Filter 逻辑,打印请求的 URI :
@Slf4j
public class RequestFilter extends AbstractFilterBean {
public RequestFilter(String... urlPatterns) {
super(urlPatterns);
}
@Override
public void internalHandler(ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
log.info("request from {}", httpServletRequest.getRequestURI());
}
@Override
public int getOrder() {
// 定义自己的优先级
return 0;
}
}
然后定义好其 urlPatterns
并将其注册到 Spring IoC 容器中就行了,如果有多个而且希望按照一定的顺序执行,遵循 2.2 章节
提供的方法就可以了。
以上方式是我们自己造的轮子。其实 Spring Boot 还提供了 Filter 注册机制来实现顺序执行和声明作用域。我们上面的逻辑可以改为:
package cn.felord.springboot.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 使用 Spring Boot 提供的注册机制
*
* @author Felordcn
* @since 14:27
**/
@Configuration
@Slf4j
public class SpringFilterRegistrationConfig {
@Bean
public FilterRegistrationBean<Filter> responseFilter() {
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setName("responseFilter");
registrationBean.setOrder(2);
registrationBean.setFilter((request, response, chain) -> {
HttpServletResponse servletResponse = (HttpServletResponse) response;
log.info("response status {}", servletResponse.getStatus());
chain.doFilter(request,response);
});
registrationBean.addUrlPatterns("/foo/*");
return registrationBean;
}
@Bean
public FilterRegistrationBean<Filter> requestFilter() {
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setName("requestFilter");
registrationBean.setOrder(1);
registrationBean.setFilter((request, response, chain) -> {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
log.info("request from {}", httpServletRequest.getRequestURI());
chain.doFilter(request,response);
});
registrationBean.addUrlPatterns("/foo/*");
return registrationBean;
}
}
FilterRegistrationBean
与 Filter
之间是一对一关系。 FilterRegistrationBean
需要调用其 setName(String name)
为其声明唯一名称,否则只有第一个注册成功的有效。 setOrder(int order)
方法进行设置。
我们在本文中通过自定义和 Spring Boot
提供的两种方式实现了使用自定义 Filter
,虽然 Spring Boot 提供的方式更加方便一些,但是自定义的方式更能体现你对面向对象理解和提高你的抽象能力。希望多多关注,与往常一样。通过关注公众号: Felordcn
回复 f01
获取本文 DEMO
。
本文通过OpenWrite的Markdown转换工具发布
关注我,回复“ 加群 ” 加入各种主题讨论群
HttpClient 连接池设置不当引发的一次雪崩
为什么程序员要了解业务?
Python 2 宣布正式退休,Python 3 时代到来!
8种经常被忽视的SQL错误用法
昨天你用的 YYYY-MM-dd 被捶了吗?
点击“ 阅读原文 ” 免费 领取价值199元学习大礼包