听说 Spring Bean 的创建还有一条捷径?
文章目录
- 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 {@Overridepublic 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);}@Overridepublic 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;}}//...
}
从这个地方开始,分成了两条线:
- 如果是一个切面 Bean 的话,则执行第一个方法 isInfrastructureClass 就可以返回 true 了。
- 如果是一个普通 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)));
}
这里的判断主要是两方面:
- 调用父类的方法去判断当前类是否和 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 中的老熟人了。
- 调用
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 看一些哦~
相关文章:

听说 Spring Bean 的创建还有一条捷径?
文章目录 1. resolveBeforeInstantiation1.1 applyBeanPostProcessorsBeforeInstantiation1.2 applyBeanPostProcessorsAfterInitialization1.3 案例 2. 源码实践2.1 切面 Bean2.2 普通 Bean 在 Spring Bean 的创建方法中,有如下一段代码: AbstractAutow…...

大数据课程E6——Flume的Processor
文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Processor的概念和配置参数; ⚪ 掌握Processor的使用方法; ⚪ 掌握Processor的Default Processo; ⚪ 掌握Processor的Load Balance Processo; 一、Failover Sink Processor 1. …...

实现邮箱管理之gmail邮箱、office365(Azure)邮箱之披荆斩棘问题一览
要进行Office365邮箱的授权对接,你需要先申请一个应用,并获取授权访问令牌。 以下是一个简单的步骤: 登录 Azure 门户:https://portal.azure.com/创建一个新的应用程序,或者使用现有的应用程序。要创建新的应用程序&…...

(AcWing)多重背包问题 I,II
有 N 种物品和一个容量是 V 的背包。 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 输出最大价值。 输入格式 第一行两个整数 N,…...

如何把几个视频合并在一起?视频合并方法分享
当我们需要制作一个比较长的视频时,将多个视频进行合并可以使得整个过程更加高效。此外,合并视频还可以避免出现“剪辑断层”的情况,使得视频内容更加连贯,更加容易被观众理解和接受。再有,合并视频还可以减少视频文件…...

【MyBatis】初学MyBatis
目录 MyBatis 是什么?MyBatis框架搭建1.添加MyBatis框架2.设置MyBatis配置数据库的相关链接信息xml 保存路径和命名格式 根据MyBatis写法完成数据库的操作MyBatis插件MyBatis传递参数查询${} 和 #{} 有什么区别?SQL注入问题 MyBatis like查询MyBatis多表…...

深度学习训练营之DCGAN网络学习
深度学习训练营之DCGAN网络学习 原文链接环境介绍DCGAN简单介绍生成器(Generator)判别器(Discriminator)对抗训练 前置工作导入第三方库导入数据数据查看 定义模型初始化权重定义生成器generator定义判别器 模型训练定义参数模型训…...

自定义MVC增删改查
目录 mymvcdemo是自定义mvc框架的使用示例 1.1 实体类 1.2 dao方法 1.3 写Service / biz 三层架构 1.4 建action 相当于selvert 1.5 con连接MySQL 8.0 版本 1.6 配置文件 XML 1.7 主界面布局 1.8 增加界面布局 1.9 写tld配置文件 2.0 注意架包 我是已经打包好的 mymv…...

RabbitMQ 教程 | 第2章 RabbitMQ 入门
👨🏻💻 热爱摄影的程序员 👨🏻🎨 喜欢编码的设计师 🧕🏻 擅长设计的剪辑师 🧑🏻🏫 一位高冷无情的编码爱好者 大家好,我是 DevO…...

双网卡如何配置DNS?我是一个仅主机模式配置静态(static)IP、一个NET或桥接(dhcp获取)
目录 一、所有主机初始化 二、135、136服务器,部署DNS调度服务器 1、更改主机主从DNS服务器的主机名称 2、安装bind软件、修改主配置文件 3、修改区域配置文件 4、修改数据文件 5、启动named服务、修改网卡信息 6、解析 7、双网卡的话记得注释以下内容、注…...

Android10: 动态隐藏导航栏和状态栏总结
(1)全屏相关设置 //(1)主题添加 <item name"android:windowFullscreen">true</item>//(2)setContentView之前添加 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCRE…...

roop 视频换脸
roop: one click face swap. 只用一张人脸图片,就能完成视频换脸。 项目地址: https://github.com/s0md3v/roopColab 部署: https://github.com/dream80/roop_colab 本文是本地部署的实践记录。 环境基础 OS: Ubuntu 22.04.2 LTSKernel: 5…...

Java类集框架(一)
目录 1.Collection集合接口 2.List 接口 (常用子类 ArrayList ,LinkedList,Vector) 3.Set 集合 接口(常用子类 HashSet LinkedHashSet,TreeSet) 4.集合输出(iterator , Enumeration) 1.Collection集合接口 Collection是集合中最大父接口,在接口中定义了核心的…...

Jsp+Ssh+Mysql实现的简单的企业物资信息管理系统项目源码附带视频指导运行教程
由jspssh(springstruts2mysql)实现的企业物资信息管理系统,系统功能比较简单,实现了基本的管理员、操作员等用户管理、物品分类管理、物品管理、入库管理、出库管理、库存预警、客户管理、供应商管理等基本功能需要的可以联系我分…...

【Spring】深究SpringBoot自动装配原理
文章目录 前言1、main入口2、SpringBootApplication3、EnableAutoConfiguration4、AutoConfigurationImportSelector4.1、selectImports()4.2、getAutoConfigurationEntry()4.3、getCandidateConfigurations()4.4、loadFactoryNames() 5、META-INF/spring.factories6、总结 前言…...

阿里云负载均衡SLB网络型NLB负载均衡架构性能详解
阿里云网络型负载均衡NLB是阿里云推出的新一代四层负载均衡,支持超高性能和自动弹性能力,单实例可以达到1亿并发连接,帮您轻松应对高并发业务。网络型负载均衡NLB具有超强性能、自动弹性伸缩、高可用、TCPSSL卸载、多场景流量分发和丰富的高级…...

JavaScript学习 -- SM4算法应用实例
SM4算法,也被称为国密算法,是中国公布的一种高效且安全的对称加密算法。在JavaScript中,我们可以通过使用CryptoJS库来实现SM4算法的加密和解密。本篇博客将为您介绍如何在JavaScript中使用SM4算法,并提供一个实际的案例。 首先&…...

【JVM】什么是双亲委派机制
文章目录 1、类加载机制2、双亲委派模型2.1、介绍2.2、为什么需要双亲委派2.3、源码解析 3、破坏双亲委派3.1、介绍3.2、破坏实现3.3、破坏双亲委派的例子 4、线程上下文类加载器 1、类加载机制 类加载阶段分为加载、连接、初始化三个阶段,而加载阶段需要通过类的全…...

网络安全 Day24-select高级用法和多表连接
select高级用法和多表连接 1. select 多子句单表高级实践1.1 select 多子句高级语法1.2 聚合函数1.3 group by 实践1.4 having 筛选1.5 order by 排序1.6 limit 2. 多表连接 1. select 多子句单表高级实践 1.1 select 多子句高级语法 where 和 having 区别是后者是分组后进行…...

JUC并发编程之volatile详解
目录 1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1.5 volatile 日常使用场景 送书活动 1. volatile 在并发编程中,多线程操作共享的变量时&a…...

swing布局详解
1. 布局管理器接口 (1)说明 布局管理器接口为LayoutManager和LayoutManager2,LayoutManager2是LayoutManager的子类。 (2)常用方法 方法描述LayoutManageraddLayoutComponent(String name, Component comp) removeL…...

el-table某一列嵌套使用el-popover,使用click触发,导致页面下拉框组件无法触发弹框关闭(解决办法)
在弹框触发的方法里加上document.body.click() 即可 尝试了很多其他的方法都没用,只有这个解决了 完整代码: <el-select change"sourceChange" clearable ><el-optionv-for"option in list1":key"option.code":…...

正泰电力携手图扑:VR 变电站事故追忆反演
VR(Virtual Reality,虚拟现实)技术作为近年来快速发展的一项新技术,具有广泛的应用前景,支持融合人工智能、机器学习、大数据等技术,实现更加智能化、个性化的应用。在电力能源领域,VR 技术在高性能计算机和专有设备支…...

报错 -bash: wget: command not found
1、报错 -bash: wget: command not found 可以重装 wget 工具: 卸载 wget 工具 yum remove wget下载 wget 工具 yum -y install wget最后尝试 wget “url” 又OK了,一般是原来的wget初始化有文件损坏造成的。...

HashMap扩容和Redis中Dict 扩容
扩容时机: Hash Map:要在某个临界点进行扩容处理,该临界点就是HashMap中元素的数量在数值上等于threshold(table数组长度*加载因子) Dict: 当每次新增键值对的时 , 会检测 负载因子(LoadFactor) , 判断以…...

【Redis】内存数据库Redis进阶(Redis持久化)
目录 分布式缓存 Redis 四大问题Redis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 A…...

在PHP8中检测数据类型-PHP8知识详解
在PHP 8中,可以使用多种方法来检测数据类型。以下是常用的四种方法:使用 gettype() 函数、使用 is_* 系列函数、使用 get_debug_type() 函数、使用 get_class() 函数。 一、使用 gettype() 函数 gettype() 函数返回给定变量的数据类型。例如:…...

amoeba实现MySQL读写分离
amoeba实现MySQL读写分离 准备环境:主机A和主机B作主从配置,IP地址为192.168.131.129和192.168.131.130,主机C作为中间件,也就是作为代理服务器,IP地址为192.168.131.136。三台服务器操作系统为RHEL6.4 x86_64,为…...

angr学习-入门篇
前言: 资源链接:GitHub - jakespringer/angr_ctf(题库仓库,里面有个讲解angr的PPT,里面有官方的题解很详细) GitHub - Hustcw/Angr_Tutorial_For_CTF: angr tutorial for ctf 安装: 关于angr…...

基于java SpringBoot和HTML的博客系统
随着网络技术渗透到社会生活的各个方面,传统的交流方式也面临着变化。互联网是一个非常重要的方向。基于Web技术的网络考试系统可以在全球范围内使用互联网,可以在本地或异地进行通信,大大提高了通信和交换的灵活性。在当今高速发展的互联网时…...