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

30 篇文章 3 订阅

一. 案例复现

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注解的作用是吻合的。这两者是有关联的,在下文会说。

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

  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");
}

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

我们可以发现,从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;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值