Spring Boot @Autowired 注入失效问题
1. 问题
最近在项目中添加了会话验证过滤器,该Filter中使用@Autowired自动装载了一些从数据库中获取的系统配置,调试的时候发现注入失败,返回为null。
// RequestAuthFilter只是一个普通类
// 这段代码处于Application.java启动类中
@Bean
public FilterRegistrationBean contextFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new RequestAuthFilter());
registrationBean.addUrlPatterns("/web/*", "/wechat/*", "/core/*");
registrationBean.setName(GlobalConstants.AUTH_FILTER_NAME);
registrationBean.setOrder(1);
return registrationBean;
}
2. 处理方案
尝试以下两种方案均成功,本质上都是将Filter变成让Spring容器中的bean。
2.1 修改filter为Spring中bean
- 修改RequestAuthFilter为Component
@Component
public class RequestAuthFilter implements Filter {
- 自动装载该类
@Autowired
RequestAuthFilter filter;
@Bean
public FilterRegistrationBean contextFilterRegistrationBean() {
...
registrationBean.setFilter(filter);
...
return registrationBean;
}
2.2 保留Filter为普通类,在Application.java中重新定义一个Bean
@Bean
public FilterRegistrationBean contextFilterRegistrationBean() {
...
registrationBean.setFilter(getFilterRegistrationBean());
...
}
@Bean
public Filter getFilterRegistrationBean() {
return new RequestAuthFilter();
}
3. 分析原因
后期通过new产生的实例中无法自动注入Spring容器中装载的实例。
原理需要了解Spring依赖注入,或者说控制反转,查阅Introduction to the Spring IoC container and beans。
It is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean.
这些Bean被Spring IoC 容器(org.springframework.context.ApplicationContext)管理,Spring容器也就是一个Bean工厂。
所以自动注入失效,一般有以下两种可能。
3.1 包没有被扫描到
引用自spring-boot-and-component-scan
- If your other packages hierarchies are below your main app with the @SpringBootApplication annotation, you’re covered by implicit components scan.
- If there are beans/components in other packages which are not sub packages of the main package, you should manually add them as @ComponentScan
Spring Boot项目的Bean装配默认规则是根据Application类(指项目入口类)所在的包位置从上往下扫描。
假设Application类在包com.comp.appname下,则只会扫描com.comp.appname包及其所有子包,如果需要自动装载的类所在包不在com.comp.appname及其子包下,则不会被扫描,自然就没法被注入!
3.2 调用者是使用new创建的
如果类A中存在成员属性B, B是通过@Autowired自动注入,而类A的实例是通过new的方式产生的,那么自动注入会失效的,此时通过Spring的上下文获取所有的Bean的方法来获取B。
/**
* Spring上下文工具类,用以让普通类获取Spring容器中的Bean
*/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
/**
* 获取applicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
/**
* 通过name获取 Bean
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
}
然后在A中就可以这样来获取Spring容器中的B实例
BclassInterface b = (BclassInterfaceImpl) SpringUtil.getBean("bclassInterfaceImpl");