SpringBoot中是可以自行注册Filter的,在SpringBoot中有几种注册Filter的方式。
一、通过org.springframework.boot.web.servlet.FilterRegistrationBean注册
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new MyFilter2());
bean.addUrlPatterns("/*");
return bean;
}
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new MyFilter1());
bean.addUrlPatterns("/*");
return bean;
}
}
public class MyFilter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
public class MyFilter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
二、通过 @WebFilter + @ServletComponentScan 注解实现
@Order(1)
@WebFilter(urlPatterns = "*.json", filterName = "reqResFilter")
public class ReqResFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
@SpringBootApplication
@ServletComponentScan(basePackages = "com.dm.base.filter")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
关于这两个注解有如下说明:
When using an embedded container, automatic registration of @WebServlet, @WebFilter, and @WebListener annotated classes can be enabled using @ServletComponentScan.
使用嵌入式容器时,可以使用@ServletComponentScan启用@WebServlet,@ WebFilter和@WebListener注释类的自动注册。
@ServletComponentScan will have no effect in a standalone container, where the container’s built-in discovery mechanisms will be used instead.
如果使用外置容器的话,就不需要使用这条注解,因为外置容器的内置发现机制将会替代这个注解的效果。
上面有使用@Order注解指定一个int值,越小越先执行。很多博客文章都是这么说的,但你真正的试了吗?真的可以使用这个注解指定顺序吗?答案是否定的。经过测试,发现 @Order 注解指定 int 值没有起作用,是无效的。为啥?因为看源码发现 @WebFilter 修饰的过滤器在加载时,没有使用 @Order 注解,而是使用的类名来实现自定义Filter顺序,所以这种方式下想定义Filter的顺序,就必须限定 Filter 的类名,比如刚才那个 Filter 叫 ReqResFilter ,加入我们现在新写了一个 Filter 叫 AlibabaFilter ,那么顺序就是 AlibabaFilter > ReqResFilter 。所以这种方式虽然实现起来简单,只需要注解,但自定义顺序就必须要限定类名,使用类名达到排序效果了。
三、Servlet对Filter处理
上面说的都是SpringBoot对Filter提供的使用方式,但是对于Servlet容器来讲,处理方式应该是统一的,毕竟SpringBoot最终还是会调用Servlet容器的接口来实现。首先来说,Filter处理逻辑是在DispatcherServlet之前的,也就是说,如果到了DispatcherServlet了,说明所有Filter逻辑已经处理完毕了。
spring boot默认的几个filter,按先后顺序分别是,可以看到他们的顺序都是依赖一个int类型的order变量决定的,数值越小,优先级越高。
// 编码过滤器
public class OrderedCharacterEncodingFilter extends CharacterEncodingFilter implements OrderedFilter {
// -2147483648
private int order = Ordered.HIGHEST_PRECEDENCE;
...
}
// Form内容处理过滤器
public class OrderedFormContentFilter extends FormContentFilter implements OrderedFilter {
// -9900
private int order = DEFAULT_ORDER;
}
// 请求的上下文环境处理了过滤器
public class OrderedRequestContextFilter extends RequestContextFilter implements OrderedFilter {
// -105
private int order = REQUEST_WRAPPER_FILTER_MAX_ORDER - 105;
}
那我们自定义的filter顺序由谁决定?可以探索一下,首先我们知道,顺序是按照filterChain变量里的filter依次执行的,所以我们需要知道filterChain是如何创建的?每次请求都会重新生成一个filterChain。看看如下方法:
org.apache.catalina.core.ApplicationFilterFactory#createFilterChain(...)
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
// If there is no servlet to execute, return null
if (servlet == null)
return null;
// Create and initialize a filter chain object
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return filterChain;
// Acquire the information we will need to match filter mappings
DispatcherType dispatcher =
(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
String requestPath = null;
Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null){
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMap, requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Add filters that match on servlet name second
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMap, servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Return the completed filter chain
return filterChain;
}
我们需要关注的关键点是这句代码(32行):FilterMap filterMaps[] = context.findFilterMaps();这个数组决定了filter的顺序,这里的context是org.apache.catalina.core.StandardContext,相关的方法有2个,但是这里有关的是addFilterMapBefore(...)。一个FilterMap对应一个Filter,这个方法会被调用多次的。
StandardContext#addFilterMap(),StandardContext#addFilterMapBefore()
@Override
public void addFilterMapBefore(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.addBefore(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}
接下来我们需要看看谁在调用addFilterMapBefore(...),是下面方法
org.apache.catalina.core.ApplicationFilterRegistration#addMappingForUrlPatterns(...)。这个方法生成FilterMap并添加,所以关键点是谁调用这个方法以及其调用顺序。
@Override
public void addMappingForUrlPatterns(
EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
String... urlPatterns) {
FilterMap filterMap = new FilterMap();
filterMap.setFilterName(filterDef.getFilterName());
if (dispatcherTypes != null) {
for (DispatcherType dispatcherType : dispatcherTypes) {
filterMap.setDispatcher(dispatcherType.name());
}
}
if (urlPatterns != null) {
// % decoded (if necessary) using UTF-8
for (String urlPattern : urlPatterns) {
filterMap.addURLPattern(urlPattern);
}
if (isMatchAfter) {
context.addFilterMap(filterMap);
} else {
context.addFilterMapBefore(filterMap);
}
}
// else error?
}
找到调用地方如下org.springframework.boot.web.servlet.AbstractFilterRegistrationBean#configure(...),这里就到了springboot控制了,他是衔接springboot和servlet的地方,需要注意下这个衔接点。
@Override
protected void configure(FilterRegistration.Dynamic registration) {
super.configure(registration);
EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
if (dispatcherTypes == null) {
dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
}
Set<String> servletNames = new LinkedHashSet<>();
for (ServletRegistrationBean<?> servletRegistrationBean : this.servletRegistrationBeans) {
servletNames.add(servletRegistrationBean.getServletName());
}
servletNames.addAll(this.servletNames);
if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);
}
else {
// 这里调用
if (!servletNames.isEmpty()) {
registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(servletNames));
}
if (!this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
StringUtils.toStringArray(this.urlPatterns));
}
}
}
也就是说这个configure(..)方法也会被调用多次,继续追查看看谁在调用他。
org.springframework.boot.web.servlet.DynamicRegistrationBean#register(...)
@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
return;
}
// 这里调用
configure(registration);
}
接着看调用链org.springframework.boot.web.servlet.RegistrationBean#onStartup(...)
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
return;
}
register(description, servletContext);
}
接着看调用链org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize(...),这个方法只调用一次,下面的filter通过遍历生成。
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
// 这里遍历调用
beans.onStartup(servletContext);
}
}
也就是说关键点在org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getServletContextInitializerBeans()方法上
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
继续追调用org.springframework.boot.web.servlet.ServletContextInitializerBeans构造函数
@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
this.initializers = new LinkedMultiValueMap<>();
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
// 排序就在这里
List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);
}
所以现在知道这个排序是怎么决定了吧,也即@Order注解,注意,如果这个Bean不是受Spring管理的就不会生效的。不生效的话默认值是2147483647,这样顺序会在最后面。
现在我们顺着来捋一下这个关系,其中有一个关键点需要明确的是,通常情况下,我们springboot应用是servlet容器的,所以实例化的ApplicationContext类型是:org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext。有了这个确定的类型,很多链路就变得清晰了。可以看到最后是丢给线程池来处理了。