【Spring Bean生命周期】高手如何解读源码(一) - 资源的扫描和注册

2,031 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天
Spring Bean生命周期,从入门到进阶:
【Spring Bean生命周期】小白也能看懂的入门篇
【Spring Bean生命周期】高手如何解读源码(一) - 资源的扫描和注册
【Spring Bean生命周期】高手如何解读源码(二) - Bean的生命周期

1 从IOC用到的设计模式开始

  在学习Spring的IOC源码之前,我们有必要先对Spring的IOC用到设计模式有所了解,以便我们能更好的理解作者的设计意图,主要有下面几种:
1)工厂模式(Factory Pattern):通过BeanFactory和ApplicationContext来创建对象。首先可以解决bean之间的依赖问题,达到松耦合的效果;其次可以在实例化bean过程中,进行一些额外的处理;

2)单例模式(Singleton Pattern):在Spring中的Bean默认的作用域就是singleton单例的。单例模式的好处在于对一些重量级的对象,省略了重复创建对象花费的时间,减少了系统的开销;其次是使用单例可以减少new操作的次数,减少了GC线程回收内存的压力。

3)模板模式(Template Pattern):spring框架中调用refresh()方法完成上下文的启动,即模板方法。模版模式的特点:从头至尾,各个属性的位置都是固定的,是一个大而全的东西,固定了流程,按照模版来就行。

4)策略模式(Strategy Pattern):spring在加载bean定义信息的时候,来源可能是xml,可能是注解,也可能是properties等等。而spring都是通过BeanDefinitionReader这个接口来解析的。这种不用考虑具体来源,就可以完成即系的方式就是策略模式。

2 一切代码从main()开始

  找到项目的启动类SpringApplication,整个项目从SpringApplication的run()方法开始,我们直接进入到SpringApplication类的run()方法中,去掉非核心逻辑,看下主要有几个动作:

public ConfigurableApplicationContext run(String... args) {
      // 1. 创建Spring上下文,此处生成AnnotationConfigServletWebServerApplicationContext类型
      context = createApplicationContext();
  ```
      // 2. 刷新上下文的准备阶段
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
```
      // 3. 刷新Spring上下文
      refreshContext(context);
      ......
}

2.1 创建Spring上下文

  • 第一步,采用策略模式,生成AnnotationConfigServletWebServerApplicationContext类型实例。 在Spring Boot工程中,应用类型有三种,定义在枚举类WebApplicationType中,
public enum WebApplicationType {

   /**
    * The application should not run as a web application and should not start an
    * embedded web server.
    */
   NONE,

   /**
    * The application should run as a servlet-based web application and should start an
    * embedded servlet web server.
    */
   SERVLET,

   /**
    * The application should run as a reactive web application and should start an
    * embedded reactive web server.
    */
   REACTIVE;
   }

  究竟启动哪种类型应用,取决于加载的类进行判断,具体实现可看里面的deduceFromXXX()方法,此处不再展开,Debug可知此时类型为SERVLET,返回生成的AnnotationConfigServletWebServerApplicationContext实例。

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
   try {
      switch (webApplicationType) {
      case SERVLET:
         return new AnnotationConfigServletWebServerApplicationContext();
      case REACTIVE:
         return new AnnotationConfigReactiveWebServerApplicationContext();
      default:
         return new AnnotationConfigApplicationContext();
      }
   }
   catch (Exception ex) {
   }
};
  • 第二步,调用AnnotationConfigServletWebServerApplicationContext的构造函数。其中对postProcessBeanFactory()方法进行了重写,在下面刷新上下文中会触发调用,此处暂且不展开。
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext implements AnnotationConfigRegistry {
    public AnnotationConfigServletWebServerApplicationContext() {
       this.reader = new AnnotatedBeanDefinitionReader(this);
       this.scanner = new ClassPathBeanDefinitionScanner(this);
    }
    
    // postProcessBeanFactory()方法被重写,在此处进行扫描
    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
       super.postProcessBeanFactory(beanFactory);
       if (this.basePackages != null && this.basePackages.length > 0) {
          this.scanner.scan(this.basePackages);
       }
       if (!this.annotatedClasses.isEmpty()) {
          this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
       }
      }
    }

2.2 刷新上下文的准备阶段

从IOC角度来看,这个阶段主要是将启动类注册

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
      ```
        ......
        // 1 拿到我们的启动类
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        
        // 2 将启动类封装成AnnotatedGenericBeanDefinition类型,然后注册到容器中
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
```
      }

2.3 刷新上下文【重点】

  其中,SpringApplicationrefreshContext(context)调用如图:

public class SpringApplication {

    private void refreshContext(ConfigurableApplicationContext context) {
       if (this.registerShutdownHook) {
          shutdownHook.registerApplicationContext(context);
       }
       refresh(context);
    }
    /**
     * Refresh the underlying {@link ApplicationContext}.
     * @param applicationContext the application context to refresh
     */
    protected void refresh(ConfigurableApplicationContext applicationContext) {
       applicationContext.refresh();
    }
    }

  本质上是调用了AnnotationConfigServletWebServerApplicationContext本身的refresh()方法,但是这个类本身没有重写refresh()方法,执行的是父类的refresh()方法,可以看下该类的UML如图: image.png 即调用的是AbstractApplicationContext.refresh(),refresh()采用的模板模式,也是整个IOC实现的核心,代码如下

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
      // 1 准备刷新上下文环境
      prepareRefresh();
      
      // 2 获取上下文中的BeanFactory,并且执行子类refreshBeanFactory()的地方 ----- <IOC-1>
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      
      // 3 对BeanFactory进行填充属性
      prepareBeanFactory(beanFactory);

      try {
         //4 BeanFactory扩展点,子类实现该接口 ----- <IOC-2>
         postProcessBeanFactory(beanFactory);

         //5 执行BeanFactory的后置处理器  ----- <IOC-3>
         invokeBeanFactoryPostProcessors(beanFactory);
         
         //6 注册Bean的后置处理器,在Bean创建过程中使用(提供针对实例化的before和after方法)
         registerBeanPostProcessors(beanFactory);
         
         //7 初始化国际资源处理器
         initMessageSource();
         
         //8 初始化上下文的事件多播器
         initApplicationEventMulticaster();
         
         //9 留给子类实现的扩展点,Spring boot就是在该扩展点实现Tomcat的启动的
         onRefresh();
         
         //10 将我们的事件监听器注册到多播器上
         registerListeners();
         
         //11 实例化剩余的单例,不包含懒加载类型的
         finishBeanFactoryInitialization(beanFactory);
         
         //12 最后一步刷新,Spring Cloud是从这里启动的
         finishRefresh();
      }

      catch (BeansException ex) {
          ......
      }
      finally {
       ......
      }
   }
}

我们只针对IOC相关的方法进行解析

IOC-1: 获取上下文中的BeanFactory

  在2.1节中,我们创建了应用的上下文AnnotationConfigServletWebServerApplicationContext,并触发了GenericApplicationContext类的构造方法如下所示,创建了beanFactory,也就是创建了DefaultListableBeanFactory类。

public GenericApplicationContext() {
   this.beanFactory = new DefaultListableBeanFactory();
}

此处的obtainFreshBeanFactory()方法,其实就是拿到我们之前创建的beanFactory。

IOC-2: postProcessBeanFactory扩展点

  在2.1节第二步,我们提到过,AnnotationConfigServletWebServerApplicationContext重写了这个钩子方法,在此处会做一个实际扫描动作

@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   super.postProcessBeanFactory(beanFactory);
   if (this.basePackages != null && this.basePackages.length > 0) {
      this.scanner.scan(this.basePackages);
   }
   if (!this.annotatedClasses.isEmpty()) {
      this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
   }
}

  但是在Debug的时候发现,在此处扫描时basePackagesannotatedClasses均为空,没有进入到扫描注册,这个先保留,后面补充下此处触发扫描的具体场景。

image.png

IOC-3:执行BeanFactory的后置处理器(重点)

  在invokeBeanFactoryPostProcessors(beanFactory)中完成了IOC容器Bean注册阶段的三个步骤,如图所示:

image.png 主要依赖于两个类实现:

  • ConfigurationClassParser 主要完成Resorce的定位
  • ConfigurationClassBeanDefinitionReader 主要完成BeanDefinition的载入和注册

第一步:Resource的载入(定位)

SpringBoot中实现三种资源定位:

  • 主类所在包的basePackage
  • SPI扩展机制实现的自动装配(比如各种starter)
  • @Import注解指定的类

  我们只讲第一种的实现,在2.2节中,我们就已经将主类注册到上下文,因此,此处可以拿到主类的basePackage的路径。 我们将invokeBeanFactoryPostProcessors(beanFactory)简化,只看IOC相关的部分

final class PostProcessorRegistrationDelegate {
        
    public static void invokeBeanFactoryPostProcessors(
          ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
            ......
            // 按照顺序执行下面的方法
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());

          }

    /**
     * Invoke the given BeanDefinitionRegistryPostProcessor beans.
     */
    private static void invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
       for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
          postProcessor.postProcessBeanDefinitionRegistry(registry);
          postProcessBeanDefRegistry.end();
       }
    }
  }

这个方法的调用栈比较长,直接debug到ConfigurationClassParser类的parse()方法。

// ConfigurationClassParser 类
public void parse(Set<BeanDefinitionHolder> configCandidates) {
   for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
         if (bd instanceof AnnotatedBeanDefinition) {
            parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
         }
         else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
            parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
         }
         else {
            parse(bd.getBeanClassName(), holder.getBeanName());
         }
      }
      catch (BeanDefinitionStoreException ex) {
         throw ex;
      }
      catch (Throwable ex) {
         throw new BeanDefinitionStoreException(
               "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
      }
   }

   this.deferredImportSelectorHandler.process();
}

前面2.2节我们知道,启动类会被注册为AnnotatedGenericBeanDefinition类型,它继承自AnnotatedBeanDefinition,所以继续跟踪到

// ConfigurationClassParser 类
protected final void parse(Class<?> clazz, String beanName) throws IOException {
   processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
}

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
 
   // Recursively process the configuration class and its superclass hierarchy.
   SourceClass sourceClass = asSourceClass(configClass, filter);
   do {
      sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
   }
   while (sourceClass != null);

   this.configurationClasses.put(configClass, configClass);
}

  其中processConfigurationClass(ConfigurationClass configClass),此方法是解析ConfigurationClass的主要入口,它除了主流程的调用外,它还会由于后续解析出多个配置类而被轮循和递归调用:
1.当配置类被解析出有定义了内部类,就将该内部类封装成ConfigurationClass然后调用这方法;

2.在解析ComponentScan标注后,对符合成为配置类的Bean封装成ConfigurationClass然后调用这方法;

3.在解析Import标注环节,被引入的类并非ImportSelector、ImportBeanDefinitionRegistar和DeferredImportSelector接口的实现类时,将封装成ConfigurationClass然后调用这方法。

4.在解析当前配置的父类时,稍稍有点特别,程序会跳转到processConfigurationClass方法里的稍后一点的地方,其实就是跳过是否是配置类的判断,然后这方法体的流程走下去。这么处理的逻辑可能是因为当前的类已经是配置类了,现在只是检查它父类的定义情况,所以就跳过了对父类是否配置类的判断了。
这里整体很多递归嵌套,逻辑较复杂,不在主流程里详细介绍。

第二步:BeanDefinition的载入

@Nullable
protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
      throws IOException {
   for (AnnotationAttributes componentScan : componentScans) {
         // The config class is annotated with @ComponentScan -> perform the scan immediately
         Set<BeanDefinitionHolder> scannedBeanDefinitions =
               this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
   }
   

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

在doScan()方法中,将所有Resource转为BeanDefinition信息

// ClassPathBeanDefinitionScanner类
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      ......
   }
   return beanDefinitions;
}


// ClassPathScanningCandidateComponentProvider类
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
   if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
      return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
   }
   else {
      return scanCandidateComponents(basePackage);
   }
}

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<>();
   try {
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
      for (Resource resource : resources) {
            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
            if (isCandidateComponent(metadataReader)) {
               ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
               sbd.setSource(resource);
               if (isCandidateComponent(sbd)) {
                  if (debugEnabled) {
                     logger.debug("Identified candidate component class: " + resource);
                  }
                  candidates.add(sbd);
               }
          
   return candidates;
}

第三步注册也是在doScan()方法中实现的

第三步:BeanDefinition的注册


/**
 * Perform a scan within the specified base packages,
 * returning the registered bean definitions.
 * <p>This method does <i>not</i> register an annotation config processor
 * but rather leaves this up to the caller.
 * @param basePackages the packages to check for annotated classes
 * @return set of beans registered if any for tooling registration purposes (never {@code null})
 */
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
       // 第二步 完成载入
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
       
       // 第三步 注册
       for (BeanDefinition candidate : candidates) {
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}