0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Spring容器原始Bean是如何创建的?Spring源码中方法的执行顺序

OSC开源社区 来源:江南一点雨 2023-08-04 10:12 次阅读

以下内容基于 Spring6.0.4。

这个话题其实非常庞大,我本来想从 getBean 方法讲起,但一想这样讲完估计很多小伙伴就懵了,所以我们还是一步一步来,今天我主要是想和小伙伴们讲讲 Spring 容器创建 Bean 最最核心的 createBeanInstance 方法,这个方法专门用来创建一个原始 Bean 实例。

这里就以 Spring 源码中方法的执行顺序为例来和小伙伴们分享。

1. doCreateBean

AbstractAutowireCapableBeanFactory#doCreateBean 就是 Bean 的创建方法,但是 Bean 的创建涉及到的步骤非常多,包括各种需要调用的前置后置处理器方法,今天我主要是想和大家聊聊单纯的创建 Bean 的过程,其他方法咱们后面文章继续。

在 doCreateBean 方法中,有如下一行方法调用:

protectedObjectdoCreateBean(StringbeanName,RootBeanDefinitionmbd,@NullableObject[]args)
throwsBeanCreationException{
//Instantiatethebean.
BeanWrapperinstanceWrapper=null;
if(mbd.isSingleton()){
instanceWrapper=this.factoryBeanInstanceCache.remove(beanName);
}
if(instanceWrapper==null){
instanceWrapper=createBeanInstance(beanName,mbd,args);
}
Objectbean=instanceWrapper.getWrappedInstance();
//...
returnexposedObject;
}

createBeanInstance 这个方法就是真正的根据我们的配置去创建一个 Bean 了。

2. createBeanInstance

先来看源码:

protectedBeanWrappercreateBeanInstance(StringbeanName,RootBeanDefinitionmbd,@NullableObject[]args){
//Makesurebeanclassisactuallyresolvedatthispoint.
ClassbeanClass=resolveBeanClass(mbd,beanName);
if(beanClass!=null&&!Modifier.isPublic(beanClass.getModifiers())&&!mbd.isNonPublicAccessAllowed()){
thrownewBeanCreationException(mbd.getResourceDescription(),beanName,
"Beanclassisn'tpublic,andnon-publicaccessnotallowed:"+beanClass.getName());
}
SupplierinstanceSupplier=mbd.getInstanceSupplier();
if(instanceSupplier!=null){
returnobtainFromSupplier(instanceSupplier,beanName);
}
if(mbd.getFactoryMethodName()!=null){
returninstantiateUsingFactoryMethod(beanName,mbd,args);
}
//Shortcutwhenre-creatingthesamebean...
booleanresolved=false;
booleanautowireNecessary=false;
if(args==null){
synchronized(mbd.constructorArgumentLock){
if(mbd.resolvedConstructorOrFactoryMethod!=null){
resolved=true;
autowireNecessary=mbd.constructorArgumentsResolved;
}
}
}
if(resolved){
if(autowireNecessary){
returnautowireConstructor(beanName,mbd,null,null);
}
else{
returninstantiateBean(beanName,mbd);
}
}
//Candidateconstructorsforautowiring?
Constructor[]ctors=determineConstructorsFromBeanPostProcessors(beanClass,beanName);
if(ctors!=null||mbd.getResolvedAutowireMode()==AUTOWIRE_CONSTRUCTOR||
mbd.hasConstructorArgumentValues()||!ObjectUtils.isEmpty(args)){
returnautowireConstructor(beanName,mbd,ctors,args);
}
//Preferredconstructorsfordefaultconstruction?
ctors=mbd.getPreferredConstructors();
if(ctors!=null){
returnautowireConstructor(beanName,mbd,ctors,null);
}
//Nospecialhandling:simplyuseno-argconstructor.
returninstantiateBean(beanName,mbd);
}

这里就是核心的 Bean 的创建方法了,因此这个方法我来和大家详细分析一下。

2.1 resolveBeanClass

这个方法是用来解析出来当前的 beanClass 对象,它的核心逻辑就是根据我们在 XML 文件中配置的类的全路径,通过反射加载出来这个 Class

@Nullable
protectedClassresolveBeanClass(RootBeanDefinitionmbd,StringbeanName,Class...typesToMatch)
throwsCannotLoadBeanClassException{
if(mbd.hasBeanClass()){
returnmbd.getBeanClass();
}
returndoResolveBeanClass(mbd,typesToMatch);
}

首先会调用 mbd.hasBeanClass() 方法去判断是否已经通过反射加载出来 beanClass 了,如果加载出来了就直接返回,没有加载的话,就继续执行下面的 doResolveBeanClass 去加载。

什么时候会走 if 这条线呢?松哥举一个例子,如果我们设置某一个 Bean 的 Scope 是 prototype 的话,那么当第二次获取该 Bean 的实例的时候,就会走 if 这条线。

@Nullable
privateClassdoResolveBeanClass(RootBeanDefinitionmbd,Class...typesToMatch)
throwsClassNotFoundException{
//...
StringclassName=mbd.getBeanClassName();
if(className!=null){
Objectevaluated=evaluateBeanDefinitionString(className,mbd);
if(!className.equals(evaluated)){
//Adynamicallyresolvedexpression,supportedasof4.2...
if(evaluatedinstanceofClassclazz){
returnclazz;
}
elseif(evaluatedinstanceofStringstr){
className=str;
freshResolve=true;
}
else{
thrownewIllegalStateException("Invalidclassnameexpressionresult:"+evaluated);
}
}
if(freshResolve){
//Whenresolvingagainstatemporaryclassloader,exitearlyinorder
//toavoidstoringtheresolvedClassinthebeandefinition.
if(dynamicLoader!=null){
returndynamicLoader.loadClass(className);
}
returnClassUtils.forName(className,dynamicLoader);
}
}
//Resolveregularly,cachingtheresultintheBeanDefinition...
returnmbd.resolveBeanClass(beanClassLoader);
}

按理说,根据我们配置的类的全路径加载出来一个 Class 应该是非常容易的,直接 Class.forName 就可以了。

但是!!!

如果对 Spring 用法比较熟悉的小伙伴就知道,配置 Class 全路径的时候,我们不仅可以像下面这样老老实实配置:


我们甚至可以使用 SpEL 来配置 Bean 名称,例如我有如下类:

publicclassBeanNameUtils{
publicStringgetName(){
return"org.javaboy.bean.User";
}
}

这里有一个 getName 方法,这个方法返回的是一个类的全路径,现在我们在 XML 文件中可以这样配置:



在 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
publicClassresolveBeanClass(@NullableClassLoaderclassLoader)throwsClassNotFoundException{
StringclassName=getBeanClassName();
if(className==null){
returnnull;
}
ClassresolvedClass=ClassUtils.forName(className,classLoader);
this.beanClass=resolvedClass;
returnresolvedClass;
}

这个方法就相当直白了,根据 className 加载出来 Class 对象,然后给 beanClass 属性也设置上值,这就和一开始的 if (mbd.hasBeanClass()) 对应上了。

好了,到此,我们总算是根据 className 拿到 Class 对象了。

2.2 Supplier 和 factory-method

好了,回到一开始的源码中,接下来该执行如下两行代码了:

SupplierinstanceSupplier=mbd.getInstanceSupplier();
if(instanceSupplier!=null){
returnobtainFromSupplier(instanceSupplier,beanName);
}
if(mbd.getFactoryMethodName()!=null){
returninstantiateUsingFactoryMethod(beanName,mbd,args);
}

这两个松哥在前面的文章中和小伙伴们已经讲过了(Spring5 中更优雅的第三方 Bean 注入):前面的 obtainFromSupplier 方法是 Spring5 开始推出来的 Supplier,通过回调的方式去获取一个对象;第二个方法 instantiateUsingFactoryMethod 则是通过配置的 factory-method 来获取到一个 Bean 实例。

对这两个方法不熟悉的小伙伴可以参考前面的文章:Spring5 中更优雅的第三方 Bean 注入。

2.3 re-create 逻辑

继续回到一开始的源码中,接下来是一段 re-create 的处理逻辑,如下:

booleanresolved=false;
booleanautowireNecessary=false;
if(args==null){
synchronized(mbd.constructorArgumentLock){
if(mbd.resolvedConstructorOrFactoryMethod!=null){
resolved=true;
autowireNecessary=mbd.constructorArgumentsResolved;
}
}
}
if(resolved){
if(autowireNecessary){
returnautowireConstructor(beanName,mbd,null,null);
}
else{
returninstantiateBean(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)){
returnautowireConstructor(beanName,mbd,ctors,args);
}

先来看 determineConstructorsFromBeanPostProcessors 方法,这个方法主要是考虑到你可能提供了 SmartInstantiationAwareBeanPostProcessor,松哥在前面的文章中和大家专门讲过 BeanPostProcessor(BeanFactoryPostProcessor 和 BeanPostProcessor 有什么区别?),这里的 SmartInstantiationAwareBeanPostProcessor 算是 BeanPostProcessor 的一种,也是 Bean 的一种增强器。SmartInstantiationAwareBeanPostProcessor 中有一个 determineCandidateConstructors 方法,这个方法返回某一个 Bean 的构造方法,将来可以通过这个构造方法初始化某一个 Bean。

我给大家举一个简单例子,假设我有如下类:

publicclassUser{
privateStringusername;
privateStringaddress;

publicUser(){
System.out.println("=====noargs=====");
}

publicUser(ObjectProviderusername){
System.out.println("args==username");
this.username=username.getIfAvailable();
}

//省略getter/setter/toString
}

现在我在 Spring 容器中注册这个对象:



按照我们已有的知识,这个将来会调用 User 的无参构造方法去完成 User 对象的初始化。

但是现在,假设我添加如下一个处理器:

publicclassMySmartInstantiationAwareBeanPostProcessorimplementsSmartInstantiationAwareBeanPostProcessor{
@Override
publicConstructor[]determineCandidateConstructors(ClassbeanClass,StringbeanName)throwsBeansException{
if("user".equals(beanName)){
Constructorconstructor=null;
try{
constructor=beanClass.getConstructor(ObjectProvider.class);
}catch(NoSuchMethodExceptione){
thrownewRuntimeException(e);
}
returnnewConstructor[]{constructor};
}
returnSmartInstantiationAwareBeanPostProcessor.super.determineCandidateConstructors(beanClass,beanName);
}
}

在 determineCandidateConstructors 方法中,返回一个有参构造方法,那么将来 Spring 容器会通过这里返回的有参构造方法去创建 User 对象,而不是通过无参构造方法去创建 User 对象。

最后,将这个处理器注册到 Spring 容器:


现在,当我们启动 Spring 容器的时候,User 就是通过有参构造方法初始化的,而不是无参构造方法。之所以会这样,就是因为本小节一开始提到的源码 determineConstructorsFromBeanPostProcessors,这个方法就是去查看有无 SmartInstantiationAwareBeanPostProcessor,如果有,就调用对应的方法找到处理器并返回。

这个弄懂之后,if 中其他几种情况就好理解了,mbd.getResolvedAutowireMode() 是查看当前对象的注入方式,这个一般是在 XML 中配置的,不过日常开发中我们一般不会配置这个属性,如果需要配置,方式如下:



如果添加了 autowire="constructor" 就表示要通过构造方法进行注入,那么这里也会进入到 if 中。

if 里边剩下的几个条件都好说,就是看是否有配置构造方法参数,如果配置了,那么也直接调用相应的构造方法就行了。

这里最终执行的是 autowireConstructor 方法,这个方法比较长,我就不贴出来了,和大家说一说它的思路:

首先把能获取到的构造方法都拿出来,如果构造方法只有一个,且目前也没有任何和构造方法有关的参数,那就直接用这个构造方法就行了。

如果第一步不能解决问题,接下来就遍历所有的构造方法,并且和已有的参数进行参数数量和类型比对,找到合适的构造方法并调用。

2.5 PreferredConstructors

继续回到一开始的源码中,接下来是这样了:

ctors=mbd.getPreferredConstructors();
if(ctors!=null){
returnautowireConstructor(beanName,mbd,ctors,null);
}

这块代码看字面好理解,就是获取到主构造方法,不过这个是针对 Kotlin 的,跟我们 Java 无关,我就不啰嗦了。

2.6 instantiateBean

最后就是 instantiateBean 方法了,这个方法就比较简单了,我把代码贴一下小伙伴们应该自己都能看明白:

protectedBeanWrapperinstantiateBean(StringbeanName,RootBeanDefinitionmbd){
try{
ObjectbeanInstance=getInstantiationStrategy().instantiate(mbd,beanName,this);
BeanWrapperbw=newBeanWrapperImpl(beanInstance);
initBeanWrapper(bw);
returnbw;
}
catch(Throwableex){
thrownewBeanCreationException(mbd.getResourceDescription(),beanName,ex.getMessage(),ex);
}
}
@Override
publicObjectinstantiate(RootBeanDefinitionbd,@NullableStringbeanName,BeanFactoryowner){
//Don'toverridetheclasswithCGLIBifnooverrides.
if(!bd.hasMethodOverrides()){
ConstructorconstructorToUse;
synchronized(bd.constructorArgumentLock){
constructorToUse=(Constructor)bd.resolvedConstructorOrFactoryMethod;
if(constructorToUse==null){
finalClassclazz=bd.getBeanClass();
if(clazz.isInterface()){
thrownewBeanInstantiationException(clazz,"Specifiedclassisaninterface");
}
try{
constructorToUse=clazz.getDeclaredConstructor();
bd.resolvedConstructorOrFactoryMethod=constructorToUse;
}
catch(Throwableex){
thrownewBeanInstantiationException(clazz,"Nodefaultconstructorfound",ex);
}
}
}
returnBeanUtils.instantiateClass(constructorToUse);
}
else{
//MustgenerateCGLIBsubclass.
returninstantiateWithMethodInjection(bd,beanName,owner);
}
}

从上面小伙伴么可以看到,本质上其实就是调用了 constructorToUse = clazz.getDeclaredConstructor();,获取到一个公开的无参构造方法,然后据此创建一个 Bean 实例出来。

3. 小结

好了,这就是 Spring 容器中 Bean 的创建过程,我这里单纯和小伙伴们分享了原始 Bean 的创建这一个步骤,这块内容其实非常庞杂,以后有空我会再和小伙伴们分享。

最后,给上面分析的方法生成了一个时序图,小伙伴们作为参考。

8ddfb090-31ee-11ee-9e74-dac502259ad0.png

其实看 Spring 源码,松哥最大的感悟就是小伙伴们一定要了解 Spring 的各种用法,在此基础之上,源码就很好懂,如果你只会 Spring 一些基本用法,那么源码一定是看得云里雾里的。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    18297

    浏览量

    222206
  • XML技术
    +关注

    关注

    0

    文章

    15

    浏览量

    5990
  • Beacon技术
    +关注

    关注

    0

    文章

    6

    浏览量

    6226
  • spring框架
    +关注

    关注

    0

    文章

    7

    浏览量

    2023

原文标题:Spring容器原始Bean是如何创建的?

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    java spring教程

    Spring核心概念介绍控制反转(IOC)依赖注入(DI)集合对象注入等Bean的管理BeanFactoryApplicationContextSpring 在web中的使用
    发表于 09-11 11:09

    什么是java spring

    的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建
    发表于 09-11 11:16

    spring实例

    ;UTF-8"?><!DOCTYPE beans PUBLIC"-//SPRING//DTD BEAN//EN""http://www.springframework.org
    发表于 09-11 11:22

    怎么阅读Spring源码

    封装,很有可能在读源码的过程中掉到各种细节里出不来,所以读这种源码要事无巨细,理解原理即可。基本原理其实就是通过反射解析类及其类的各种信息,包括构造器、方法及其参数,属性。然后将其封装成bea
    发表于 05-04 15:21

    三大框架之Spring

    Spring:没有出现Spring之前,各层之间的访问,需要在调用层中创建被调用层的对象,使用该对象去调用相应的方法,这样层与层之间的耦合性(紧密度)就会加强,日后在修改代码的时候,会
    发表于 05-27 07:21

    Spring工作原理

    的依赖关系核心:bean工厂;在Spring中,bean工厂创建的各个实例称作bean二.AOP(Aspect-Oriented Progr
    发表于 07-10 07:41

    Spring笔记分享

    框架:高度抽取,可重用代码的一种设计高度的通用性多个可重用模块的集合,形成某个领域的整体解决方案Spring => 容器框架包含并管理应用对象的生命周期IOC和SOP容器框架容器 =&
    发表于 11-04 07:51

    Spring认证」Spring Hello World 项目示例

    ()。该 API 加载 bean 配置文件,并最终基于提供的 API,负责创建和初始化所有对象,即配置文件中提到的 bean。第二步用于使用创建的上下文的getBean()
    发表于 08-17 13:49

    Spring Boot嵌入式Web容器原理是什么

    Spring Boot嵌入式Web容器原理Spring Boot的目标是构建“非常容易创建、独立、产品级别的基于Spring的应用”。这些应
    发表于 12-16 07:57

    Spring应用 1 springXML配置说明

    Spring应用 1 springXML配置说明 隐式对Spring容器注册Process   context:annotation-config / 为了在spring开发过程中,为
    发表于 01-13 12:20 317次阅读

    解析加载及实例化Bean顺序(零配置)

    的@AutoConfigureAfter注解,手动的指定Bean的实例化顺序。 了解SpringBean的解析,加载和实例化顺序机制有助于
    的头像 发表于 08-04 16:08 1143次阅读

    Spring认证」Spring IoC 容器

    Spring 容器Spring 框架的核心容器创建对象,将它们连接到配置中,并管理它们从创建
    的头像 发表于 06-28 13:27 505次阅读
    「<b class='flag-5'>Spring</b>认证」<b class='flag-5'>Spring</b> IoC <b class='flag-5'>容器</b>

    bean放入Spring容器中有哪些方式

    bean放入Spring容器中有哪些方式?
    的头像 发表于 09-19 15:25 558次阅读

    浅谈Spring事务底层原理

    开启Spring事务本质上就是增加了一个Advisor,但我们使用@EnableTransactionManagement注解来开启Spring事务是,该注解代理的功能就是向Spring容器
    的头像 发表于 12-06 09:56 514次阅读

    Spring Dependency Inject与Bean Scops注解

    DependsOn`注解可以配置Spring IoC容器在初始化一个Bean之前,先初始化其他的Bean对象。下面是此注解使用示例代码:
    的头像 发表于 04-07 11:35 477次阅读
    <b class='flag-5'>Spring</b> Dependency Inject与<b class='flag-5'>Bean</b> Scops注解