容器启动时过滤器初始化和排序注册的相关逻辑。
使用1 @WebFilter过滤器@Order无效
引导过程:
Controller:
实现两个过滤器:
AuthFilter
TimeCostFilter
如果使用@Order,预计将首先运行TimeCostFilter。TimeCostFilter最初旨在统计此接口的性能,因此需要统计AuthFilter执行的身份认证过程。
所有代码实现完成后,执行结果如下:
观察日志,执行时间不包括授权过程,因此违背了期待,添加了@Order!
但是,如果交换Order指定的值,就没有效果了,why?订单无法排序WebFilter吗?
源代码分析
请求来临时,将以StandardWrapperValve#invoke()的身份运行,创建ApplicationFilterChain,然后通过ApplicationFilterChain # dofilter()
过滤器执行顺序由实例变量Filters维护。Filters是createFilterChain()在容器启动时依次遍历StandardContext的成员变量FilterMaps。
对StandardContext#FilterMaps的写入
AddFilterMapBefore()
滤镜的执行顺序是由StandardContext#FilterMaps顺序决定的,FilterMaps是包装的数组,因此只需进一步了解FilterMaps中每个元素的排序顺序即可。
将断点添加到addFilterMapBefore()中。
e.detail&_iz=31825&index=8" width="640" height="108"/>Spring从selfInitialize()一直调用到addFilterMapBefore()。
selfInitialize()通过调用getServletContextInitializerBeans()获取所有ServletContextInitializer类型Bean,并调用该Bean的onStartup(),最终调用到addFilterMapBefore()。
上述的selfInitialize()又从何处调用过来呢?
selfInitialize()
getServletContextInitializerBeans()
返回的ServletContextInitializer类型Beans顺序=》
决定了addFilterMapBefore()调用顺序=》
决定FilterMaps内元素顺序=》
最终决定了过滤器的执行顺序。
getServletContextInitializerBeans()
仅返回了ServletContextInitializerBeans类的一个实例,参考代码如下:
ServletContextInitializerBeans是个集合类
所以,selfInitialize()能遍历 ServletContextInitializerBeans,查看其iterator()所遍之物:
对外暴露的集合遍历元素为sortedList成员变量,即selfInitialize()最终遍历sortedList。
综上,sortedList中的过滤器Bean元素顺序决定最终过滤器的执行顺序。
继续查看ServletContextInitializerBeans构造器:
第87行可知:sortedList元素顺序由通过AnnotationAwareOrderComparator排序而得。
AnnotationAwareOrderComparator通过两种方式获取比较器需要的order值以决定sortedInitializers的排列顺序:
- 待排序的对象元素实现Order接口,则通过 getOrder() 获取order值
- 否则执行 OrderU() 获取该对象类 @Order 的属性
因为类型为ServletContextInitializer,其实现了Ordered接口,所以此处比较器使用 getOrder() 获取order值,对应实例变量order。
如下方法均构建了ServletContextInitializer子类的实例,并添加到了:
- addAdaptableBeans()
- addServletContextInitializerBeans()
这里只看addServletContextInitializerBeans,因为使用 @WebFilter 标记添加过滤器的方案最终只会通过该方法生效:实例化并注册了所有的ServletRegistrationBean、FilterRegistrationBean以及ServletListenerRegistrationBean。
在这个方法中,Spring通过 getOrderedBeansOfType() 实例化了所有ServletContextInitializer子类:
根据不同类型,调用addServletContextInitializerBean(),ServletContextInitializer子类包括:
- ServletRegistrationBean
- FilterRegistrationBean
- ServletListenerRegistrationBean
正好对应了Servlet三大要素。
现在只关心对应Filter的FilterRegistrationBean,显然,FilterRegistrationBean是ServletContextInitializer的子类(实现了Ordered接口),同样由成员变量order的值决定其执行的优先级。
private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
ListableBeanFactory beanFactory) {
...
if (initializer instanceof FilterRegistrationBean) {
Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
addServletContextInitializerBean, beanName, initializer, beanFactory, source);
}
...
}
最终添加到:
我们没有定义FilterRegistrationBean,那这里FilterRegistrationBean在哪里被定义出的?其order类成员变量是否有特定取值逻辑?
WebFilterHandler#doHandle()
通过 BeanDefinitionBuilder动态构建了FilterRegistrationBean类型BeanDefinition。然而这里并未设置order值,也没设置 @Order 指定值。
至此,也就知道问题根因,所有被**@WebFilter**注解的类,最终都会在此处被包装为FilterRegistrationBean类的BeanDefinition。
虽FilterRegistrationBean也实现了Ordered接口
但在这并未填充值,因为:
- 这里所有属性都是从 @WebFilter 对应的属性获取
- 但 @WebFilter 本身没有指定可以辅助排序的属性
过滤器执行顺序
- RegistrationBean中order属性的值
- ServletContextInitializerBeans类成员变量sortedList中元素的顺序
- ServletWebServerApplicationContext 中selfInitialize()遍历FilterRegistrationBean的顺序
- addFilterMapBefore()调用的顺序
- filterMaps内元素的顺序
- 过滤器的执行顺序
RegistrationBean中order属性的值最终可以决定过滤器的执行顺序。
然而,使用 @WebFilter 时,构建的FilterRegistrationBean并未依据 @Order 的值去设置order属性,所以 @Order 失效。
修正
实现自己的FilterRegistrationBean配置添加过滤器,不再使用 @WebFilter :
由于WebFilterHandler#doHandle()虽构建FilterRegistrationBean类型BeanDefinition,但未设置order值。
所以,考虑直接手动实例化FilterRegistrationBean实例且设置其setOrder()。
切记去掉AuthFilter和TimeCostFilter类中的**@WebFilter** 即可。
2 竟然重复执行了过滤器
解决排序问题,可能有人就想了是不是有其他解决方案?
比如在两个过滤器中使用 @Component,让@Order生效?
代码如下。
AuthFilter:
@WebFilter
@Slf4j
@Order(2)
@Component
public class AuthFilter implements Filter {
@SneakyThrows
@Override
public void doFilter(Servletrequest request, ServletResponse response, FilterChain chain){
if(isPassAuth()){
Sy("通过授权");
c(request, response);
}else{
Sy("未通过授权");
((HttpServletResponse)response).sendError(401);
}
}
private boolean isPassAuth() throws InterruptedException {
Sy("执行检查权限");
T(1000);
return true;
}
}
TimeCostFilter类如下:
@WebFilter
@Slf4j
@Order(1)
@Component
public class TimeCostFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Sy("#开始计算接口耗时");
long start = Sy();
c(request, response);
long end = Sy();
long time = end - start;
Sy("#执行时间(ms):" + time);
}
}
最终执行结果如下:
#开始计算接口耗时
执行检查权限
通过授权
执行检查权限
通过授权
#开始计算接口耗时
......用户注册成功
#执行时间(ms):73
#执行时间(ms):2075
更改 AuthFilter 类中的Order值为0,继续测试,得到结果如下:
执行检查权限
通过授权
#开始计算接口耗时
执行检查权限
通过授权
#开始计算接口耗时
......用户注册成功
#执行时间(ms):96
#执行时间(ms):1100
看来控制Order值可以调整Filter执行顺序了,但过滤器本身却被执行2次,why?
源码解析
被@WebFilter的过滤器,会在WebServletHandler类中被重新包装为FilterRegistrationBean类的BeanDefinition,而非Filter类型。
而自定义过滤器增加 @Component 时,怀疑Spring会根据当前类再次包装一个新过滤器,因而doFIlter()被执行两次。
ServletContextInitializerBeans构造器:
addAdaptableBeans()
实例化并注册了所有实现Servlet、Filter及EventListener接口的类,重点看Filter实现类,然后再逐一包装为FilterRegistrationBean。
于是,可知Spring能直接实例化FilterRegistrationBean类型过滤器的原因:
WebFilterHandler相关类通过扫描 @WebFilter,动态构建了FilterRegistrationBean类型的BeanDefinition,并注册到Spring;
或我们自己使用 @Bean 显式实例化FilterRegistrationBean并注册到Spring,如案例1中的解决方案。
但Filter类型的过滤器如何才能被Spring直接实例化呢?
任何通过 @Component 修饰的的类,都可自动注册到Spring,被Spring直接实例化。
调用了addAsRegistrationBean(),其beanType为Fil:
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
// ...
addAsRegistrationBean(beanFactory, Fil, new FilterRegistrationBeanAdapter());
// ...
}
继续查看最终调用到的方法addAsRegistrationBean():
private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
// 创建所有 Filter 子类的实例,即所有实现Filter接口且被@Component修饰的类
List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, );
// 依次遍历这些Filter类实例
for (Entry<String, B> entry : entries) {
String beanName = en();
B bean = en();
if (.add(bean)) {
// 通过RegistrationBeanAdapter将这些类包装为RegistrationBean
RegistrationBean registration = ada(beanName, bean, en());
// 获取Filter类实例的Order值
int order = getOrder(bean);
// 设置到包装类 RegistrationBean
regi(order);
// 将RegistrationBean添加到
.add(type, registration);
if ()) {
logger.trace("Created " + () + " initializer for bean '" + beanName + "'; order="
+ order + ", resource=" + getResourceDescription(beanName, beanFactory));
}
}
}
}
当过滤器同时被 @WebFilter、@Component 修饰时,会导致两个FilterRegistrationBean实例产生:
- addServletContextInitializerBeans()
- addAdaptableBeans()
最终都会创建FilterRegistrationBean的实例,但不同的是:
- @WebFilter 会让addServletContextInitializerBeans()实例化,并注册所有动态生成的FilterRegistrationBean类型的过滤器
- @Component 会让addAdaptableBeans()实例化所有实现Filter接口的类,然后再逐一包装为FilterRegistrationBean类型的过滤器。
修正
参考案例1的问题修正部分。
也可去掉@WebFilter保留@Component:
//@WebFilter
@Slf4j
@Order(1)
@Component
public class TimeCostFilter implements Filter {
//省略非关键代码
}
总结
- @WebFilter和@Component的相同点是:
它们最终都被包装并实例化成为了FilterRegistrationBean;
它们最终都是在 ServletContextInitializerBeans的构造器中开始被实例化。
- @WebFilter和@Component的不同点:
被@WebFilter修饰的过滤器会被提前在BeanFactoryPostProcessors扩展点包装成FilterRegistrationBean类型的BeanDefinition,然后在Servle() 进行实例化
@Component修饰的过滤器类,在Servle() 中被实例化成Filter类型后,再包装为RegistrationBean类型
被@WebFilter修饰的过滤器不会注入Order属性
被@Component修饰的过滤器会在Servle() 中注入Order属性
作者:JavaEdge.
原文链接:
1.《【applicationisinterrupted】不要小看“弹簧过滤器”。这些知识点必须掌握。》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《【applicationisinterrupted】不要小看“弹簧过滤器”。这些知识点必须掌握。》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/gl/2509921.html