Redian新闻
>
Spring容器原始Bean是如何创建的?

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, nullnull);
  }
  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, nullnull);
 }
 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 方法,这个方法比较长,我就不贴出来了,和大家说一说它的思路:

  1. 首先把能获取到的构造方法都拿出来,如果构造方法只有一个,且目前也没有任何和构造方法有关的参数,那就直接用这个构造方法就行了。
  2. 如果第一步不能解决问题,接下来就遍历所有的构造方法,并且和已有的参数进行参数数量和类型比对,找到合适的构造方法并调用。

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 一些基本用法,那么源码一定是看得云里雾里的。


END



红帽回应质疑:授人以渔而非授人以鱼




这里有最新开源资讯、软件更新、技术干货等内容

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
揭秘Spring依赖注入和SpEL表达式大厂整体提Bar!实习考System Design是疯了么!如何创建 Ubuntu 持久化立付 U 盘 | Linux 中国一杯牛奶的前世今生,伊利如何创造「牛奶丝路」?今天,六四推荐35款 SpringBoot/SpringCloud 开源项目,附源码【𝐂𝐚𝐥𝐧𝐢𝐊𝐞𝐚𝐧双皮奶无痕内裤】49元三条!巨巨巨好穿 !!简直就是辣妹顶配,食品级冰箱收纳盒【一日团】梦中的那片海ep 56 is too long to view it now.Cube轻量虚拟化如何做到100ms交付一个安全容器「请听讲」第10期 | 敏捷文化:如何创建一个支持敏捷方法的企业文化?女人潮pen是什么感觉?刘姥姥眼中的贾府是什么样子的?王熙凤又是如何对待刘姥姥的?大河,小镇。展望与计划 Prospecting vs PlanningIn Northeast China, a New ‘Fangcang’ Hospital Sparks Uproar5天内用户数破亿、增速碾压ChatGPT,Twitter劲敌Threads是如何构建的?博世:汽车行业内卷,上游制造业巨头如何创新破局?|内训案例小可怜远行记 冷明逐帧解析!苹果是如何用45分钟讲清楚Vision Pro的?鸿蒙生态服务分发,如何创造一个"共赢"的机会?汉武帝如何平定福建的?十大美剧运作人教你如何创作文生图模型又“卷”起来了!比 Stable Diffusion 中文理解能力更强、更懂国人的文生图模型是如何构建的?Nginx 是如何解决惊群效应的?耗时一年用户从 0 增长至 1400 万,背后仅三名工程师,这家社交巨头背后的技术栈是如何搭建的?高分美剧的剧本是如何创作的?大话三国334:诸葛亮如何使用反间计,他是如何玩弄高定的?Spring在多线程环境下如何确保事务一致性只会用 Spring Boot 创建微服务?那你就 OUT 了,还有这 4 种替代方案!在法国犯人都圈(juān)不进去了还不同意多建点监狱?为啥呀?𝐂𝐚𝐥𝐧𝐢𝐊𝐞𝐚𝐧双皮奶内衣裤,软弹有度,上身0束缚~SpringBoot+Mybatis 如何实现流式查询,你知道吗?希腊苏达湾(Souda Bay),黎明前静悄悄SPAC真要凉了,一堆SPAC忙着清算,还能SPAC上市吗?My.Games首席关卡策划:如何创造具有吸引力的开放世界?
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。