记一个Spring初始化引用ApplicationContextAware工具类遇到的问题

记一个Spring初始化引用ApplicationContextAware工具类遇到的问题

出错场景

  1. SpringBeanClassA中初始化方法中 会有通过Spring工具类获取容器中的Bean的操作
@Component
public class SpringBeanClassA  {

    @PostConstruct
    public void init() {
        // 类A bean初始化方法中 会有通过Spring工具类获取容器中的Bean的操作
        SpringBeanClassB bean = SpringBeanUtils.getBean(SpringBeanClassB.class);
    }

}
  1. 获取时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;
    }
}

image-20240801210812742

image-20240801210722031

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创建三部曲的地方)

  1. 实例化: instanceWrapper = createBeanInstance(beanName, mbd, args);
  2. 依赖注入:populateBean(beanName, mbd, instanceWrapper);
  3. 初始化: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

image-20240801223014217

可以看到 通过@DependsOn注解定义了依赖关系后,我们在创建springBeanClassA这个Bean的时候 会先去创建注解中定义的依赖的springBeanUtils 这个Bean

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值