Spring中的上下文工具你写的可能有bug

文章目录

  • 前言
  • 功能
    • 第一种:ApplicationContext
    • 第二种方式:ApplicationContextAware
    • 第三种:BeanFactoryPostProcessor
  • 源码
    • 第一种
    • 第二种
    • 第三种

前言

本篇是针对如何写一个比较好的spring工具的一个探讨。

功能

下面三种方式,你觉得哪种最好?

  1. 第一种:直接注入ApplicationContext
  2. 第二种:实现ApplicationContextAware接口;
  3. 第三种:实现BeanFactoryPostProcessor接口;

第一种:ApplicationContext

它的功能如下,它有国际化功能,beanFactory功能,事件发布功能,以及资源加载功能,作为上下文,他这个功能已经很强大了。

它发生的时机是在bean实例化后的依赖注入。

Spring中的上下文工具你写的可能有bug_第1张图片

示例:

@Component
public class CustomConfig9 {
    
    @Autowired
    private ApplicationContext applicationContext;
    
    @PostConstruct
    public void init() {
        CustomConfig3 bean = applicationContext.getBean(CustomConfig3.class);
        System.out.println("customConfig9 获取了 customConfig3" + bean);
    }
}

Spring中的上下文工具你写的可能有bug_第2张图片

优点: 这种方式也比较简单,在需要用的bean中直接注入就行;

缺点: 它的局限就是在bean中才能使用,如果你要给工具类,或一个静态方法中使用,你就不太好这样做,你得控制你业务的执行时机;

第二种方式:ApplicationContextAware

这种方式是通过Bean初始化后,执行Aware接口回调方式实现,我见过很多项目,他们都是这样做的:

@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}

这写法没毛病,可是,它存在一个bug :不是任何地方都能使用。

为何这么说?

那么我再增加一个类:

@Component
public class CustomConfig6 implements InitializingBean {

    private CustomConfig config;

    @Override
    public void afterPropertiesSet() throws Exception {
        config = SpringUtil.getBean(CustomConfig.class);
        // 这里示例比较简单,一般业务场景可能是jdbc检索,redis缓存这些逻辑
    }
}

在我增加了这样的一个类后,你觉得你的项目会是正常的吗?

请思考3秒钟…

.

.

.

那么答案是有可能是异常的,为何?

大家还记得Aware接口是在哪个时机调用的,它是在bean初始化后调用的,spring bean的生命周期是单线程的,如果说Spring先实例化了CustomConfig6,那么它会先调用afterPropertiesSet里的SpringUtil.getBean,而这时SpringUtil还没有被实例化,SpringUtil里的applicationContext必然是null

为何会有先后顺序?

我们先复习一下Spring怎么扫描bean的,Spring是先扫描的当前包下的class,顺序扫描,扫描到的class,在经过一些了的校验后,会放到一个容器里,实例化时,再根据bean的名称(它是一个list)进行遍历实例化,到这,大概的一个原因应该明了了吧,如果SpringUtil所在的文件位置考前,在其他类之前扫描到,就能先实例化,那么就是正常的,如果它靠后,就会出现其他业务bean回调时,通过SpringUtil使用ApplicationContext功能而出现空指针异常。

优点: 使用时直接静态方法调用,方便;

缺点: 可能存在bug;

第三种:BeanFactoryPostProcessor

在第二种的方式上进行优化,我们需要考虑,它的一个初始化时机,bean实例化都是统一进行的,所以,我们要打破这个规则,提前进行对SpringUtil中的applicationContext进行赋值,所以我们可以使用BeanFactoryPostProcessor,这个后置处理器是在beanFactory准备完成后端一个回调操作,我们的bean,配置类等等这些都是在这里被扫描出来的,是bean生命周期开始的开端。

@Component
public class SpringUtil2 implements BeanFactoryPostProcessor {

    private static ConfigurableListableBeanFactory applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtil2.applicationContext = beanFactory;
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}

优点: 可用在任何地方法;

源码

第一种

ApplicationContext它是通过依赖注入进行注入的,我们直接看创建bean的方法

位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

那我们的ApplicationContext就是在populateBean方法中被注入点,但是在此之前,它需要查找注入点,然后在注入时,可以直接通过注入点进行属性注入。(图片没有写全,注入点包含@Value, @Inject, 依赖注入的也包含@Value这写, 详细的看:spring源码篇(四)依赖注入(控制反转))

@Autowired注入是由AutowiredAnnotationBeanPostProcessor后置处理器进行处理,而@ResourceCommonAnnotationBeanPostProcessor处理

第二种

第二种是Aware方法调用,也是在bean初始化时调用的,如上面图片描述的,他会在initializeBean方法中调用,位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)

Spring中的上下文工具你写的可能有bug_第3张图片

Spring中的上下文工具你写的可能有bug_第4张图片

invokeAwareMethods它提供了3个Aware

  • BeanNameAware:回调setBeanName,实际上调用有我们控制,你想做什么就做什么;
  • BeanClassLoaderAware:bean容器的类加载器,通过它你可以加载到classpath(这个包含多种路径)下的所有class;
  • BeanFactoryAware:bean工厂(bean容器)回调,

其他的在ApplicationContextAwareProcessor

Spring中的上下文工具你写的可能有bug_第5张图片

如上,只要你实现ResourceLoaderAware, ApplicationEventPublisherAware, MessageSourceAware, ApplicationContextAware他都会吧applicationContext给你设置上。

第三种

这第三种Spring启动时执行的一个方法,就是进行bean容器的初始化;

这就是我们main方法里的SpringApplication.run

Spring中的上下文工具你写的可能有bug_第6张图片

Spring中的上下文工具你写的可能有bug_第7张图片

这部分就是执行我们自定义的beanFactoryPostProcessor,它分排序的和没有排序的,这个方法已经来会先进行BeanDefinitionRegistryPostProcessor这个处理器的执行,这里就是进行扫描,解析配置类和bean(@Component, @Configuation, @Bean....)的地方。所以我们在后面才能获取的我们自定义的bean,并提前实例化。

你可能感兴趣的:(Spring,spring,java)