有些時候咱們須要在 Spring Boot Servlet Web 應用中聲明一些自定義的 Servlet Filter 來處理一些邏輯。好比簡單的權限系統、請求頭過濾、防止 XSS 攻擊等。本篇將講解如何在 Spring Boot 應用中聲明自定義 Servlet Filter 以及定義它們各自的做用域和順序。html
可能有人說聲明 Servlet Filter 不就是實現 Filter 接口嘛,沒有什麼好講的!是的這個沒錯,可是不少時候咱們並不想咱們聲明的 Filter 做用於所有的請求。甚至當一個請求通過多個 Filter 時須要按照既定的順序執行。接下來我會一一講解如何實現以上的功能。java
在 Spring Boot 中 只須要聲明一個實現 javax.servlet.Filter
接口的 Spring Bean 就能夠了。以下:web
@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
若是須要實現順序化,能夠藉助於 Spring 提供的 @Order
註解或者 Ordered
接口。這裏有一個坑:若是使用 @Order
註解必定要註解標註到具體的類上。
爲了方便 JavaConfig 風格的聲明。咱們能夠實現 OrderedFilter
接口,該接口是 Filter
接口和 Ordered
接口的複合體,最終上面的配置以下springboot
@Configuration public 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 。
這裏很是適合抽象一個基類來把該流程固定下來,留一個抽象方法做爲函數鉤子,只須要繼承基類實現該抽象方法鉤子就能夠了。 爲了保證順序執行基類咱們依然實現了 OrderedFilter
接口,咱們來定義基類:this
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:url
@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 章節 提供的方法就能夠了。spa
以上方式是咱們本身造的輪子。其實 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 。