• Spring常见问题解决 - @WebFilter注解装配的过滤器无法被@Autowired自动注入?


    一. 案例复现

    1.我们自定义一个过滤器,比如用来计算接口的执行时长。

    @WebFilter
    public class MyFilter implements Filter {
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            long start = System.currentTimeMillis();
            chain.doFilter(request, response);
            long end = System.currentTimeMillis();
            System.out.println("接口执行时间: " + (end - start));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.自定义一个Controller层代码:(里面是什么逻辑不重要)

    @RestController
    public class MyController {
        @PostMapping("/hello")
        public Student hello(@Validated @RequestBody Student student) {
            return student;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.启动类:主要需要加上@ServletComponentScan注解:

    @SpringBootApplication
    @ServletComponentScan
    public class Main8080 {
        public static void main(String[] args) {
            SpringApplication.run(Main8080.class,args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.此时访问一下接口,结果如下:可见这个过滤器是正常运行的。
    在这里插入图片描述
    5.那么此时,如果我代码中出现某个业务类,需要引用到这个过滤器怎么办?例如:

    @Service
    public class AdminService {
        @Autowired
        private MyFilter myFilter;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此时程序重新启动就会报错:
    在这里插入图片描述

    二. 原理分析

    首先我们应该了解一下,@ServletComponentScan注解是用来干啥的,简单点说就是:

    • Servlet 可以直接通过 @WebServlet 注解自动注册。
    • Filter可以直接通过 @WebFilter 注解自动注册。
    • Listener可以直接通过 @WebListener 注解自动注册。

    首先,我们从上文的案例结果可以看出,此时我们自定义的MyFilter类,并不会被加载到SpringBoot的容器中。那是不是@WebFilter这个注解有什么特殊的地方呢?

    2.1 @WebFilter 注解加载的是什么Bean?

    首先我们看下它的出处:

    • @Service:import org.springframework.stereotype.Service;
    • @WebFilter:import javax.servlet.annotation.WebFilter;

    可见, @WebFilter 他并不是Spring本身自带的一种注解。 那么我们再来看下,SpringBoot在启动的过程中,是否对这个注解做出了一定的处理。我们全局搜索import javax.servlet.annotation.WebFilter;,可以看到以下结果:

    在这里插入图片描述
    可以看到WebFilterHandler这个类中对这个注解做了对应的处理,那好了,我们在里面打个断点瞅瞅:
    在这里插入图片描述
    根据左下角调用栈,我们重点关注ServletComponentRegisteringPostProcessor这个处理器,看下源码发现,这个类里面有一个静态代码块和一段扫描代码:

    class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
    
    	private static final List<ServletComponentHandler> HANDLERS;
    
    	static {
    		List<ServletComponentHandler> servletComponentHandlers = new ArrayList<>();
    		servletComponentHandlers.add(new WebServletHandler());
    		servletComponentHandlers.add(new WebFilterHandler());
    		servletComponentHandlers.add(new WebListenerHandler());
    		HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
    	}
    
    	@Override
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    		if (isRunningInEmbeddedWebServer()) {
    			ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
    			for (String packageToScan : this.packagesToScan) {
    				scanPackage(componentProvider, packageToScan);
    			}
    		}
    	}
    
    	private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) {
    		for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
    			if (candidate instanceof AnnotatedBeanDefinition) {
    				for (ServletComponentHandler handler : HANDLERS) {
    					handler.handle(((AnnotatedBeanDefinition) candidate),
    							(BeanDefinitionRegistry) this.applicationContext);
    				}
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    从这个静态代码块,我们可以发现跟上文中讲到的@ServletComponentScan注解的作用是吻合的。这两者是有关联的,在下文会说。

    上述代码结合本文案例来说就是:

    1. WebFilterHandler类会处理所有被@WebFilter注解的类。
    2. 通过registry.registerBeanDefinition(name, builder.getBeanDefinition());这段代码进行注册。

    那我们执行一下这段代码,看看会怎么样。步骤如下:

    1. 让代码跑到这一步:
      在这里插入图片描述

    2. 对这行代码,alt+左键(即查看执行结果,虽然这个函数并不会有返回,但是查看的同时相当于执行了一遍这段代码),然后下一步就能发现抛出了异常:
      在这里插入图片描述

    异常的完整信息是:

    org.springframework.beans.factory.support.BeanDefinitionOverrideException:
    Invalid bean definition with name ‘com.filter.MyFilter’ defined in null:
    Cannot register bean definition [Root bean: clas s[org.springframework.boot.web.servlet.FilterRegistrationBean];

    这里的意思,通俗点就是,无法注册一个FilterRegistrationBean类型的Bean。也就是说,我们执行这段代码,想要注册我们通过@WebFilter注解修饰的过滤器的时候,这个过滤器的类型是FilterRegistrationBean

    2.2 过滤器是如何被封装成 FilterRegistrationBean 类型的?

    封装的这一个环节,可想而知是发生在类的实例化阶段。我们可以在自定义的过滤器中加一个构造函数,然后打个断点,debug下瞅瞅:

    public MyFilter() {
        System.out.println("MyFilter");
    }
    
    • 1
    • 2
    • 3

    调试如下:
    在这里插入图片描述

    我们可以发现,从tomcat启动开始,就会调用onStartup()函数进行一些初始化的启动操作。根据调用链,我们可以定位到selfInitialize()这个函数中,看下代码:
    在这里插入图片描述

    我们可以得知,这个时候,FilterRegistrationBean这个类型的Bean需要被初始化。我们可以看下getServletContextInitializerBeans()这个函数上的注释:
    在这里插入图片描述
    那么紧接着往后看,看到createBean()这个函数:这个函数到目前为止应该是用于创建FilterRegistrationBean的实例的,只不过这里装配了下MyFilter自身。
    在这里插入图片描述
    我们知道,Spring容器中的Bean,其加载过程分为三大类:详细的可以看Spring源码系列:Bean的加载

    • Bean实例的创建。
    • 相关属性的注入。
    • 初始化操作。

    从调用栈看来,这三个步骤确实都包含了:

    并且,调用栈的意思应该是:

    1. 创建了FilterRegistrationBean的实例。

    2. FilterRegistrationBean的属性进行注入,此时需要创建MyFilter类型的Bean
      在这里插入图片描述

    3. MyFilterBean重复做三个动作:实例化、属性注入、初始化:
      在这里插入图片描述

    但是最终装配的对象是是一种InnerBean

    2.3 InnerBean 怎么就不能注入了?

    对于本文案例的MyFilter类而言,它满足两个条件:

    • @WebFilter修饰。
    • 在其他业务类中,通过@Autowired注解来自动装配了进来。

    然后我们再来说说InnerBean的情况。首先,我们从上面的调试流程来看,我们知道,我们项目中的MyFilter类的类型是一种InnerBean也就是内部bean。因为内部bean总是匿名的并且总是由外部bean创建的。因此不可能单独访问内部bean,也不可能将它们注入到协作bean中。

    结合本文案例来说就是:

    • 我们引入的MyFilter 对象是一个InnerBean。无法通过@Autowired private MyFilter myFilter;的方式去引入。
    • 此时对于@WebFilter相关的过滤器而言,真正注册到Spring容器中的是FilterRegistrationBean类。

    三. 问题解决

    我们可以对需要引入过滤器的业务类这么更改:

    更改前
    @Service
    public class AdminService {
        @Autowired
        private MyFilter myFilter;
    }
    
    更改后
    @Service
    public class AdminService {
        @Autowired
        @Qualifier("com.filter.MyFilter")
        private FilterRegistrationBean myFilter;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    【Unity】Unity常用类:向量Vector3、四元数Quaternion
    A3纸内容分2页打在A4纸上
    第二部分:DDD 设计中的基本元素
    机器学习吴恩达课程学习笔记 Part1——从机器学习概念到线性回归
    详细讲解js实现电梯导航
    ZYNQ之路--程序固化教程
    Linux上运行Nacos服务出现报错及解决方法
    皕杰报表数据源报错
    m基于中继协助的认知无线电频谱切换机制的matlab仿真分析
    GraphRAG学习小结(4)
  • 原文地址:https://blog.csdn.net/Zong_0915/article/details/126705693