听说 Spring Bean 的创建还有一条捷径?

news2024/9/29 11:30:56

文章目录

    • 1. resolveBeforeInstantiation
      • 1.1 applyBeanPostProcessorsBeforeInstantiation
      • 1.2 applyBeanPostProcessorsAfterInitialization
      • 1.3 案例
    • 2. 源码实践
      • 2.1 切面 Bean
      • 2.2 普通 Bean

在 Spring Bean 的创建方法中,有如下一段代码:

AbstractAutowireCapableBeanFactory#createBean:

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	//...
	try {
		// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
		Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
		if (bean != null) {
			return bean;
		}
	}
	try {
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		return beanInstance;
	}
	//...
}

我们平时说的 Bean 的创建逻辑都是指 doCreateBean 方法中的逻辑,在松哥前面几篇文章中,凡是涉及到 Bean 的创建流程的,我说的也都是 doCreateBean 方法的流程。

但是小伙伴们注意,在 doCreateBean 方法执行之前,其实还有一个 resolveBeforeInstantiation 方法会先执行,而这个方法可能就直接产生一个 Bean 了!如果这个方法直接产生一个 Bean 了,那么 doCreateBean 方法中的逻辑就不会生效了。

那么 resolveBeforeInstantiation 方法存在的意义是什么呢?其实大家从该方法的注释上大概也能看出一些端倪出来了:

给 BeanPostProcessor 一个机会去创建一个代理对象,用这个代理对象来代替目标 Bean。

1. resolveBeforeInstantiation

看下面的源码小伙伴们一定要先搞清楚两个比较相似的单词,否则看到后面就乱了:

  • instantiation:实例化,从 Class 到 Bean 就是实例化。
  • initialization:初始化,给 Bean 做各种配置就是初始化。

搞明白这两个单词,我们来看源码。

首先我先来和小伙伴们稍微梳理一下 resolveBeforeInstantiation 方法。

AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation:

@Nullable
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
	Object bean = null;
	if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
		// Make sure bean class is actually resolved at this point.
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			Class<?> targetType = determineTargetType(beanName, mbd);
			if (targetType != null) {
				bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
				if (bean != null) {
					bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
				}
			}
		}
		mbd.beforeInstantiationResolved = (bean != null);
	}
	return bean;
}

小伙伴们看一下,这里有一个判断条件,mbd.isSynthetic 是判断是否是一个合成 Bean,这种一般都是 Spring 定义的,我们自定义的 Bean 一般都不属于这一类,然后后面的 hasInstantiationAwareBeanPostProcessors 方法则是判断当前是否存在 InstantiationAwareBeanPostProcessor 类型的后置处理器,如果存在,则进入到 if 分支中。

如果我们想要在 resolveBeforeInstantiation 方法中就完成 Bean 的处理,那么就需要自己提供一个 InstantiationAwareBeanPostProcessor 类型的后置处理器。

接下来会调用两个方法:

  • applyBeanPostProcessorsBeforeInstantiation:从名字可以看出来,这个是在实例化之前触发的方法,所以这个方法的参数还是 Class,因为还未实例化。
  • applyBeanPostProcessorsAfterInitialization:从名字可以看出来,这个是在初始化之后出发的方法,所以这个方法的参数是 Bean,因为此时已经完成了初始化了。

1.1 applyBeanPostProcessorsBeforeInstantiation

@Nullable
protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
	for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
		Object result = bp.postProcessBeforeInstantiation(beanClass, beanName);
		if (result != null) {
			return result;
		}
	}
	return null;
}

这个地方就很简单了,就是执行 InstantiationAwareBeanPostProcessor 类型的后置处理器的 postProcessBeforeInstantiation 方法。

1.2 applyBeanPostProcessorsAfterInitialization

@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
		throws BeansException {
	Object result = existingBean;
	for (BeanPostProcessor processor : getBeanPostProcessors()) {
		Object current = processor.postProcessAfterInitialization(result, beanName);
		if (current == null) {
			return result;
		}
		result = current;
	}
	return result;
}

这个是执行 BeanPostProcessor 的 postProcessAfterInitialization 方法。

所以这块的源码其实并不难,道理很简单。

1.3 案例

松哥写一个简单的案例小伙伴们来看下。

假设我有一个 BookService,如下:

public class BookService {

    public void hello() {
        System.out.println("hello javaboy");
    }
}

然后我再创建一个 InstantiationAwareBeanPostProcessor 类型的后置处理器,并且重写前面提到的 postProcessBeforeInstantiation 和 postProcessAfterInitialization 方法:

public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (beanClass == BookService.class) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(beanClass);
            enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
                String name = method.getName();
                System.out.println(name + " 方法开始执行了...");
                Object invoke = proxy.invokeSuper(obj, args);
                System.out.println(name + " 方法执行结束了...");
                return invoke;
            });
            BookService bookService = (BookService) enhancer.create();
            return bookService;
        }
        return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean.getClass() ========= " + bean.getClass());
        return InstantiationAwareBeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

在 postProcessBeforeInstantiation 方法中,如果要创建的 Bean 是 BookService,则这里通过 Enhancer 来创建一个 CGLIB 的代理对象,如果是其他的 Bean 的创建,则调用父类方法即可。这样重写之后,就会导致在 1.1 小节中,获取到的 result 就是一个代理的 BookService 对象。

在 postProcessAfterInitialization 方法中,我未做任何额外处理,就是把拿到的 Bean 打印了一下,此时我们拿到手的 Bean 其实就是前面 postProcessBeforeInstantiation 方法生成的代理对象,然后这里调用父类方法去返回,实际上就是把参数 Bean 原封不动返回。

最后将这两个 Bean 注册到 Spring 容器中:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="org.javaboy.bean.aop2.BookService" id="bookService"/>
    <bean class="org.javaboy.bean.aop2.MyInstantiationAwareBeanPostProcessor"
          id="myInstantiationAwareBeanPostProcessor"/>
</beans>

然后初始化 Spring 容器:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop2.xml");
BookService bs = ctx.getBean(BookService.class);
System.out.println("bs.getClass() = " + bs.getClass());
bs.hello();

最终执行结果如下:

可以看到,最终拿到的 BookService 就是一个代理对象,从源码层面来讲,这个代理对象在 resolveBeforeInstantiation 方法中就生成了,后续的 doCreateBean 方法实际上并未执行。

这就是 resolveBeforeInstantiation 方法的作用,实际上就是给 BeanPostProcessor 一个机会去创建一个代理对象,用这个代理对象来代替目标 Bean。

2. 源码实践

松哥为什么会关注到这个方法呢?

如果有小伙伴研究过 Spring AOP 源码,就会发现这个方法在处理 Spring AOP 的时候,有一个用武之地。

当我们在 Spring AOP 中,往往通过如下代码来定义切面:

@Component
@Aspect
@EnableAspectJAutoProxy
public class LogAspect {
    //...
}

这个类上面有一个 @Aspect 注解,那么问题来了,Spring 是如何识别出这是一个切面而非普通的 Bean 的?

答案就是在 1.1 小节中的 applyBeanPostProcessorsBeforeInstantiation 方法中,这个方法会遍历所有 InstantiationAwareBeanPostProcessor 类型的后置处理器,InstantiationAwareBeanPostProcessor 有一个子类是 AnnotationAwareAspectJAutoProxyCreator,在这个处理器中,识别出来了 LogAspect 是一个切面。

具体识别方法如下:

首先调用 AnnotationAwareAspectJAutoProxyCreator 的 postProcessBeforeInstantiation 方法(实际上是 AnnotationAwareAspectJAutoProxyCreator 的父类 AbstractAutoProxyCreator 中的方法):

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
	Object cacheKey = getCacheKey(beanClass, beanName);
	if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
		if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return null;
		}
	}
	//...
}

从这个地方开始,分成了两条线:

  1. 如果是一个切面 Bean 的话,则执行第一个方法 isInfrastructureClass 就可以返回 true 了。
  2. 如果是一个普通 Bean 的话,则第一个方法会返回 false,此时就会执行第二个方法 shouldSkip(虽然该方法也会返回 false),但是该方法有一些其他的价值在里边。

2.1 切面 Bean

我们先来看 isInfrastructureClass 方法,先来看切面 Bean 是怎么处理的。

这个方法我摘了一部分出来,我们重点关注 isInfrastructureClass 方法,这个方法用来判断当前类是否是一个 Aspect:

@Override
protected boolean isInfrastructureClass(Class<?> beanClass) {
	return (super.isInfrastructureClass(beanClass) ||
			(this.aspectJAdvisorFactory != null && this.aspectJAdvisorFactory.isAspect(beanClass)));
}

这里的判断主要是两方面:

  1. 调用父类的方法去判断当前类是否和 AOP 相关:
protected boolean isInfrastructureClass(Class<?> beanClass) {
	boolean retVal = Advice.class.isAssignableFrom(beanClass) ||
			Pointcut.class.isAssignableFrom(beanClass) ||
			Advisor.class.isAssignableFrom(beanClass) ||
			AopInfrastructureBean.class.isAssignableFrom(beanClass);
	return retVal;
}

这个就不用我解释了,这些都是我们在 AOP 中的老熟人了。

  1. 调用 aspectJAdvisorFactory.isAspect 方法去判断当前类是否包含 @Aspect 注解:

AbstractAspectJAdvisorFactory#isAspect

@Override
public boolean isAspect(Class<?> clazz) {
	return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
private boolean hasAspectAnnotation(Class<?> clazz) {
	return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}

这代码可太好理解了,就是检查当前类是否有 @Aspect 注解。后面那个 compiledByAjc 方法是检查是否需要 ajc 编译,这个我们一般都不需要,所以,只要 hasAspectAnnotation 方法返回 true,整体上就会返回 true。

如果我们的类上包含 @Aspect 注解,那么最终就会在将当前类名加入到 advisedBeans Map 中,在 advisedBeans 这个 Map 中,key 是当前 Bean 的名称,value 则是 false 是一个标记,表示当前类不需要生成代理类。

这就是 isInfrastructureClass 方法执行的大致逻辑。

2.2 普通 Bean

如果是普通 Bean 的话,很明显 isInfrastructureClass 方法会返回 false,这就会导致 shouldSkip 方法去执行,这个方法名虽然叫 shouldSkip,但是却干了不少实事。

这个方法我会在下篇文章中和小伙伴们分享 AOP 的创建过程中再和大家详解,这里先说一句,这个方法会把各种 Aspect Bean 都收集整理起来,将来根据这些 Bean 去生成 Advisor。

好啦,这就是 resolveBeforeInstantiation 方法的作用,感兴趣的小伙伴可以自己 DEBUG 看一些哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/804330.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp 微信小程序:v-model双向绑定问题(自定义 props 名无效)

uniapp 微信小程序&#xff1a;v-model双向绑定问题&#xff08;自定义 props 名无效&#xff09; 前言问题双向绑定示例使用 v-model使用 v-bind v-on使用 sync 修饰符 参考资料 前言 VUE中父子组件传递数据的基本套路&#xff1a; 父传子 props子传父 this.$emit(事件名, …

Linux 终端生成二维码

1、安装qrencode [rootnode1 script]# yum -y install qrencode2、输出正常的 [rootnode1 ~]# echo https://www.github.com|qrencode -o - -t utf83、输出彩色的 [rootnode1 ~]# qrencode -t utf8 -s 1 https://www.github.com|lolcatPS&#xff1a;没有lolcat命令 #由于…

动静态网页、Django创建表关系、Django框架的请求生命周期流程图

一、request对象的几个方法 在视图函数中写方法的时候&#xff0c;都会有一个形参requestdef index(request):passrequest.method # GET POST request.GET.get() # 它获取最后一个元素值 request.GET.getlist() # 获取到所有的request.POST.get() # 它获取最后一个元素值 req…

2023年Q2京东清洁电器行业分析报告(京东数据平台)

如今&#xff0c;消费者尤其是年轻一代对智能产品替代繁琐家务劳动的需求日趋强烈&#xff0c;这激发了对清洁电器产品需求的增长。随着各类清洁电器的热度持续增长&#xff0c;我国清洁电器的零售额也在逐年攀升。 接下来结合鲸参谋平台数据&#xff0c;我们一起来分析当前清洁…

算法----二叉搜索树中第K小的元素

题目 二叉搜索树中第K小的元素 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 个最小元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,1,4,null,2], k 1 输出&#xff…

练习四答案

练习2答案 构建数据库 数据库 数据表 answer开头表为对应题号答案形成的数据表 表结构 表数据 答案&#xff1a; 1、编写脚本&#xff0c;创建mis数据库&#xff0c;创建dept&#xff0c;创建employee表 SQL语句 CREATE TABLE dept ( …

HCIP——OSPF的重发布以及路由策略

OSPF重发布以及路由策略 一、重发布1、作用2、条件3、规则4、名词解释点向 5、单点重发布6、双点重发布 二、路由策略1、控制层流量和数据层流量2、抓流量ACL列表前缀列表 (ip-prefix) 3、做策略过滤策略(filter-policy)路由策略(route-policy) 一、重发布 在同一个网络拓扑结…

工欲善其事必先利其器,IT工作电脑更要维护好

目录 一&#xff1a;电脑的组成 二&#xff1a;维护措施 三&#xff1a;助力记忆 一&#xff1a;电脑的组成 当谈到电脑主机时&#xff0c;我们通常指的是电脑的中央处理器(CPU)、内存、主板、电源、硬盘、显卡、声卡、网卡等核心部件组成的整体。这些部件共同协作&#xff…

CSS 高频按钮样式

矩形与圆角按钮 正常而言&#xff0c;我们遇到的按钮就这两种 -- 矩形和圆角&#xff1a; 它们非常的简单&#xff0c;宽高和圆角和背景色。 <div classbtn rect>rect</div><div classbtn circle>circle</div>.btn {margin: 8px auto;flex-shrink: 0;…

率土抽卡助手 微信小程序开发

欢迎使用 微信率土抽卡助手 你好&#xff01;率土抽卡助手是包含全赛季土地难度表、开荒阵容、武将阵容、主城守军阵容推荐、内政、荣誉值计算、拆迁队、队伍克制、沃土坐标、鱼塘坐标、武将寻访技巧、行军外观判断队伍、T0阵容、战法拆解、卡包抽取等全功能小程序。可在微信游…

机器人状态估计:robot_localization 功能包简介与安装

机器人状态估计&#xff1a;robot_localization 功能包简介与参数配置 前言功能包简介安装使用ubuntu软件源安装使用源码安装 前言 移动机器人的状态估计需要用到很多传感器&#xff0c;因为对单一的传感器来讲&#xff0c;都存在各自的优缺点&#xff0c;所以需要一种多传感器…

Linux下CMake开发

CMake编译和运行C文件 编写CMakeLists.txt # 声明要求的 cmake 最低版本 cmake_minimum_required( VERSION 3.1 )# 声明一个 cmake 工程 project( pro )# 设置编译模式 set( CMAKE_BUILD_TYPE "Release" )#添加OPENCV库 #指定OpenCV版本&#xff0c;代码如下 #find…

vue2+wangEditor5富文本编辑器(图片视频自定义上传七牛云/服务器)

1、安装使用 安装 yarn add wangeditor/editor # 或者 npm install wangeditor/editor --save yarn add wangeditor/editor-for-vue # 或者 npm install wangeditor/editor-for-vue --save在main.js中引入样式 import wangeditor/editor/dist/css/style.css在使用编辑器的页…

压缩算法的原理丨基因型vcf文件为什么压缩后发生了什么?

压缩算法的本质 最近碰到一个神奇的现象&#xff0c;一份大小为16GB的xx.vcf.gz文件&#xff0c;解压之后体积变为600GB的vcf文件&#xff0c;为什么一份文件经过压缩后体积缩小了这么多&#xff1f; (work) [bio notes 21:29:40 ~/work/20230726/data]$ ls -lh总用量 620GB-…

mac m1安装Centos9

先看结果&#xff08;在mac M1 安装centos8 安装不成功的原因大部分是没有找到正确的系统&#xff09; 由于Cnetos8 停服&#xff0c;现有mac m1 上能够按照的Centos8 并非由官方发布&#xff0c;因此寻找官方发布的能够在mac m1上安装的centos版本。 在YouTuBe上找到一个视频…

华为产品测评官-开发者之声 +【小白的CodeArts产品体验(Devops实践)】

关于使用华为云Codearts产品中产品优点以及实践中出现的问题 一、Devops简介二、产品的优点1. 直观化的思维导图2. 迭代详情3. 合理的项目工作流程4. 合理的分支请求5. 可视化的代码问题统计图6. 报告的完整性7.流水线视图 三、实践中的小坑第一个小坑第二个小坑第三个小坑&…

点击按钮横向滚动左右滚动

展示 原理 隐藏超过部分&#xff0c;通过设置元素左侧的滚动距离&#xff0c;使元素向右向左滚动 1.scrollLeft&#xff1a;元素左侧已滚动的距离&#xff0c;即位于元素左边界与元素中当前可见内容的最左端之间的距离 2.scrollWidth:元素的整体宽度&#xff0c;包括由于溢出…

现代C++中的从头开始深度学习:激活函数

一、说明 让我们通过在C中实现激活函数来获得乐趣。人工神经网络是生物启发模型的一个例子。在人工神经网络中&#xff0c;称为神经元的处理单元被分组在计算层中&#xff0c;通常用于执行模式识别任务。 在这个模型中&#xff0c;我们通常更喜欢控制每一层的输出以服从一些约束…

详解python中的垃圾回收机制

目录 什么是垃圾回收机制 垃圾回收的工作流程 为什么要进行垃圾回收 详解python中的垃圾回收机制 总结 什么是垃圾回收机制 垃圾回收&#xff08;Garbage Collection&#xff09;是一种自动内存管理机制&#xff0c;用于检测和释放不再被程序使用的内存资源&#xff0c;以…

【数据结构】实验十一:图

实验十一 图 一、实验目的与要求 1&#xff09;掌握图的存储表示与操作实现。 2&#xff09;掌握图的连通性及其应用。 二、 实验内容 1.用邻接表存储一个图形结构&#xff0c;并计算每个顶点的度。 2. 采用深度和广度优先搜索算法&#xff0c;遍历上述这张图&#xff0c;…