spring源码(二) 探索bean加载

272 阅读6分钟

spring源码 系列文章目录

前言

源码跟踪,自下而上,由简至繁。找到一种使用情况为切入点,自下而上去追踪调用链,追踪时只看相关的主线代码,不要想着一次看完所有流程,由简至繁。

文章涉及的源码均已上传到了码云,参考【README.md】文件部署运行即可
spring码云地址: gitee.com/tangjingsha…
注释版本代码分支:git checkout nickyStudy5.3.4
spring源码流程图:www.processon.com/view/link/6…
当前文章的代码路径:org.springframework.study.debug

点击查看详细spring源码流程图

image.png

正文

一、定切入点

众所周知,spring是用来管理bean的,那么势必会有一个获取bean的入口,所以我们就从获取bean为切入点

二、自下而上,制定阅读计划

前面说的【自下而上】,所以我们从getBean这里作为切入点。假设抛开spring的实现,如果让你自己去实现,自下而上推,是否一定也是必须要按着以下步骤去实现呢

  • 实例化后的当前bean保存在哪里 ->
  • 何时触发保存当前bean ->
  • 何时触发实例化当前bean ->
  • 何时扫描到当前bean的class ->
  • 哪个对象扫描的当前bean的class

三、准备测试代码

本次追踪,只用spring,不用springboot,所以需要显示触发去加载某个bean,需要的文件如下

以下代码,在码云项目的以下位置:org.springframework.study.dataflow.a_study_bean.a_getBean

  1. 启动类
点击查看详细代码
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.study.StudyConfig;
import org.springframework.study.service.TestAutowired;

@ComponentScan("org.springframework.study.service")
public class a_getBean {
   public static void main(String[] args) {
      StudyConfig.thisDebugNames = "estAutowired";
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(a_getBean.class);
      TestAutowired testAutowired = context.getBean(TestAutowired.class);
      testAutowired.doSomething("from main");
   }
}
  1. 测试类
点击查看详细代码
@Service
public class TestAutowired {
   public TestAutowired() {
      System.out.println("调用无参构造函数实例化TestAutowired");
   }

   public void doSomething(String from){
      System.out.println(ExceptionUtils.getStackTrace(new Throwable()));
   };
}

四、开始探索

1. 实例化后的当前bean保存在哪里

从获取bean入手,去寻找bean最终是保存在哪里的。在context.getBean处打上断点,步入。

调试技巧: 看程序在哪一行准备return了,然后看看return的这个变量在哪里构造,在构造处打上断点X,再return前步出当前方法,然后步入断点X,以此方式追溯,即可找到你所需要的代码。

实例化后的当前bean保存在哪里2.0.gif
结论:部分实例化后的bean最终是保存在一个map中

// 最终获取bean的位置
{@link org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    ...
    return singletonObject;
}

// 实例化后的bean的保存位置
{@link org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonObjects}

2. 何时触发保存当前bean

经过第一步已经知道了实例化后的bean是保存在map中,那么是何时触发保存的呢。
既然是一个map,那么必然会调用xxx.put方法加入数据,全局搜索当前文件【this.singletonObjects.put】,可以发现只有一个方法调用了,在这里打上断点,重新调试,因为此处为公有方法,所以需要加上断点条件,过滤不相干的bean
断点条件: beanName.indexOf(StudyConfig.thisDebugNames)!=-1

image.png

image.png
结论:经调试,可以发现实例化后的bean的确是在此处put到map缓存中。那么下一步就是寻找形参【singletonObject】是何时触发的实例化

{@link org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton}

protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) {
      this.singletonObjects.put(beanName, singletonObject);
      ....
   }
}

3. 何时触发实例化当前bean

可以通过左下角的调用链一路往上找出实例化的触发点,但是这里还有更简单的方法。生成一个对象,必然会调用其构造函数,所以我们重写其无参构造函数,并打上断点,即可快速定位到实例化的触发点

何时触发实例化.gif
结论:经调试,可以发现bean是在以下位置通过java反射,调用无参构造函数实例化的。

{@link org.springframework.beans.factory.support.SimpleInstantiationStrategy#instantiate(org.springframework.beans.factory.support.RootBeanDefinition, java.lang.String, org.springframework.beans.factory.BeanFactory)}
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
    ...
    return BeanUtils.instantiateClass(constructorToUse);
}

{@link 
org.springframework.beans.BeanUtils#instantiateClass(java.lang.reflect.Constructor, java.lang.Object...)}
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
    ...
    return ctor.newInstance(argsWithDefaultValues);
}
3.1 形参beanName从何而来

这里还需要再额外追溯下SimpleInstantiationStrategy#instantiate的形参beanName从何而来,可以分析下【3 何时触发实例化当前bean】的调用链

beanName循环.gif

结论:经调试,可以发现beanName是来自于beanDefinitionNames这个集合,其由下面的【4.4 形参beanDefinition,何处生成】构造,此处先不追溯

{@link 
org.springframework.beans.factory.support.DefaultListableBeanFactory#beanDefinitionNames}

{@link org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons}
public void preInstantiateSingletons() throws BeansException {
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
    for (String beanName : beanNames) {
        ...
        getBean(beanName);
    }
    ...
}

4. 何时扫描到当前bean的class

无参构造函数必然是从class对象获取,那么class对象又是何处获取的呢

调试技巧: 看程序在哪一行准备return了,然后看看return的这个变量在哪里构造

  1. 如果这个变量为形参,则直接看调用链的上一个调用者即可
  2. 如果这个变量在当前方法内部构造,则在构造处打上断点X,再直接步出当前 方法或重启应用程序,然后步入断点X,以此方式追溯,即可找到你所需要的代码。
4.1 形参ctor,何处生成

分析上一步(3)左下角的调用链,可以发现形参【ctor】是由以下位置生成的

形参ctor,何处生成.gif

{@link org.springframework.beans.factory.support.SimpleInstantiationStrategy#instantiate(org.springframework.beans.factory.support.RootBeanDefinition, java.lang.String, org.springframework.beans.factory.BeanFactory)}

public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
    ...
    final Class<?> clazz = bd.getBeanClass();
    constructorToUse = clazz.getDeclaredConstructor();//获取无参构造函数
    return BeanUtils.instantiateClass(constructorToUse);
}
4.2 BeanDefinition.setBeanClass何处调用

既然有getBeanClass那么势必就有setBeanClass,在setBeanClass处加上断点条件【beanClass.getName().indexOf(StudyConfig.thisDebugNames)!=-1】

何时扫描到当前bean的class_setBeanClass.gif

{@link org.springframework.beans.factory.support.DefaultListableBeanFactory#getBeanDefinition}

public BeanDefinition getBeanDefinition(String beanName){
   BeanDefinition bd = this.beanDefinitionMap.get(beanName);
   ...
   return bd;
}
4.3 beanDefinitionMap何处put

可以发现BeanDefinition也是放到一个map中,同理,全局搜索当前文件【this.beanDefinitionMap.put】可以发现有三处调用,这三处都在一个方法中,而且BeanDefinition依然是形参传入的,而非当前方法生成的,所以再此方法的第一行代码加上断点条件【beanName.indexOf(StudyConfig.thisDebugNames)!=-1】

{@link 
org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition}

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
    ...
    this.beanDefinitionMap.put(beanName, beanDefinition);
}
4.4 形参beanDefinition,何处生成

分析上一步registerBeanDefinition的调用链,可知在以下位置生成beanDefinition 形参beanDefinition,何处生成.gif

{@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan}

//beanDefinition由这个方法生成
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
    ...
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    //fixme 这里会存入 {@link DefaultListableBeanFactory#beanDefinitionNames},后续实例化就是从遍历这个集合开始的
    registerBeanDefinition(definitionHolder, this.registry);
}
4.5 findCandidateComponents如何生成BeanDefinition

在findCandidateComponents那一行代码,打上断点,看看他做了什么,此处不需要断点条件。
可以发现,findCandidateComponents是先将指定路径basePackage的class文件扫描包装成Resource对象,然后判断是否为待加载的bean,进而包装成 BeanDefinition findCandidateComponents如何生成BeanDefinition.gif
已经获取class文件,自然就可以创建class对象,具体的创建逻辑不在追溯,有兴趣的同学可以在以下位置加上断点,研究下调用链

扫描.class文件
{@link PathMatchingResourcePatternResolver#doRetrieveMatchingFiles(java.lang.String, java.io.File, java.util.Set)}
赋值className
{@link org.springframework.core.type.classreading.SimpleAnnotationMetadataReadingVisitor#visit(int, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])}
创建class对象
{@link org.springframework.beans.factory.support.AbstractBeanDefinition#resolveBeanClass(java.lang.ClassLoader)}

5. 哪个对象扫描的当前bean的class

根据上一步(4)可知,doScan方法内部调用findCandidateComponents方法去构造的bean的包装类BeanDefinition,那么doScan又是由谁实例化的呢,我们再在doScan方法打上断点
分析doScan的调用链可知,doScan是由对象ClassPathBeanDefinitionScanner执行扫描的

哪个对象扫描的当前bean的class.gif

{@link org.springframework.context.annotation.ComponentScanAnnotationParser#parse}

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(...);
    ...
    return scanner.doScan(StringUtils.toStringArray(basePackages));
}

而parse方法则是由BeanFactory的后置处理器触发的 【ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor】 (PS: mybatis的mapper扫描也是实现了这个接口去自定义扫描的)

BeanDefinitionRegistryPostProcessor.gif

{@link 
org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry}

五、自上而下,总结

  1. 熟练使用这两个调试技巧,位于【1. 实例化后的当前bean保存在哪里】、【4. 何时扫描到当前bean的class】
  2. 复制以下代码到项目中,ctrl+鼠标右键即可跳转到目标位置

以下注释,在码云项目的以下位置:org.springframework.study.dataflow.a_study_bean.SimpleFlow

/**
 * - [ ] 哪个对象扫描的当前bean的class
 * {@link org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry}
 * {@link org.springframework.context.annotation.ComponentScanAnnotationParser#parse}
 *        (ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(...);)
 *        (scanner.doScan(StringUtils.toStringArray(basePackages));)
 * - [ ] 何时扫描到当前bean的class ->
 * {@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan}
 *        (Set<BeanDefinition> candidates = findCandidateComponents(basePackage);)//扫描到当前bean的class
 *        (registerBeanDefinition(definitionHolder, this.registry);//存入beanDefinitionNames)
 * - [ ] 何时触发实例化当前bean ->
 * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons()}
 *        (for (String beanName : beanDefinitionNames) {..//循环beanDefinitionNames)
 * {@link org.springframework.beans.BeanUtils#instantiateClass(java.lang.reflect.Constructor, java.lang.Object...)}
 *        (return ctor.newInstance(argsWithDefaultValues);)
 * - [ ] 何时触发保存当前bean ->
 * {@link org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton}
 *        (this.singletonObjects.put(beanName, singletonObject);)
 * - [ ] 实例化后的当前bean保存在哪里 ->
 * {@link org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)}
 *        (Object singletonObject = this.singletonObjects.get(beanName);)
 * {@link org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonObjects}
 */

六、简易流程图

image.png