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

前言

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

功能

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

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

第一种:ApplicationContext

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

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

image-20231223133451041

示例:

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

image-20231223141902132

优点: 这种方式也比较简单,在需要用的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

image-20231223152332165

那我们的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)

image-20231223154215448

image-20231223153523930

invokeAwareMethods它提供了3个Aware

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

其他的在ApplicationContextAwareProcessor

image-20231223154321603

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

第三种

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

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

image-20231223154713386

image-20231223155058597

image-20231223155258481

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

相关推荐

  1. 探索Python上下文管理器

    2023-12-24 10:38:04       7 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-24 10:38:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-24 10:38:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-24 10:38:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-24 10:38:04       18 阅读

热门阅读

  1. git 常用命令

    2023-12-24 10:38:04       35 阅读
  2. AI智能体的介绍

    2023-12-24 10:38:04       36 阅读
  3. ubuntu-22.04.3 配置

    2023-12-24 10:38:04       37 阅读
  4. 功能强大的开源数据中台系统 DataCap 1.18.0 发布

    2023-12-24 10:38:04       45 阅读
  5. 【hive】Hive中的大宽表及其底层详细技术点

    2023-12-24 10:38:04       39 阅读
  6. 1.反射,泛型

    2023-12-24 10:38:04       35 阅读
  7. 【投稿】北海 - Rust与面向对象(二)

    2023-12-24 10:38:04       33 阅读