记一个Spring初始化引用ApplicationContextAware工具类遇到的问题
文章目录
出错场景
- SpringBeanClassA中初始化方法中 会有通过Spring工具类获取容器中的Bean的操作
@Component
public class SpringBeanClassA {
@PostConstruct
public void init() {
// 类A bean初始化方法中 会有通过Spring工具类获取容器中的Bean的操作
SpringBeanClassB bean = SpringBeanUtils.getBean(SpringBeanClassB.class);
}
}
- 获取时
bean = applicationContext.getBean(clazz);
在执行这一行代码的时候报空指针,这说明这个时候setApplicationContext
方法还没有被执行
@Component
public class SpringBeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
private static Map<Class, Object> classBeans = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
System.out.println("SpringBeanUtils init");
}
public static <T> T getBean(Class<T> clazz) {
// 获取容器中的Bean实例
T bean = (T) classBeans.get(clazz);
if (bean != null) {
return bean;
}
bean = applicationContext.getBean(clazz);
classBeans.put(clazz, bean);
return bean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// aware方法中注入高级容器
SpringBeanUtils.applicationContext = applicationContext;
}
}
Spring容器中的Bean初始化
我们知道高级容器ApplicationContext跟BeanFactory不一样,会在一开始的时候尝试初始化扫描到的所有注册到容器中的Bean(单例 没有懒加载标识 非抽象类)
Bean初始化执行流程:
org.springframework.context.support.AbstractApplicationContext#refresh
org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(实际执行Bean创建三部曲的地方)
- 实例化:
instanceWrapper = createBeanInstance(beanName, mbd, args);
- 依赖注入:
populateBean(beanName, mbd, instanceWrapper);
- 初始化:
exposedObject = initializeBean(beanName, exposedObject, mbd);
// org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 这个方法中
// 获取所有从注解/xml中配置的BeanDefinition
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// ...
// 实例化该BeanDefinition对应的Bean
getBean(beanName);
}
}
}
问题解析
我们上面遇到的@PostConstruct
注解标注的方法和 org.springframework.context.ApplicationContextAware#setApplicationContext
Aware接口的注入方法,都是在容器创建Bean第3步 初始化的时候执行的
当 SpringBeanClassA#init
的方法执行,先于SpringBeanUtils
这个Bean的初始化时候,调用就会报错
解决方法
核心:只要我们保证能保证setApplicationContext
方法先于SpringBeanClassA#init
方法执行都行
思路一:放到容器整个初始化后执行该方法
使用@EventListener
注解 监听容器refresh完成事件,如果没有其他特殊需要,推荐在容器初始化完成后执行,这样所有的单例Bean状态都是已经初始化的,不用考虑太多先后初始化关系
@Component
public class SpringBeanClassA {
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
// 在这里处理容器初始化完成事件的逻辑
SpringBeanClassB bean = SpringBeanUtils.getBean(SpringBeanClassB.class);
}
}
执行流程:
org.springframework.context.support.AbstractApplicationContext#refresh
// 初始化单例bean finishBeanFactoryInitialization(beanFactory); // 发布容器初始化完成事件 finishRefresh();
org.springframework.context.support.AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)
daxiao.spring.springdemo.bean.SpringBeanClassA#handleContextRefresh 监听到事件
思路二:定义两个Bean之间的先后初始化关系
使用@DependsOn
注解,定义Bean依赖关系
@Component
// value属性为依赖的SpringBeanUtils的beanName
@DependsOn(value = "springBeanUtils")
public class SpringBeanClassA {
@PostConstruct
public void init() {
System.out.println("SpringBeanClassA init");
SpringBeanClassB bean = SpringBeanUtils.getBean(SpringBeanClassB.class);
}
}
创建Bean中@DependsOn
的使用:
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
// 获取@DependsOn注解中标注的依赖的BeanName String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { // ... // 先创建依赖的Bean 会执行完依赖的Bean的 1.实例化 2.依赖注入 3.初始化 getBean(dep); // ... } } // 创建当前BeanName对应的Bean
可以看到 通过@DependsOn
注解定义了依赖关系后,我们在创建springBeanClassA
这个Bean的时候 会先去创建注解中定义的依赖的springBeanUtils
这个Bean