您当前的位置:首页 > 电脑百科 > 程序开发 > 容器

Spring容器原始Bean是如何创建的?

时间:2023-08-03 14:58:55  来源:  作者:OSC开源社区

以下内容基于 Spring6.0.4。

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

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

1. doCreateBean

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

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

protectedObject doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)

throwsBeanCreationException {

// 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;

//...

returnexposedObject;

}

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

2. createBeanInstance

先来看源码:

protectedBeanWrapper 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) {

thrownewBeanCreationException(mbd.getResourceDeion, beanName,

"Bean class isn't public, and non-public access not allowed: "+ beanClass.getName);

}

Supplier<?> instanceSupplier = mbd.getInstanceSupplier;

if(instanceSupplier != null) {

returnobtAInFromSupplier(instanceSupplier, beanName);

}

if(mbd.getFactoryMethodName != null) {

returninstantiateUsingFactoryMethod(beanName, mbd, args);

}

// Shortcut when re-creating the same bean...

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);

}

}

// Candidate constructors for autowiring?

Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);

if(ctors != null|| mbd.getResolvedAutowireMode == AUTOWIRE_CONSTRUCTOR ||

mbd.hasConstructorArgumentValues || !ObjectUtils.isEmpty(args)) {

returnautowireConstructor(beanName, mbd, ctors, args);

}

// Preferred constructors for default construction?

ctors = mbd.getPreferredConstructors;

if(ctors != null) {

returnautowireConstructor(beanName, mbd, ctors, null);

}

// No special handling: simply use no-arg constructor.

returninstantiateBean(beanName, mbd);

}

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

2.1 resolveBeanClass

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

@Nullable

protectedClass<?> resolveBeanClass(RootBeanDefinition mbd, String beanName, Class<?>... typesToMatch)

throwsCannotLoadBeanClassException {

if(mbd.hasBeanClass) {

returnmbd.getBeanClass;

}

returndoResolveBeanClass(mbd, typesToMatch);

}

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

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

@Nullable

privateClass<?> doResolveBeanClass(RootBeanDefinition mbd, Class<?>... typesToMatch)

throwsClassNotFoundException {

//...

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 instanceofClass<?> clazz) {

returnclazz;

}

elseif(evaluated instanceofString str) {

className = str;

freshResolve = true;

}

else{

thrownewIllegalStateException( "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) {

returndynamicLoader.loadClass(className);

}

returnClassUtils.forName(className, dynamicLoader);

}

}

// Resolve regularly, caching the result in the BeanDefinition...

returnmbd.resolveBeanClass(beanClassLoader);

}

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

但是!!!

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

< beanclass= "org.JAVAboy.bean.Book"/>

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

publicclassBeanNameUtils{

publicString getName{

return"org.javaboy.bean.User";

}

}

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

< beanclass= "org.javaboy.bean.BeanNameUtils"id= "beanNameUtils"/>

< beanclass= "#{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

publicClass<?> resolveBeanClass( @NullableClassLoader classLoader) throwsClassNotFoundException {

String className = getBeanClassName;

if(className == null) {

returnnull;

}

Class<?> resolvedClass = ClassUtils.forName(className, classLoader);

this.beanClass = resolvedClass;

returnresolvedClass;

}

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

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

2.2 Supplier 和 factory-method

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

Supplier<?> instanceSupplier = 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{

privateString username;

privateString address;

publicUser{

System.out.println( "=====no args=====");

}

publicUser(ObjectProvider<String> username){

System.out.println( "args==username");

this.username = username.getIfAvailable;

}

//省略 getter/setter/toString

}

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

< beanclass= "org.javaboy.bean.User"id= "user">

</ bean>

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

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

publicclassMySmartInstantiationAwareBeanPostProcessorimplementsSmartInstantiationAwareBeanPostProcessor{

@Override

publicConstructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throwsBeansException {

if( "user".equals(beanName)) {

Constructor<?> constructor = null;

try{

constructor = beanClass.getConstructor(ObjectProvider . class) ;

} catch(NoSuchMethodException e) {

thrownewRuntimeException(e);

}

returnnewConstructor[]{constructor};

}

returnSmartInstantiationAwareBeanPostProcessor. super.determineCandidateConstructors(beanClass, beanName);

}

}

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

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

< beanclass= "org.javaboy.bean.MySmartInstantiationAwareBeanPostProcessor"/>

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

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

< beanclass= "org.javaboy.bean.User"id= "user"autowire= "constructor">

</ bean>

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

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

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

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

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

2.5 PreferredConstructors

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

ctors = mbd.getPreferredConstructors;

if(ctors != null) {

returnautowireConstructor(beanName, mbd, ctors, null);

}

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

2.6 instantiateBean

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

protectedBeanWrapper instantiateBean(String beanName, RootBeanDefinition mbd){

try{

Object beanInstance = getInstantiationStrategy.instantiate(mbd, beanName, this);

BeanWrapper bw = newBeanWrapperImpl(beanInstance);

initBeanWrapper(bw);

returnbw;

}

catch(Throwable ex) {

thrownewBeanCreationException(mbd.getResourceDeion, beanName, ex.getMessage, ex);

}

}

@Override

publicObject 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) {

finalClass<?> clazz = bd.getBeanClass;

if(clazz.isInterface) {

thrownewBeanInstantiationException(clazz, "Specified class is an interface");

}

try{

constructorToUse = clazz.getDeclaredConstructor;

bd.resolvedConstructorOrFactoryMethod = constructorToUse;

}

catch(Throwable ex) {

thrownewBeanInstantiationException(clazz, "No default constructor found", ex);

}

}

}

returnBeanUtils.instantiateClass(constructorToUse);

}

else{

// Must generate CGLIB subclass.

returninstantiateWithMethodInjection(bd, beanName, owner);

}

}

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

3. 小结

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

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

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

END



Tags:Spring容器   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
深入理解Spring容器中Bean的作用域
容器中Bean的作用域是Spring框架中一个重要的概念,它决定了Bean实例的生命周期和可见范围。当我们在Spring容器中创建一个Bean实例时,除了完成Bean的实例化外,还可以为Bean指定...【详细内容】
2023-11-12  Search: Spring容器  点击:(233)  评论:(0)  加入收藏
Springboot之把外部依赖包纳入Spring容器管理的两种方式
前言在Spring boot项目中,凡是标记有@Component、@Controller、@Service、@Configuration、@Bean等注解的类,Spring boot都会在容器启动的时候,自动创建bean并纳入到Spring容器...【详细内容】
2023-11-08  Search: Spring容器  点击:(232)  评论:(0)  加入收藏
Spring容器原始Bean是如何创建的?
以下内容基于 Spring6.0.4。这个话题其实非常庞大,我本来想从 getBean 方法讲起,但一想这样讲完估计很多小伙伴就懵了,所以我们还是一步一步来,今天我主要是想和小伙伴们讲讲 Sp...【详细内容】
2023-08-03  Search: Spring容器  点击:(475)  评论:(0)  加入收藏
Spring容器中获取 Bean 实例的七种方式
说明一、写作原因首先解释一下写这篇博文的原因,因为在使用spring框架的过程中获取bean是非常常见的操作,但是网上非常的博文大多承自一家之言,因此很多可操作性上并不强,本文是...【详细内容】
2022-07-07  Search: Spring容器  点击:(309)  评论:(0)  加入收藏
Spring容器这些扩展点你都清楚了吗?
环境:Spring5.3.10通常,应用程序开发人员不需要对ApplicationContext实现类进行子类化。相反,SpringIOC容器可以通过插入特殊集成接口的实现来扩展。使用BeanPostProcessor自定...【详细内容】
2021-10-26  Search: Spring容器  点击:(306)  评论:(0)  加入收藏
如何按照条件向Spring容器中注册bean?这次我懂了
写在前面当bean是单实例,并且没有设置懒加载时,Spring容器启动时,就会实例化bean,并将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,直接返回IOC容器中的bean,不再创建新的b...【详细内容】
2020-08-21  Search: Spring容器  点击:(379)  评论:(0)  加入收藏
Spring容器6种注入方式
本节学习Spring的DI/IOC容器的6种注入方式。准备工作,编写基础类:Role.javapackage raky.train.entity;public class Role { private Integer id; private String name...【详细内容】
2020-07-18  Search: Spring容器  点击:(523)  评论:(0)  加入收藏
▌简易百科推荐
Docker 和传统虚拟机有什么区别?
我有一个程序员朋友,他每年情人节都要送女朋友一台服务器。他说:“谁不想在过节当天收到一台 4核8g 的服务器呢?”“万一对方不要,我还能留着自己用。” 给他一次过节的机会,他能...【详细内容】
2024-03-26  小白debug  微信公众号  Tags:Docker   点击:(12)  评论:(0)  加入收藏
掌握Docker网络驱动程序:优化容器通信
Docker为在容器内包装、交付和运行应用程序提供了一个强大的平台,从而彻底改变了容器化。网络是容器化的重要组成部分,Docker提供了各种网络驱动程序来支持容器之间的通信以...【详细内容】
2024-03-22    51CTO  Tags:Docker   点击:(10)  评论:(0)  加入收藏
Containerd容器管理
Nginx 指定容器名称 使用 ctr container create 命令创建容器后,容器并没有处于运行状态,其只是一个静态的容器。容器基本操作容器基本操作主要是 ctr image 命令,查看命令帮...【详细内容】
2024-03-20  云原生运维圈  微信公众号  Tags:容器   点击:(13)  评论:(0)  加入收藏
如何基于Docker镜像逆向生成Dockerfile
引言你是否曾经遇到过一个想要使用的 Docker 镜像,但却无法修改以适应你的特定需求?或者你可能发现了一个喜欢的 Docker 镜像,但想要了解它是如何构建的?在这两种情况下,将 Docke...【详细内容】
2024-03-07  云原生运维圈  微信公众号  Tags:Docker   点击:(22)  评论:(0)  加入收藏
Kubernetes是什么?主要特点是什么?
Kubernetes是什么?Kubernetes,也称为K8s,是一个开源的容器编排系统,由Google首次开发和维护。它允许容器化的应用程序在集群中自动部署、扩展和管理。Kubernetes提供了一种容器...【详细内容】
2024-02-01    简易百科  Tags:Kubernetes   点击:(154)  评论:(0)  加入收藏
我们一起聊聊容器资源自愈
在企业实际在使用容器这类资源的时候,除了技术本身,要考虑的其他问题也会很多。企业管理的容器有千千万万,出于效率考虑,对于有特殊需求的容器如何进行批量创建和管理呢,这就需要...【详细内容】
2024-01-30  匠心独运维妙维效  微信公众号  Tags:容器   点击:(47)  评论:(0)  加入收藏
Docker与Docker Compose入门:释放你应用部署的威力
今天给大家介绍一项强大而有趣的技能,那就是使用 Docker 和 Docker Compose 来释放你的应用部署的威力!无论你是一名开发人员还是系统管理员,掌握这个技能都将为你的工作带来巨...【详细内容】
2024-01-17  waynblog  微信公众号  Tags:Docker   点击:(65)  评论:(0)  加入收藏
Docker镜像与容器的交互及在容器内部执行代码的原理与实践
Docker作为一种流行的容器技术,已经成为现代应用程序开发和部署的重要工具。在Docker中,镜像是构建和运行容器的基础,而容器则是基于镜像创建的可执行实例。Docker镜像与容器的...【详细内容】
2024-01-10  编程技术汇  今日头条  Tags:Docker   点击:(77)  评论:(0)  加入收藏
如何在 Ubuntu 上安装 Docker
使用 Docker 意味着开启一个新的计算领域,但如果你刚刚开始使用 Docker,安装可能看起来是一项艰巨的任务。在 Ubuntu 上安装 Docker 有两种推荐的方法: 从 Ubuntu 的仓库安装 D...【详细内容】
2024-01-04    Linux中国  Tags:Docker   点击:(124)  评论:(0)  加入收藏
从Kubernetes的探针到DevOps
今天在群里又看有人问如何设置 Kubernetes 的探针,感觉要补充的话太多了,结合我们在一些 DevOps 项目中痛苦的体验,今天一劳永逸的全部说完,此外,也为大家展现一下为什么 DevOps...【详细内容】
2023-12-27  云云众生s  微信公众号  Tags:Kubernetes   点击:(114)  评论:(0)  加入收藏
站内最新
站内热门
站内头条