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

文章目录

前言

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

功能

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

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

第一种:ApplicationContext

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

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

示例:

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

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

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

第二种方式:ApplicationContextAware

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

java 复制代码
@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 :不是任何地方都能使用。

为何这么说?

那么我再增加一个类:

java 复制代码
@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生命周期开始的开端。

java 复制代码
@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)

invokeAwareMethods它提供了3个Aware

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

其他的在ApplicationContextAwareProcessor

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

第三种

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

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

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

相关推荐
lizi8888828 分钟前
单组件的编写
java
java_heartLake30 分钟前
设计模式之代理模式
java·设计模式·代理模式
魏 无羡1 小时前
pgsql 分组查询方法
java·服务器·数据库
兩尛1 小时前
java--面向对象编程(中级部分)
java·开发语言
Xxxx. .Xxxx2 小时前
C语言程序设计实验与习题指导 (第4版 )课后题-第二章+第三章
java·c语言·开发语言
姜西西_2 小时前
[Spring]Spring MVC 请求和响应及用到的注解
java·spring·mvc
逸狼2 小时前
【JavaEE初阶】多线程6(线程池\定时器)
java·开发语言·算法
qq_35323353892 小时前
【原创】java+springboot+mysql科研成果管理系统设计与实现
java·spring boot·mysql·mvc·web
dawn1912282 小时前
SpringMVC 入门案例详解
java·spring·html·mvc
极客先躯2 小时前
高级java每日一道面试题-2024年9月16日-框架篇-Spring MVC和Struts的区别是什么?
java·spring·面试·mvc·struts2·框架篇·高级java