有些时候咱们须要在 Spring Boot Servlet Web 应用中声明一些自定义的 Servlet Filter 来处理一些逻辑。好比简单的权限系统、请求头过滤、防止 XSS 攻击等。本篇将讲解如何在 Spring Boot 应用中声明自定义 Servlet Filter 以及定义它们各自的做用域和顺序。 java
可能有人说声明 Servlet Filter 不就是实现 Filter 接口嘛,没有什么好讲的!是的这个没错,可是不少时候咱们并不想咱们声明的 Filter 做用于所有的请求。甚至当一个请求通过多个 Filter 时须要按照既定的顺序执行。接下来我会一一讲解如何实现以上的功能。 web
在 Spring Boot 中 只须要声明一个实现 javax.servlet.Filter
接口的 Spring Bean 就能够了。以下: spring
@Configurationpublic 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 规则为 /*
。因此须要咱们改进 springboot
若是须要实现顺序化,能够借助于 Spring 提供的 @Order
注解或者 Ordered
接口。这里有一个坑:若是使用 @Order
注解必定要注解标注到具体的类上。 为了方便 JavaConfig 风格的声明。咱们能够实现 OrderedFilter
接口,该接口是 Filter
接口和 Ordered
接口的复合体,最终上面的配置以下 app
@Configurationpublic class FilterConfig { @Bean public OrderedFilter responseFilter() { return new ResponseFilter("/foo/*"); } @Bean public OrderedFilter requestFilter() { return new RequestFilter("/foo/*"); } }
Filter 执行的规则是 数字越小越先执行 。跟以前 Bean 实例化的优先级是一致的。 ide
实现了顺序化以后咱们来看看如何实现自定义 Filter 的做用域。咱们先说一下思路: 函数
经过
ServletRequest
对象来获取请求的URI
,而后对 URI 进行 ANT 风格匹配,关于 ANT 风格能够参考个人这一篇文章。 匹配经过执行具体的逻辑,不然跳过该 Filter 。 this
这里很是适合抽象一个基类来把该流程固定下来,留一个抽象方法做为函数钩子,只须要继承基类实现该抽象方法钩子就能够了。 为了保证顺序执行基类咱们依然实现了 OrderedFilter
接口,咱们来定义基类: url
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: spa
@Slf4jpublic 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@Slf4jpublic 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 提供的方式更加方便一些,可是自定义的方式更能体现你对面向对象理解和提升你的抽象能力。