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));
}
}
2.自定义一个Controller
层代码:(里面是什么逻辑不重要)
@RestController
public class MyController {
@PostMapping("/hello")
public Student hello(@Validated @RequestBody Student student) {
return student;
}
}
3.启动类:主要需要加上@ServletComponentScan
注解:
@SpringBootApplication
@ServletComponentScan
public class Main8080 {
public static void main(String[] args) {
SpringApplication.run(Main8080.class,args);
}
}
4.此时访问一下接口,结果如下:可见这个过滤器是正常运行的。
5.那么此时,如果我代码中出现某个业务类,需要引用到这个过滤器怎么办?例如:
@Service
public class AdminService {
@Autowired
private MyFilter myFilter;
}
此时程序重新启动就会报错:
二. 原理分析
首先我们应该了解一下,@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);
}
}
}
}
}
从这个静态代码块,我们可以发现跟上文中讲到的@ServletComponentScan
注解的作用是吻合的。这两者是有关联的,在下文会说。
上述代码结合本文案例来说就是:
WebFilterHandler
类会处理所有被@WebFilter
注解的类。- 通过
registry.registerBeanDefinition(name, builder.getBeanDefinition());
这段代码进行注册。
那我们执行一下这段代码,看看会怎么样。步骤如下:
-
让代码跑到这一步:
-
对这行代码,
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");
}
调试如下:
我们可以发现,从tomcat
启动开始,就会调用onStartup()
函数进行一些初始化的启动操作。根据调用链,我们可以定位到selfInitialize()
这个函数中,看下代码:
我们可以得知,这个时候,FilterRegistrationBean
这个类型的Bean
需要被初始化。我们可以看下getServletContextInitializerBeans()
这个函数上的注释:
那么紧接着往后看,看到createBean()
这个函数:这个函数到目前为止应该是用于创建FilterRegistrationBean
的实例的,只不过这里装配了下MyFilter
自身。
我们知道,Spring
容器中的Bean
,其加载过程分为三大类:详细的可以看Spring源码系列:Bean的加载。
Bean
实例的创建。- 相关属性的注入。
- 初始化操作。
从调用栈看来,这三个步骤确实都包含了:
并且,调用栈的意思应该是:
-
创建了
FilterRegistrationBean
的实例。 -
对
FilterRegistrationBean
的属性进行注入,此时需要创建MyFilter
类型的Bean
。
-
对
MyFilter
的Bean
重复做三个动作:实例化、属性注入、初始化:
但是最终装配的对象是是一种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;
}