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。有了这个确定的类型,很多链路就变得清晰了。可以看到最后是丢给线程池来处理了。

springboot 用户注册非空判断 springboot filter注册_ide