springboot的初始化启动过程
发布日期:2021-06-30 20:00:12 浏览次数:4 分类:技术文章

本文共 29239 字,大约阅读时间需要 97 分钟。

 springboot run 的总体流程如下:

(1)加载各种配置文件以及环境参数

主要根据main方法和自动配置文件META-INF/spring.factories 中的相关配置类进行自动化化加载初始化等操作。

注册监事件听器。

初始化容器上下文Context。

解析@ComponentScan扫描Bean定义生成BeanDefinition、BeanDefinitionRegistry 把BeanDefinition注册到BeanFactory。

 

目录:

  辅助阅读:

  应用:

启动入口

本文是springboot启动流程的第一篇,涉及的内容是SpringApplication这个对象的实例化过程。为什么从SpringApplication这个对象说起呢?我们先看一段很熟悉的代码片段

复制代码

@SpringBootApplicationpublic class SpringBootLearnApplication {    public static void main(String[] args) {        SpringApplication.run(SpringBootLearnApplication.class, args);    }}

复制代码

springboot项目从一个main方法开始,main方法将会调用SpringApplication的run方法开始springboot的启动流程。所以,本文即从构造SpringApplication对象开始。

 

我们跟进SpringApplication的run方法

public static ConfigurableApplicationContext run(Class
primarySource, String... args) { return run(new Class
[] { primarySource }, args);}

这是一个静态方法,入参有两个:

1)main方法所在的类,该类后续将被作为主要的资源来使用,比如通过该类获取到basePackage;

2)main方法的命令行参数,命令行参数可以通过main传入,也就意味着可以在springboot启动的时候设置对应的参数,比如当前是dev环境、还是production环境等。

第2行代码,run方法将调用另外一个内部run方法,并返回一个ConfigurableApplicationContext,预示着spring容器将在后续过程中创建。

 

跟进另一个run方法

public static ConfigurableApplicationContext run(Class
[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}

run方法中先是构造了一个SpringApplication实例对象,而后调用了SpringApplication的成员方法run,这个run方法将包含springboot启动流程的核心逻辑。本文只讨论SpringApplication的实例化过程。

 

构造函数

跟进SpringApplication的构造函数中

public SpringApplication(Class
... primarySources) { this(null, primarySources);}

构造函数调用了另外一个构造函数,继续跟进

复制代码

public SpringApplication(ResourceLoader resourceLoader, Class
... primarySources) { // 设置资源加载器 this.resourceLoader = resourceLoader; // 设置主要资源类 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 推断当前应用的类型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 设置ApplicationContext的初始化器 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 设置Application监听器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 推断并设置主类 this.mainApplicationClass = deduceMainApplicationClass();}

复制代码

构造过程主要包含:

1)推断当前应用类型

2)设置ApplicationContext初始化器、Application监听器

3)根据堆栈来推断当前main方法所在的主类

 

推断当前应用类型

WebApplicationType是一个枚举对象,枚举了可能的应用类型

复制代码

public enum WebApplicationType {      /**      * 非web应用类型,不启动web容器      */     NONE,     /**      * 基于Servlet的web应用,将启动Servlet的嵌入式web容器      */    SERVLET,    /**     * 基于reactive的web应用,将启动reactive的嵌入式web容器     */    REACTIVE;    // 省略...}

复制代码

deduceFromClasspath方法将会推断出当前应用属于以上三个枚举实例的哪一个,跟进方法

复制代码

static WebApplicationType deduceFromClasspath() {     // 类路径中是否包含DispatcherHandler,且不包含DispatcherServlet,也不包含ServletContainer,那么是reactive应用     if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)             && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {         return WebApplicationType.REACTIVE;     }     // 如果Servlet或者ConfigurableWebApplicationContext不存在,那么就是非web应用     for (String className : SERVLET_INDICATOR_CLASSES) {         if (!ClassUtils.isPresent(className, null)) {            return WebApplicationType.NONE;        }    }    // 否则都是Servlet的web应用    return WebApplicationType.SERVLET;}

复制代码

推断过程将根据类路径中是否有指示性的类来判断

 

设置ApplicationContext初始化器、Application监听器

getSpringFactoriesInstances(ApplicationContextInitializer.class)

这个方法调用将会从META-INF/spring.factories配置文件中找到所有ApplicationContextInitializer接口对应的实现类配置,然后通过反射机制构造出对应的实例对象。

getSpringFactoriesInstances(ApplicationListener.class)

这个方法也是一样的做法,将会获取ApplicationListener接口的所有配置实例对象

有关于如何从spring.factories配置文件中获取配置并构造出实例对象请看:

 

根据堆栈来推断当前main方法所在的主类

构造SpringApplication还有最后一步,推断出main方法所在的主类。我们跟进deduceMainApplicationClass方法

复制代码

private Class
deduceMainApplicationClass() { try { // 获取堆栈链路 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); // 遍历每一个栈帧信息 for (StackTraceElement stackTraceElement : stackTrace) { // 如果该栈帧对应的方法名等于main if ("main".equals(stackTraceElement.getMethodName())) { // 获取该类的class对象 return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null;}

复制代码

该方法采用遍历栈帧的方式来判断最终main方法落在哪个栈帧上,并通过forName来获取该类

 

总结

到这里本文就结束了,核心点就在于SpringApplication的实例化,可以看出最主要的就是做了应用类型的推断,后面的Application创建、Environment创建也会基于该类型。

SpringApplication.run方法逻辑

在中,我们看到SpringApplication的静态方法最终是去构造了一个SpringApplication实例对象,并调用了SpringApplication的成员方法run

public static ConfigurableApplicationContext run(Class
[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}

本文将梳理一下run方法的代码主要的逻辑,为后面其它内容做一个铺垫

 

跟进run方法,这个方法的代码有点长我们将抛弃掉一些比较次要的内容

复制代码

public ConfigurableApplicationContext run(String... args) {    // 声明一个Context容器    ConfigurableApplicationContext context = null;    // 获取监听器    SpringApplicationRunListeners listeners = getRunListeners(args);    // 调用监听器的启动    listeners.starting();    try {        // 创建并配置Environment(这个过程会加载application配置文件)        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);        // 根据应用类型创建对应的Context容器        context = createApplicationContext();        // 刷新Context容器之前的准备        prepareContext(context, environment, listeners, applicationArguments, printedBanner);        // 刷新Context容器        refreshContext(context);        // 刷新Context容器之后处理        afterRefresh(context, applicationArguments);        // Context容器refresh完毕发布        listeners.started(context);        // 触发Context容器refresh完以后的执行        callRunners(context, applicationArguments);    } catch (Throwable ex) {}    try {        // Context启动完毕,Runner运行完毕发布        listeners.running(context);    } catch (Throwable ex) {}    return context;}

复制代码

简化后的代码看起来就比较清晰了,我们再整理一下逻辑

1)首先会从spring.factories配置文件中获取SpringApplicationRunListener监听器并启动监听器;

2)而后就会去创建Environment

3)紧接着创建ApplicationContext

4)ApplicationContext的refresh的事前准备

5)ApplicationContext的refresh

6)ApplicationContext的refresh之后

7)发布ApplicationContext的refresh完毕的事件

8)触发runner

9)最后发布refresh完毕、runner执行完毕的事件

run方法描述了SpringApplication这个类的职责,包含了不少步骤,但简单的看其实就是为了创建并配置好一个ApplicationContext。

总结

我们忽略各种细节以后就会发现,SpringApplication的run方法主要就是为了构建出一个ApplicationContext,后续文章也将围绕着构建ApplicationContext相关的内容展开。

Environment

我们简单了解了一下SpringApplication的run方法的代码逻辑。其中的prepareEnvironment方法正如它的方法名表示的意思一样,为当前应用准备一个Environment对象,也就是运行环境。在阅读prepareEnvironment代码之前,我们先了解一下Environment。

组成

首先,Environment是Spring3.1才提供的一个接口。它是对当前运行的应用程序的环境的抽象,下面我们了解一下它的组成。

Environment由两部分组成

1)profiles

profile中文直译是"概述"、"简介"、"轮廓"的意思,但在使用spring开发应用程序的时候,我们对profile的认识更亲切的是用在划分多环境的时候。

通常,我们会将profile划分成如:开发、测试、预生产、生产环境。每个环境会有有些bean不同、配置不同等。每个profile将有相应的bean和配置与之匹配,那么当我们切换profile的时候自然也就切换了相应的bean和配置文件,从而达到在不同环境中快速切换避免不断修改的问题。

这也就是spring的java doc里面描述的"logical group"的意思。

2)properties

properties的概念想必我们已经非常熟悉了,在java中properties代表着key-value的键值对象集合。Environment内部设计了key-value结构的对象来存储相应的键值。

综上所述,Environment中包含着用于切换环境的profile,还包含着存储键值对的properties。

 

核心uml类图

上面的内容中,我们了解了Environment的组成部分包括profile和properties。spring在对Environment进行设计的时候也把这两个部分进行了隔离。

如上图所示,PropertyResolver包含了properties相关的操作,如:getProperty(String key),Environment继承于PropertyResolver同时也就将properties的相关能力给组合了进来。

Environment的则包含了profile的相关操作,如:getActiveProfiles()。

如果查看PropertyResolver和Environment接口的方法,我们就会发现这两个接口都只是包含了如getter方法的获取操作,并没有setter样子的操作。这或许也意味着spring希望在程序的开发运行过程中,Environment尽量是维持稳定的,而不是不断地被修改、变化。

那么在程序启动过程中势必要对Environment进行配置,因此我们会看到多个继承自Environment和PropertyResolver接口地子接口,如:ConfigurableEnvironment和ConfigurablePropertyResolver。

再往下看,AbstractEnvironment显然包含了Environment设计地大部分实现,而从StandardEnvironment再往下走了两个分支,也就是针对reactive和Servlet的Environment实现。

到这里,我们基本了解了Environment主要的相关接口设计,设计路线也比较简单。

 

profile和properties的数据结构

前面的两个部分,我们了解了Environment包含profile和properties。也知道了Environment相关接口也主要是根据profile和properties来设计的。但是我们并不知道具体的实现里面profile和properties的数据结构是怎么样的。

从uml类图中,我们清晰地看到Environment的具体实现是在AbstractEnvironment这个抽象类中。我们可以直接打开这个类

 

profile数据结构

AbstractEnvironment类中包含着profile的成员变量

private final Set
activeProfiles = new LinkedHashSet<>();private final Set
defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());

profile的存储结构看起来相对简单,就是两个set集合,每个profile就是单纯的一个String类型的字符串表示而已。

activeProfiles表示的是当前应用中"激活"的profile集合,比如我当profile=test的时候表示当前环境是测试环境。

而defaultProfiles则表示的是默认的profile集合,也就是说如果没有任何指定的profile,那么就会采用默认的。

 

properties数据结构

我们再看看AbstractEnvironment中properties的数据结构

private final MutablePropertySources propertySources = new MutablePropertySources();

前面我们一直提到,properties是一种key-value的键值对存储的集合。那么也就是说MutablePropertySources这个类实现了这个概念。

 

我们先看看MutablePropertySources的继承结构是怎么样的

看起来很简单的设计路线,Iterable接口表明MutablePropertySources像集合一样是可以迭代的,我们可以大胆猜测其内部就是组成了一个集合。Iterable往下,就是PropertySources,这个接口表示的是PropertySource类的集合,也就是说被迭代的元素就是PropertySource。MutablePropertySources则直接继承于PropertySources。

那么,我们基本可以想得到PropertySource这个类就是properties概念得设计,是我们主要得关注对象。

现在让我们打开MutablePropertySources看看PropertySource的具体结构

复制代码

public class MutablePropertySources implements PropertySources {    private final List
> propertySourceList = new CopyOnWriteArrayList<>(); // 省略}

复制代码

跟我们想象得差不多,就是一个PropertySource类的集合作为成员组合在MutablePropertySources中。

 

我们继续跟进PropertySource这个类,更多得了解一下

复制代码

public abstract class PropertySource
{ protected final String name; protected final T source; // 省略}

复制代码

看起来就是一个key-value的数据结构是吗?这里请注意!跟我们想象的稍微有点不同,举例说明

我们创建了一个config.properties文件,内容如

username=testpassword=a123456

那么当config.properties这个文件被加载到内存中,并作为一个PropertySource存在的时候,name=config而非name=username或者password。也就是说,加载config.properties这样的资源,泛型T将会是一个Map集合,而Map集合包含着config.properties文件中所有的键值对。

 

另外,我们注意到PropertySource是一个抽象类。spring将会针对资源的不同来源而使用不同的实现,例如上例中的config.properties加载到内存作为Properties对象添加的,就是PropertySource的其中一个实现类PropertiesPropertySource。

还有诸如

1)来自命令行的配置:CommandLinePropertySource

2) 来自Servlet的配置:ServletConfigPropertySource、ServletContextPropertySource

下面是一张PropertySource的层级图

 

 

prepareEnvironment创建Environment

上部分的内容包括了不少介绍的内容,下面我们简单看看SpringApplication的run方法中包含的prepareEnvironment方法,跟进方法

复制代码

private ConfigurableEnvironment prepareEnvironment(        SpringApplicationRunListeners listeners,        ApplicationArguments applicationArguments        ) {    // 创建一个Environment对象    ConfigurableEnvironment environment = getOrCreateEnvironment();    // 配置Environment对象    configureEnvironment(environment, applicationArguments.getSourceArgs());    // 触发监听器(主要是触发ConfigFileApplicationListener,这个监听器将会加载如application.properties/yml这样的配置文件)    listeners.environmentPrepared(environment);    bindToSpringApplication(environment);    if (!this.isCustomEnvironment) {        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,                deduceEnvironmentClass());    }    ConfigurationPropertySources.attach(environment);    return environment;}

复制代码

该方法核心内容包括三部分

1)创建一个Environment对象

2)配置Environment对象

3)触发ConfigFileApplicationListener监听器(加载application.properties/yml将再后续文章中说明)

 

 

getOrCreateEnvironment

我们跟进getOrCreateEnvironment方法看看创建过程

复制代码

private ConfigurableEnvironment getOrCreateEnvironment() {    if (this.environment != null) {        return this.environment;    }    switch (this.webApplicationType) {    case SERVLET:        return new StandardServletEnvironment();    case REACTIVE:        return new StandardReactiveWebEnvironment();    default:        return new StandardEnvironment();    }}

复制代码

在文章中,我们提到SpringApplication在deduceFromClassPath方法中会推断出WebApplicationType具体的枚举实例,代表了当前应用的类型。

getOrCreateEnvironment方法中根据WebApplicationType类型选择具体的Environment类型,也就是我们提到过的Servlet类型、Reative类型或者非Web应用类型。

 

 

configureEnvironment

复制代码

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {    if (this.addConversionService) {        ConversionService conversionService = ApplicationConversionService.getSharedInstance();        environment.setConversionService((ConfigurableConversionService) conversionService);    }    // 添加初始的properties(注意:当前并未加载如application.properties/yml的properties)    configurePropertySources(environment, args);    // 添加初始的profile(注意:当前并未加载如application.properties/yml配置profile)    configureProfiles(environment, args);}

复制代码

 

总结

本文,我们大体地讲解了Environment的接口设计、profile和properties的数据结构设计。再从prepareEnvironment方法中看到了Environment是根据webApplicationType匹配后创建的。到这里,Environment相关的内容简单介绍就结束了,我们也初步地为spring地Context创建了一个Environment的对象。

 

refreshContext刷新(上)

run方法中,prepareContext和afterRefresh之间的refreshContext方法正是ioc容器刷新的入口方法。

 

ApplicationContext和BeanFactory

但是在阅读refreshContext方法之前,我们得先区分一下ApplicationContext和BeanFactory两者之间的关系。在之前的文章中,我们并没有把二者进行区分。比如,我们总是说"把Bean注册到ApplicationContext容器"。

在我们的理解中,容器应该是一个空间的概念,用于存放事物的东西。在spring中,存放的是Bean。而BeanFactory提供了这么一个空间用于存放Bean,所以BeanFactory才是Bean所在的主要容器,而不是我们一直说的ApplicationContext。

既然ApplicationContext不是容器,那它又是啥呢?我们称之为"上下文"。"上下文"的概念我们也许不见得那么熟,但是"场景","场所"这样的概念我们应该就比较熟悉了。比如说"拍摄场景","交易场所"等。它们的共同点都是事件发生的地方。所以ApplicationContext正是spring定义的应用程序的事件发生场所,也就是所谓的应用上下文。

 

上面,我了解了BeanFactory作为Bean容器,而ApplicationContext作为上下文。那么Bean容器和上下文之间是什么关系呢?我们可以看一个代码片段

复制代码

// 通用的应用上下文实现public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {        // 默认BeanFactory的实现    private final DefaultListableBeanFactory beanFactory;    // 省略}

复制代码

我们看到BeanFactory是被组合在ApplicationContext当中的,所以它们的其中一种关系就是组合关系。也就是说应用上下文中包含着Bean工厂。

 

接着,我们分别看看ApplicationContext的类图

我们看到ApplicationContext和BeanFactory还存在着继承关系,这意味着ApplicationContext可以对外被当做BeanFactory来使用,这也是为什么我们总是把ApplicationContext当做容器来看的主要原因,因为对外来看两者是一体的。

结合上面的组合关系,我们可以知道对内的话ApplicationContext的BeanFactory相关实现会由内部组合的BeanFactory的实现类来完成具体工作。

到这里,我们基本就明白了ApplicationContext和BeanFactory之间的关系有两种:组合、继承。

后面我们将称呼ApplicationContext为上下文,而BeanFactory为Bean容器,进行区分。

上下文组合Bean工厂

那么BeanFactory是什么时候被组合到ApplicationContext当中的呢?我们先看看ApplicationContext的实现类的类图

注意!springboot默认的servlet项目的ApplicationContext实现类是AnnotationCofnigServletWebServerApplicationContext,所以我们会以它作为实现类来看

我们自下而上,顺着继承链找到GenericApplicationContext我们就会看到之前出现过的代码片段

复制代码

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {    private final DefaultListableBeanFactory beanFactory;    public GenericApplicationContext() {        this.beanFactory = new DefaultListableBeanFactory();    }    // 省略}

复制代码

这里,在GenericApplicationContext的构造方法当中构建了一个DefaultListableBeanFactory的实例对象。DefaultListableBeanFactory也就是BeanFactory的默认实现,那么也就是说在构造ApplicationContext实例对象的时候创建并组合了一个BeanFactory的实例。

 

我们顺便也看看DefaultListableBeanFactory的继承关系吧

这个类图只保留了BeanFactory的东西,设计路线自BeanFactory到DefaultListableBeanFactory也很清晰。

refreshContext刷新过程

下面,我们将正式进行refreshContext方法的阅读。打开refreshContext方法

复制代码

private void refreshContext(ConfigurableApplicationContext context) {    refresh(context);    if (this.registerShutdownHook) {        try {            context.registerShutdownHook();        } catch (AccessControlException ex) {            // Not allowed in some environments.        }    }}

复制代码

跟进refresh方法

protected void refresh(ApplicationContext applicationContext) {    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);    ((AbstractApplicationContext) applicationContext).refresh();}

我们看到,这里调用的是AbstractApplicationContext的refresh方法,顺序AnnotationConfigServletWebServerApplicationContext的继承链向上可以找到AbstractApplicationContext。

 

我们继续跟进AbstractApplicationContext的refresh方法,refresh方法有点长

复制代码

public void refresh() throws BeansException, IllegalStateException {    synchronized (this.startupShutdownMonitor) {        // 刷新前准备,设置flag、时间,初始化properties等        prepareRefresh();        // 获取ApplicationContext中组合的BeanFactory        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();        // 设置类加载器,添加后置处理器等准备        prepareBeanFactory(beanFactory);        try {            // 供子类实现的后置处理            postProcessBeanFactory(beanFactory);            // 调用Bean工厂的后置处理器            invokeBeanFactoryPostProcessors(beanFactory);            // 注册Bean的后置处理器            registerBeanPostProcessors(beanFactory);            // 初始化消息源            initMessageSource();            // 初始化事件广播            initApplicationEventMulticaster();            // 供之类实现的,初始化特殊的Bean            onRefresh();            // 注册监听器            registerListeners();            // 实例化所有的(non-lazy-init)单例Bean            finishBeanFactoryInitialization(beanFactory);            // 发布刷新完毕事件            finishRefresh();        }        catch (BeansException ex) {            //         } finally {                    //         }    }}

复制代码

在中,我们提到了这么一个初始化过程:

annotation或者xml中Bean的配置 --> 内存中的BeanDefinition --> Bean

也就是实际的配置,转化成内存中的配置对象,再根据配置对象转化成具体的实例对象。说白了就是从元数据到实例的一个转化过程。

为什么会提及这么一个转化过程呢?因为我们的refresh过程主要包含的就是其中的一步,也就是从annotation或者xml的Bean配置 --> 内存中的BeanDefinition的过程。这个过程的实现在调用Bean工厂的后置处理器的时候完成,也就是invokeBeanFactoryPostProcessors方法

 

我们跟进invokeBeanFactoryPostProcessors方法

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());    // }

这里先获取了所有后置处理器,然后调用处理。再跟进PostProcessorRegistrationDelegate的invokeBeanFactoryFactoryPostProcessors方法

该方法很长,我们删减掉大部分内容

复制代码

public static void invokeBeanFactoryPostProcessors(        ConfigurableListableBeanFactory beanFactory,         List
beanFactoryPostProcessors ) { // if (beanFactory instanceof BeanDefinitionRegistry) { // while (reiterate) { // 调用BeanDefinition注册的后置处理器 invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); // } // } else { // } //}

复制代码

可以看到,调用后置处理器的时候会调用到注册BeanDefinition的后置处理器。也就是从这里开始作为BeanDefinition的注册入口

 

跟进invokeBeanDefinitionRegistryPostProcessors

复制代码

private static void invokeBeanDefinitionRegistryPostProcessors(            Collection
postProcessors, BeanDefinitionRegistry registry ) { for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeanDefinitionRegistry(registry); }}

复制代码

这里我们需要断点一下,看看有哪些后置处理器处理BeanDefinition注册

我们看到了ConfigurationClassPostProcessor也就是它完成BeanDefinition注册这项工作的

 

我们跟进ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {    // 省略    processConfigBeanDefinitions(registry);}

继续跟进,我们看到processConfigBeanDefinitions方法挺长的,进行了大量的缩减

复制代码

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {    List
configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { // } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { // 默认仅有主类被添加 configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // // 解析被 @Configuration 注解的类 ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set
candidates = new LinkedHashSet<>(configCandidates); // do { // 解析的核心方法 parser.parse(candidates); parser.validate(); // candidates.clear(); // } while (!candidates.isEmpty()); // }

复制代码

中,也就是在prepareContext方法的核心逻辑里,main方法所在的主类将会被作为BeanDefinition加载到BeanFactory当中。而在这里,该主类将被作为一个配置类被解析,解析器即ConfigurationClassParser。

 

我们跟进parse方法看看

复制代码

public void parse(Set
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) {} catch (Throwable ex) {} } this.deferredImportSelectorHandler.process();}

复制代码

继续跟进parse方法

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {    processConfigurationClass(new ConfigurationClass(metadata, beanName));}

可以看到主类作为配置类的解析过程将从processConfigurationClass这里开始

 

 

总结

到这里,ioc容器的refresh过程先做一个小结。我们知道了上下文和Bean容器是继承关系又是组合关系。refreshContext的核心就是为了加载BeanDefinition,而加载BeanDefinition将从main方法所在的主类开始,主类作为一个配置类将由ConfigurationClassParser解析器来完成解析的职责。下一篇文章,我们将会看到从主类中解析出BeanDefinition的主要逻辑。

 

refreashContext(下) 

,我们知道了解析过程将从解析main方法所在的主类开始。在文章的最后我们稍微看了一下ConfigurationClassParser这个解析器的parse方法

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {    processConfigurationClass(new ConfigurationClass(metadata, beanName));}

本文将从这个parse方法继续下去,看看解析main方法所在的主类这个过程主要发生了什么。

 

跟进processConfigurationClass方法

复制代码

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {    //     // 由main方法所在的主类开始,向超类逐层向上递归解析    SourceClass sourceClass = asSourceClass(configClass);    do {        // 这里包含了解析单个配置类的核心逻辑        sourceClass = doProcessConfigurationClass(configClass, sourceClass);    } while (sourceClass != null);    // }

复制代码

我们注意到,doProcessConfigurationClass方法将会完成解析的主要工作,但是又会返回一个新的sourceClass用于解析。而这个新的sourceClass会是当前上一个sourceClass的父类。所在解析过程是一个递归过程,由主类开始,向超类逐层向上递归解析处理。

 

继续跟进doProcessConfigurationClass方法,我们看看这个核心的解析逻辑。代码量对较多,我们只关注两个点

1)@ComponentScan注解解析,扫描并注册BeanDefinition

2)获取超类向上递归

复制代码

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)        throws IOException {    //    // 处理@ComponentScan注解    Set
componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { // 遍历@ComponentScan的属性值 for (AnnotationAttributes componentScan : componentScans) { // 解析扫描 Set
scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // } } // // 判断是否有超类 if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // 返回待解析的超类 return sourceClass.getSuperClass(); } } // 没有超类,则解析完毕 return null;}

复制代码

首先我们main方法所在的主类是被@SpringbootApplication注解所标注的,而@SpringbootApplication组合了@ComponentScan。所谓解析主类的时候将会处理@ComponentScan注解。解析@ComponentScan的主要工作的实现由ComponentScanAnnotationParser这个解析器来完成。通常这个解析器完成之后,被扫描到的BeanDefinition将会被注册到BeanFactory当中。

doProcessConfigurationClass方法的最后一部分是从当前被解析的类元数据中获取超类,如果超类存在且需要被解析那么就当做返回值返回回去,从而被外层的方法给递归处理。

 

@ComponentScan注解解析

下面,我们跟进ComponentScanAnnotationParser这个解析器的parse方法,看看@ComponentScan的处理过程

复制代码

public Set
parse( AnnotationAttributes componentScan, final String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); // Set
basePackages = new LinkedHashSet<>(); // 从basePackages配置获取扫描路径 String[] basePackagesArray = componentScan.getStringArray("basePackages"); for (String pkg : basePackagesArray) { String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); Collections.addAll(basePackages, tokenized); } // 从basePackageClasses获取扫描路径 for (Class
clazz : componentScan.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } if (basePackages.isEmpty()) { // 默认添加当前被解析类的路径作为根路径 basePackages.add(ClassUtils.getPackageName(declaringClass)); } // // 扫描目标路径 return scanner.doScan(StringUtils.toStringArray(basePackages));}

复制代码

这里获取了一个扫描器,然后找到了待扫描的路径,最后利用扫描器去扫描路径。

 

跟进doScan方法

复制代码

protected Set
doScan(String... basePackages) { Set
beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // 扫描获取BeanDefinition Set
candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { // if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // 注册BeanDefinition到BeanFactory registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions;}

复制代码

我们看到,findCandidateComponents方法将会根据扫描路径获取BeanDefinition,而扫描出来的BeanDefinition将会进入注册方法registerBeanDefinition。

 

我们先跟进findCandidateComponents方法看看如何扫描获取

复制代码

public Set
findCandidateComponents(String basePackage) { if (this.componentsIndex != null && indexSupportsIncludeFilters()) { return addCandidateComponentsFromIndex(this.componentsIndex, basePackage); } else { // 进入 return scanCandidateComponents(basePackage); }}

复制代码

再跟进scanCandidateComponents方法

复制代码

private Set
scanCandidateComponents(String basePackage) { Set
candidates = new LinkedHashSet<>(); try { // 拼接出搜索路径,例如:classpath*:cn/lay/springbootlearn/**/*.class String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; // 获取搜索路径下待处理资源 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); // for (Resource resource : resources) { // if (resource.isReadable()) { try { MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { // 转化成BeanDefinition ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); // } else { // } } catch (Throwable ex) { // } } else { // } } } catch (IOException ex) { // } return candidates;}

复制代码

basePackage将会被拼接成搜索路径,如:classpath*:cn/lay/springbootlearn/**/*.class。而getResources方法将会从搜索路径中获取相应的资源对象,这些资源对象并最终被读取并转化为BeanDefinition。

到这里,@ComponentScan扫描的Bean就已经成为了BeanDefinition,但是还有一步就是将BeanDefinition注册到BeanFactory当中。

 

我们回到doScan方法,并跟进registerBeanDefinition方法,看看注册过程

protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);}

继续跟进registerBeanDefinition

复制代码

public static void registerBeanDefinition(        BeanDefinitionHolder definitionHolder,         BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {    String beanName = definitionHolder.getBeanName();    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());    // 省略}

复制代码

我们看到这里直接注册到了BeanDefinitionRegistry中去了,其实就是注册到BeanFactory当中。BeanFactory的默认实现类DefaultListableBeanFactory实现了BeanDefinitionRegistry,所以DefaultListableBeanFactory即是BeanDefinition的注册位置。

 

跟进DefaultListableBeanFactory的registerBeanDefinition方法

复制代码

private final Map
beanDefinitionMap = new ConcurrentHashMap<>(256);public void registerBeanDefinition( String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { // if (existingDefinition != null) { // } else { if (hasBeanCreationStarted()) { // Cannot modify startup-time collection elements anymore (for stable iteration) synchronized (this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); // } } else { // } // } // }

复制代码

最终,也就是将BeanDefinition添加到一个key-value的集合当中,这样就完成了注册工作。

 

总结

到这里,ioc容器refresh过程部分就结束了。我们略过不少东西,将解析主类、解析@ComponentScan扫描Bean定义、注册到BeanFactory这个主要的流程过了一遍。当然,在这里可能还存在一个比较困惑的点。前面的文章中,我们提过几次:配置 -> BeanDefinition -> Bean这样一个过程。ioc的refresh过程却只有从配置 -> BeanDefinition这样一个过程,那么BeanDefinition -> Bean这个过程又在哪里呢?后面ioc容器注入部分将说明这部分内容。

 

转载地址:https://liuhuiyao.blog.csdn.net/article/details/106962769 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:springboot run方法初始化,spring 初始化,以及bean的生命周期。mvc的初始化以及生命周期
下一篇:jdk1.8 1.7 ConcurrentHashMap HashMap 实现对比

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年04月08日 17时02分42秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章