Spring 容器原始 Bean 是如何创建的?
以下内容基于 Spring6.0.4。
这个话题其实非常庞大,我本来想从 getBean 方法讲起,但一想这样讲完估计很多小伙伴就懵了,所以我们还是一步一步来,今天我主要是想和小伙伴们讲讲 Spring 容器创建 Bean 最最核心的 createBeanInstance 方法,这个方法专门用来创建一个原始 Bean 实例。
松哥这里就以 Spring 源码中方法的执行顺序为例来和小伙伴们分享。
1. doCreateBean
AbstractAutowireCapableBeanFactory#doCreateBean 就是 Bean 的创建方法,但是 Bean 的创建涉及到的步骤非常多,包括各种需要调用的前置后置处理器方法,今天我主要是想和大家聊聊单纯的创建 Bean 的过程,其他方法咱们后面文章继续。
在 doCreateBean 方法中,有如下一行方法调用:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); //... return exposedObject; }
createBeanInstance 这个方法就是真正的根据我们的配置去创建一个 Bean 了。
2. createBeanInstance
先来看源码:
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // Make sure bean class is actually resolved at this point. Class<!--?--> beanClass = resolveBeanClass(mbd, beanName); if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); } Supplier<!--?--> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // Shortcut when re-creating the same bean... boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { if (autowireNecessary) { return autowireConstructor(beanName, mbd, null, null); } else { return instantiateBean(beanName, mbd); } } // Candidate constructors for autowiring? Constructor<!--?-->[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // Preferred constructors for default construction? ctors = mbd.getPreferredConstructors(); if (ctors != null) { return autowireConstructor(beanName, mbd, ctors, null); } // No special handling: simply use no-arg constructor. return instantiateBean(beanName, mbd); }
这里就是核心的 Bean 的创建方法了,因此这个方法我来和大家详细分析一下。
2.1 resolveBeanClass
这个方法是用来解析出来当前的 beanClass 对象,它的核心逻辑就是根据我们在 XML 文件中配置的类的全路径,通过反射加载出来这个 Class:
@Nullable protected Class<!--?--> resolveBeanClass(RootBeanDefinition mbd, String beanName, Class<!--?-->... typesToMatch) throws CannotLoadBeanClassException { if (mbd.hasBeanClass()) { return mbd.getBeanClass(); } return doResolveBeanClass(mbd, typesToMatch); }
首先会调用 mbd.hasBeanClass()
方法去判断是否已经通过反射加载出来 beanClass 了,如果加载出来了就直接返回,没有加载的话,就继续执行下面的 doResolveBeanClass 去加载。
> 什么时候会走 if 这条线呢?松哥举一个例子,如果我们设置某一个 Bean 的 Scope 是 prototype 的话,那么当第二次获取该 Bean 的实例的时候,就会走 if 这条线。
@Nullable private Class<!--?--> doResolveBeanClass(RootBeanDefinition mbd, Class<!--?-->... typesToMatch) throws ClassNotFoundException { //... String className = mbd.getBeanClassName(); if (className != null) { Object evaluated = evaluateBeanDefinitionString(className, mbd); if (!className.equals(evaluated)) { // A dynamically resolved expression, supported as of 4.2... if (evaluated instanceof Class<!--?--> clazz) { return clazz; } else if (evaluated instanceof String str) { className = str; freshResolve = true; } else { throw new IllegalStateException("Invalid class name expression result: " + evaluated); } } if (freshResolve) { // When resolving against a temporary class loader, exit early in order // to avoid storing the resolved Class in the bean definition. if (dynamicLoader != null) { return dynamicLoader.loadClass(className); } return ClassUtils.forName(className, dynamicLoader); } } // Resolve regularly, caching the result in the BeanDefinition... return mbd.resolveBeanClass(beanClassLoader); }
按理说,根据我们配置的类的全路径加载出来一个 Class 应该是非常容易的,直接 Class.forName 就可以了。
但是!!!
如果对 Spring 用法比较熟悉的小伙伴就知道,配置 Class 全路径的时候,我们不仅可以像下面这样老老实实配置:
<bean class="org.javaboy.bean.Book" />
我们甚至可以使用 SpEL 来配置 Bean 名称,例如我有如下类:
public class BeanNameUtils { public String getName() { return "org.javaboy.bean.User"; } }
这里有一个 getName 方法,这个方法返回的是一个类的全路径,现在我们在 XML 文件中可以这样配置:
<bean class="org.javaboy.bean.BeanNameUtils" id="beanNameUtils" /> <bean class="#{beanNameUtils.name}" id="user" />
在 XML 的 class 属性中,我们可以直接使用 SpEL 去引用一个方法的执行,用该方法的返回值作为 class 的值。
了解了 Spring 中的这个玩法,再去看上面的源码就很好懂了:
- 首先调用
mbd.getBeanClassName();
去获取到类路径。 - 接下来调用
evaluateBeanDefinitionString
方法进行 SpEL 运算,这个运算的目的是为了解析 className 中的 SpEL 表达式,当然,一般情况下 className 就是一个普通的字符串,不是 SpEL 表达式,那么解析完成之后就还是原本的字符串。如果是 className 是一个 SpEL,那么合法的解析结果分为两种:- 首先就是解析之后拿到了一个 Class,那这个就是我们想要的结果,直接返回即可。
- 要么就是解析出来是一个字符串,松哥上面举的例子就是这种情况,那么就把这个字符串赋值给 className,并且将 freshResolve 属性设置为 true,然后在接下来的 if 分支中去加载 Class。
当然,上面这些都是处理特殊情况,一般我们配置的普通 Bean,都是直接走最后一句 mbd.resolveBeanClass(beanClassLoader)
,这个方法的逻辑其实很好懂,我把代码贴出来小伙伴们来瞅一瞅:
@Nullable public Class<!--?--> resolveBeanClass(@Nullable ClassLoader classLoader) throws ClassNotFoundException { String className = getBeanClassName(); if (className == null) { return null; } Class<!--?--> resolvedClass = ClassUtils.forName(className, classLoader); this.beanClass = resolvedClass; return resolvedClass; }
这个方法就相当直白了,根据 className 加载出来 Class 对象,然后给 beanClass 属性也设置上值,这就和一开始的 if (mbd.hasBeanClass())
对应上了。
好了,到此,我们总算是根据 className 拿到 Class 对象了。
2.2 Supplier 和 factory-method
好了,回到一开始的源码中,接下来该执行如下两行代码了:
Supplier<!--?--> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); }
这两个松哥在前面的文章中和小伙伴们已经讲过了(Spring5 中更优雅的第三方 Bean 注入):前面的 obtainFromSupplier 方法是 Spring5 开始推出来的 Supplier,通过回调的方式去获取一个对象;第二个方法 instantiateUsingFactoryMethod 则是通过配置的 factory-method 来获取到一个 Bean 实例。
对这两个方法不熟悉的小伙伴可以参考前面的文章:Spring5 中更优雅的第三方 Bean 注入。
2.3 re-create 逻辑
继续回到一开始的源码中,接下来是一段 re-create 的处理逻辑,如下:
boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { if (autowireNecessary) { return autowireConstructor(beanName, mbd, null, null); } else { return instantiateBean(beanName, mbd); } }
根据前面的介绍,我们现在已经获取到 Class 对象了,接下来直接调用相应的构造方法就可以获取到 Bean 实例了。但是这个 Class 对象可能存在多个构造方法,所以还需要一堆流程去确定到底调用哪个构造方法。
所以这里会先去判断 resolvedConstructorOrFactoryMethod 是否不为空,不为空的话,说明这个 Bean 之前已经创建过了,该用什么方法创建等等问题都已经确定了,所以这次就不用重新再去确定了(resolved = true
)。另一方面,autowireNecessary 表示构造方法的参数是否已经处理好了,这个属性为 true 则表示构造方法的参数已经处理好了,那么就可以调用 autowireConstructor 方法去创建一个 Bean 出来,否则调用 instantiateBean 方法初始化 Bean。
这里涉及到的 autowireConstructor 和 instantiateBean 方法我们先不细说了,因为在后面还会再次涉及到。
2.4 构造器注入
继续回到一开始的源码中,接下来就是针对各种处理器的预处理了:
Constructor<!--?-->[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); }
先来看 determineConstructorsFromBeanPostProcessors 方法,这个方法主要是考虑到你可能提供了 SmartInstantiationAwareBeanPostProcessor,松哥在前面的文章中和大家专门讲过 BeanPostProcessor(BeanFactoryPostProcessor 和 BeanPostProcessor 有什么区别?),这里的 SmartInstantiationAwareBeanPostProcessor 算是 BeanPostProcessor 的一种,也是 Bean 的一种增强器。SmartInstantiationAwareBeanPostProcessor 中有一个 determineCandidateConstructors 方法,这个方法返回某一个 Bean 的构造方法,将来可以通过这个构造方法初始化某一个 Bean。
我给大家举一个简单例子,假设我有如下类:
public class User { private String username; private String address; public User() { System.out.println("=====no args====="); } public User(ObjectProvider<string> username) { System.out.println("args==username"); this.username = username.getIfAvailable(); } //省略 getter/setter/toString }
现在我在 Spring 容器中注册这个对象:
<bean class="org.javaboy.bean.User" id="user"> </bean>
按照我们已有的知识,这个将来会调用 User 的无参构造方法去完成 User 对象的初始化。
但是现在,假设我添加如下一个处理器:
public class MySmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor { @Override public Constructor<!--?-->[] determineCandidateConstructors(Class<!--?--> beanClass, String beanName) throws BeansException { if ("user".equals(beanName)) { Constructor<!--?--> constructor = null; try { constructor = beanClass.getConstructor(ObjectProvider.class); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } return new Constructor[]{constructor}; } return SmartInstantiationAwareBeanPostProcessor.super.determineCandidateConstructors(beanClass, beanName); } }
在 determineCandidateConstructors 方法中,返回一个有参构造方法,那么将来 Spring 容器会通过这里返回的有参构造方法去创建 User 对象,而不是通过无参构造方法去创建 User 对象。
最后,将这个处理器注册到 Spring 容器:
<bean class="org.javaboy.bean.MySmartInstantiationAwareBeanPostProcessor" />
现在,当我们启动 Spring 容器的时候,User 就是通过有参构造方法初始化的,而不是无参构造方法。之所以会这样,就是因为本小节一开始提到的源码 determineConstructorsFromBeanPostProcessors
,这个方法就是去查看有无 SmartInstantiationAwareBeanPostProcessor,如果有,就调用对应的方法找到处理器并返回。
这个弄懂之后,if 中其他几种情况就好理解了,mbd.getResolvedAutowireMode()
是查看当前对象的注入方式,这个一般是在 XML 中配置的,不过日常开发中我们一般不会配置这个属性,如果需要配置,方式如下:
<bean class="org.javaboy.bean.User" id="user" autowire="constructor"> </bean>
如果添加了 autowire="constructor"
就表示要通过构造方法进行注入,那么这里也会进入到 if 中。
if 里边剩下的几个条件都好说,就是看是否有配置构造方法参数,如果配置了,那么也直接调用相应的构造方法就行了。
这里最终执行的是 autowireConstructor 方法,这个方法比较长,我就不贴出来了,和大家说一说它的思路:
- 首先把能获取到的构造方法都拿出来,如果构造方法只有一个,且目前也没有任何和构造方法有关的参数,那就直接用这个构造方法就行了。
- 如果第一步不能解决问题,接下来就遍历所有的构造方法,并且和已有的参数进行参数数量和类型比对,找到合适的构造方法并调用。
2.5 PreferredConstructors
继续回到一开始的源码中,接下来是这样了:
ctors = mbd.getPreferredConstructors(); if (ctors != null) { return autowireConstructor(beanName, mbd, ctors, null); }
这块代码看字面好理解,就是获取到主构造方法,不过这个是针对 Kotlin 的,跟我们 Java 无关,我就不啰嗦了。
2.6 instantiateBean
最后就是 instantiateBean 方法了,这个方法就比较简单了,我把代码贴一下小伙伴们应该自己都能看明白:
protected BeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd) { try { Object beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this); BeanWrapper bw = new BeanWrapperImpl(beanInstance); initBeanWrapper(bw); return bw; } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex); } } @Override public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { // Don't override the class with CGLIB if no overrides. if (!bd.hasMethodOverrides()) { Constructor<!--?--> constructorToUse; synchronized (bd.constructorArgumentLock) { constructorToUse = (Constructor<!--?-->) bd.resolvedConstructorOrFactoryMethod; if (constructorToUse == null) { final Class<!--?--> clazz = bd.getBeanClass(); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { constructorToUse = clazz.getDeclaredConstructor(); bd.resolvedConstructorOrFactoryMethod = constructorToUse; } catch (Throwable ex) { throw new BeanInstantiationException(clazz, "No default constructor found", ex); } } } return BeanUtils.instantiateClass(constructorToUse); } else { // Must generate CGLIB subclass. return instantiateWithMethodInjection(bd, beanName, owner); } }
从上面小伙伴么可以看到,本质上其实就是调用了 constructorToUse = clazz.getDeclaredConstructor();
,获取到一个公开的无参构造方法,然后据此创建一个 Bean 实例出来。
3. 小结
好了,这就是 Spring 容器中 Bean 的创建过程,我这里单纯和小伙伴们分享了原始 Bean 的创建这一个步骤,这块内容其实非常庞杂,以后有空我会再和小伙伴们分享。
最后,给上面分析的方法生成了一个时序图,小伙伴们作为参考。
其实看 Spring 源码,松哥最大的感悟就是小伙伴们一定要了解 Spring 的各种用法,在此基础之上,源码就很好懂,如果你只会 Spring 一些基本用法,那么源码一定是看得云里雾里的。</string>

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
一种轻量级定时任务实现 | 京东云技术团队
现在市面上有各式各样的分布式定时任务,每个都有其独特的特点,我们这边的项目因为一开始使用的是分布式开源调度框架TBSchedule,但是这个框架依赖ZK,由于ZK的不稳定性和项目老旧无人维护,导致我们的定时任务会偶发出现异常,比如:任务停止、任务项丢失、任务不执行等; 每逢618大促,在单量很大的情况下,如果出现定时任务异常,会导致订单的积压,进而导致订单的履约时效,严重影响商家的履约效率,造成订单取消、客户投诉等; 为了保障整体的稳定性,在改动成本比较小的情况下,达到快速实现,稳定运行,预防这种偶发异常,我们实现了一种轻量级定时任务来进行无缝隙降级; 我们使用的TBSchedule特性: 1.支持集群、分布式 2.灵活的任务分片 3.动态的服务扩容和资源回收 4.支持单个任务线程数的设置和实时调整 我们要实现的功能 1.为了保障单个系统的稳定性,我们去中心化,单独调度自己的系统的任务 2.为了避免ZK的不稳定性,我们通过redis实现注册中心和动态分片功能 3.避免使用timer,改用线程池来控制线程 4.为了减少改造成本,不需要业务系统改动代码,我们自动实现TBSchedule内部...
- 下一篇
使用轻量级 CDC debezium-server-databend 构建实时数据同步
作者:韩山杰 Databend Cloud 研发工程师 https://github.com/hantmac Debezium Server Databend 是一个基于 Debezium Engine 自研的轻量级 CDC 项目,用于实时捕获数据库更改并将其作为事件流传递最终将数据写入目标数据库 Databend。它提供了一种简单的方式来监视和捕获关系型数据库的变化,并支持将这些变化转换为可消费事件。 使用 Debezium server databend 实现 CDC 无须依赖大型的 Data Infra 比如 Flink, Kafka, Spark 等,只需一个启动脚本即可开启实时数据同步。 这篇教程将展示如何基于 Debezium server databend 快速构建 MySQL 到 Databend 的实时数据同步。 假设我们有电子商务业务,商品的数据存储在 MySQL ,我们需要实时把它同步到 Databend 中。 接下来的内容将介绍如何使用 Debezium server databend CDC 来实现这个需求,系统的整体架构如下图所示: 准备阶段 准备一台已经安...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2全家桶,快速入门学习开发网站教程
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Windows10,CentOS7,CentOS8安装Nodejs环境
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker快速安装Oracle11G,搭建oracle11g学习环境