当前位置: 首页 > news >正文

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

文章目录

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

前言

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

功能

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

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

第一种:ApplicationContext

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

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

image-20231223133451041

示例:

@Component
public class CustomConfig9 {@Autowiredprivate ApplicationContext applicationContext;@PostConstructpublic 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;@Overridepublic 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;@Overridepublic 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;@Overridepublic 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,并提前实例化。

相关文章:

  • 创建ROS的软件包服务器
  • 量子密码学简介
  • ADRC-跟踪微分器TD的Maltab实现及参数整定
  • 智慧城市新型基础设施建设综合方案:文件全文52页,附下载
  • pycharm2023.2激活和新建项目,python3.12安装永久换源
  • 2312llvm,用匹配器构建clang工具
  • MIT 6.S081---Lab util: Unix utilities
  • 一篇了解Maven中的<optional>和<scope>使用
  • 基于RocketMQ实现分布式事务
  • 31. Ajax
  • STM32外设系列—HC-05(蓝牙)
  • 【GitHub精选项目】短信系统测试工具:SMSBoom 操作指南
  • 创新科技赋能,易点易动设备管理系统助力企业实现设备管理升级
  • Unity自带的NavMesh寻路组件
  • Hadoop——分布式计算
  • ES6指北【2】—— 箭头函数
  • .pyc 想到的一些问题
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • 【刷算法】求1+2+3+...+n
  • angular组件开发
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • Github访问慢解决办法
  • gulp 教程
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • Javascript编码规范
  • Laravel 菜鸟晋级之路
  • Shadow DOM 内部构造及如何构建独立组件
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 实战|智能家居行业移动应用性能分析
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 学习Vue.js的五个小例子
  • Mac 上flink的安装与启动
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • ​secrets --- 生成管理密码的安全随机数​
  • ###C语言程序设计-----C语言学习(6)#
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #define用法
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • (0)Nginx 功能特性
  • (6)STL算法之转换
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (function(){})()的分步解析
  • (简单) HDU 2612 Find a way,BFS。
  • (算法)前K大的和
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • .Net 4.0并行库实用性演练
  • .Net各种迷惑命名解释
  • .Net中的设计模式——Factory Method模式
  • /etc/X11/xorg.conf 文件被误改后进不了图形化界面
  • [AIGC] SQL中的数据添加和操作:数据类型介绍
  • [ASP.NET MVC]如何定制Numeric属性/字段验证消息
  • [C++] new和delete
  • [Deep Learning] 神经网络基础
  • [FC][常见Mapper IRQ研究]