首先我来啰嗦几句。本文是使用@WebFilter
注解来装配过滤器的,与此同时,在启动类上需要加上注解@ServletComponentScan
。这样,容器启动的时候,才能将自定义的过滤器注入到容器中。
当然,还有另外一种做法,你可以自定义FilterRegistrationBean
类(这种方案正好是本文对于问题的一种解决方案)。两种方案都行。本文针对第一种方案来说。
1.首先我们可以自定义一个时间过滤器,用来计算接口的执行时间的。同时希望他是第一个被执行的。
@WebFilter
// 数字越小,优先级越大
@Order(1)
public class TimeFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("先执行:接口执行时间: 2s");
chain.doFilter(request, response);
}
}
2.定义一个检查的过滤器,并模拟耗时需要1秒钟:
@WebFilter
@Order(2)
public class CheckFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
Thread.sleep(1000);
System.out.println("~~~~~~~~~~~~~后执行:检查通过~~~~~~~~~~~~~~~~~~");
chain.doFilter(request, response);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.然后随便自定义一个Controller
类,并访问下自定义的接口:
@RestController
public class MyController {
@PostMapping("/hello")
public User hello(@RequestBody User user){
return user;
}
}
4.访问接口后,控制台输出如下:
可见,程序运行的结果和我们预想的结果是相反的,也就是说我们定义的Order
顺序并没有生效?
我们来看下过滤器当中的一个重要函数:chain.doFilter(request, response);
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// ....
internalDoFilter(request,response);
}
↓↓↓↓↓↓
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// n 为过滤器的总数,每执行完一个过滤器,pos变量就会+1,然后取下一个过滤器继续执行,即链式调用
if (pos < n) {
// 这里就是我们要执行的过滤器,顺序也由它做决定
ApplicationFilterConfig filterConfig = filters[pos++];
try {
// 获取y
Filter filter = filterConfig.getFilter();
// ... 省略了 如果Spring开启了Security功能下的逻辑
// this指的是ApplicationFilterChain实例,即调用方需要在过滤器中显式地调用doFilter函数,才能完成整个链路的调用。递归调用了
filter.doFilter(request, response, this);
}
// catch / finally
return;
}
// 代码运行到这里的时候,过滤器链已经全部过了一遍了。即pos>=n
try {
// ... 省略了 如果Spring开启了Security功能下的逻辑
// 过滤器走完,那么就需要走真正地逻辑处理了。
servlet.service(request, response);
}
// catch / finally
}
}
我们来看下调试流程:
从上面我们可以得出这么几个结论:
filters
对象中的过滤器顺序。filters
属性就是用来保存调用链中的所有过滤器。servlet.service(request, response);
那么可想而知,问题出在filters
属性的赋值上。
Spring中有
这么一个类ApplicationFilterFactory
,他就是创建调用链的一个类。具体函数在于:
public final class ApplicationFilterFactory {
public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) {
// ...
// 从应用上下文中获取相关的过滤器
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// ...
return filterChain;
}
}
看图:
我们来看下StandardContext.findFilterMaps()
:
@Override
public FilterMap[] findFilterMaps() {
return filterMaps.asArray();
}
到这里为止,我们发现过滤器的调用顺序实际上由StandardContext.filterMaps
属性来做决定。 那么看下这个filterMaps
属性被谁引用赋值了?
从名字上来看,我们应该先去查看addBefore()
这个函数,那么定位到这段代码:
@Override
public void addFilterMapBefore(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.addBefore(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}
我们打个断点,调试一下看下调用栈:
这里看到了一个熟悉的身影:ServletWebServerApplicationContext
,我在@WebFilter注解装配的过滤器无法被@Autowired自动注入?这篇文章里面讲到过,被@WebFilter
修饰的过滤器,实际上其类型是一种InnerBean
,是在容器加载FilterRegistrationBean
的时候被自动装配的。其中,入口函数也包括了ServletWebServerApplicationContext
,我们来看下相关的函数:
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
}
再调试(重启项目,这段代码是容器启动的时候执行的)看下:
我们确信,过滤器的调用顺序由getServletContextInitializerBeans()
来做决定,那么我们继续深挖:
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
↓↓↓↓↓看下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);
}
调试结果如下:
也就是说,过滤器的执行顺序依赖于ServletContextInitializerBeans.sortedList
。而这个集合的对象又来自于initializers
属性:
其中,对于sort
排序的规则,大致如下:
Ordered
接口,若有,则调用getOrder()
获取值。findOrder()
获取@Order
注解属性值。那么我们再看下这些排序对象从何而来,查看initializers
属性的引用之后我们发现,构造函数中调用的addServletContextInitializerBeans
这个函数,会往initializers
属性中添加对象,。
public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {
@SafeVarargs
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
// 针对@WebFilter注解的Bean
addServletContextInitializerBeans(beanFactory);
}
↓↓↓↓↓↓
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,
initializerType)) {
addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
}
}
}
↓↓↓↓↓↓
private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
ListableBeanFactory beanFactory) {
if (initializer instanceof ServletRegistrationBean) {
Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();
addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
}
else if (initializer instanceof FilterRegistrationBean) {
Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
}
// ...
}
}
我们关注第二个if
分支,这里加入的是FilterRegistrationBean
类型的Bean
。他是
ServletContextInitializer
的一个子类:
这里面做个小总结就是:
过滤器链中的过滤器来源于ServletContextInitializerBeans.initializers
。
而initializers
属性的赋值又依赖于FilterRegistrationBean
这个对象的装配。、
@WebFIlter
修饰过的类通过加载FilterRegistrationBean
的时候被注入。
FilterRegistrationBean
在WebFilterHandler.doHandle()
中构建。
但是问题在于,包装FilterRegistrationBean
类的BeanDefinition
的时候,并没有对Order
进行相关的指定。同时我们再看下上面的类关系图,我们发现FilterRegistrationBean
的父类RegistrationBean
实现了Ordered
接口。但是上述代码中却没有对这个值进行填充赋值。 看代码:
@Override
public void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,
BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterRegistrationBean.class);
builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes));
builder.addPropertyValue("filter", beanDefinition);
builder.addPropertyValue("initParameters", extractInitParameters(attributes));
String name = determineName(attributes, beanDefinition);
builder.addPropertyValue("name", name);
builder.addPropertyValue("servletNames", attributes.get("servletNames"));
builder.addPropertyValue("urlPatterns", extractUrlPatterns(attributes));
registry.registerBeanDefinition(name, builder.getBeanDefinition());
}
根据上述的的描述,我们知道,过滤器链中的顺序的加载依赖:
第一步骤:项目启动,会执行ServletWebServerApplicationContext.selfInitialize()
函数。此时会加载FilterRegistrationBean
类型的Bean
。
第二步骤:此时会触发ServletContextInitializerBeans
这个类的构造函数执行。
addServletContextInitializerBeans()
函数,按照顺序处理ServletContextInitializer
类型的Bean
。FilterRegistrationBean
是ServletContextInitializer
的一个子类。因此第二步中加载的实际上是FilterRegistrationBean
。FilterRegistrationBean
最终又实现了Ordered
接口。相关属性存在于其父类RegistrationBean
中。即order
属性。第三步骤:ServletContextInitializerBeans
构造函数将会对第二步中收集到的ServletContextInitializerBean
进行排序。排序规则:
Ordered
接口,若有,则调用getOrder()
获取值。findOrder()
获取@Order
注解属性值。结论:
@WebFilter
注解来装配的自定义过滤器中,自己加上的@Order
注解是没有用的。因此真正过滤器的执行顺序依赖于FilterRegistrationBean
的order
值。FilterRegistrationBean
的封装的时候,没有做对于order
属性的装配。既然在使用@WebFilter
的时候,无法让@Order
注解生效。那么我们不使用这种方式来装配过滤器。我们通过装配FilterRegistrationBean
对象来替代。
1.首先,我们把CheckFilter
和TimeFilter
这两个过滤器给注释掉。
2.然后我们在配置类中添加以下Bean
:
@Configuration
public class MyConfig {
@Bean
public FilterRegistrationBean checkFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new CheckFilter());
bean.addUrlPatterns("/*");
bean.setOrder(2);
return bean;
}
@Bean
public FilterRegistrationBean timeFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new TimeFilter());
bean.addUrlPatterns("/*");
bean.setOrder(1);
return bean;
}
}
结果如下:
再或者,你可以用另外一种方式,使用普通的@Component
注解来装配过滤器,也可以出现一样的效果。
@Order(value = 1)
@Component
public class TimeFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("先执行:接口执行时间: 2s");
chain.doFilter(request, response);
}
}
@Component
@Order(2)
public class CheckFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
Thread.sleep(1000);
System.out.println("~~~~~~~~~~~~~后执行:检查通过~~~~~~~~~~~~~~~~~~");
chain.doFilter(request, response);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
总结下来就是:
@WebFilter + @ServletComponentScan
的这种过滤器定义方式,使用简单。但是过滤器链中的执行顺序不可控。FilterRegistrationBean
的方式,虽然相对来说麻烦点,但是可以控制过滤器的一个执行顺序,功能使用上会更好。@Component
来进行注解。(注意不要同时使用@WebFilter + @ServletComponentScan
)。