SpringBoot原理深入及源碼剖析(二) 自定義Starter及SpringBoot執行原理


自定義Starter

SpringBoot starter機制

SpringBoot由眾多starter組成(一系列的自動化配置的starter插件),SpringBoot之所以流行,也是因為starter。

starter是SpringBoot非常重要的一部分,可以理解為一個可拔插式的插件,正是這些starter使得使用某個功能的開發者不需要關注各種依賴庫的處理,不需要具體的配置信息,由SpringBoot自動通過classpath路徑下的類發現需要的Bean,並植入相應的Bean。

例如,想用Redis插件,那么可以使用spring-boot-starter-redis;想用MongoDB插件,那么可以使用spring-boot-starter-data-mongodb。

為什么要自定義starter

開發過程中,經常會有一些獨立於業務之外的配置模塊。如果我們將這些可獨立於業務代碼之外的功能配置模塊封裝成一個個starter,復用的時候只需要將其在pom中引入依賴即可,SpringBoot將為我們完成自動裝配。

自定義starter的命名規則

SpringBoot官方提供的starter是以spring-boot-starter-xxx的方式命名的。官方建議自定義的starter使用xxx-spring-boot-starter的命名規則,以區分SpringBoot生態提供的starter。

自定義starter

(1)新建maven jar工程,工程名為hardy-spring-boot-starter,導入依賴:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
        <version>2.5.0</version>
    </dependency>
</dependencies>

(2)編寫javaBean:

package com.hardy;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

/**
 * @Author: HardyYao
 * @Date: 2021/6/6
 */
@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {

    private int id;

    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "SimpleBean{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

(3)編寫配置類MyAutoConfiguration:

package com.hardy.config;

import com.hardy.SimpleBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

/**
 * @Author: HardyYao
 * @Date: 2021/6/6
 */
@Configuration
@ConditionalOnClass // @ConditionalOnClass:當類路徑classpath下有指定的類的情況下進行自動配置
public class MyAutoConfiguration {

    static {
        System.out.println("MyAutoConfiguration init....");
    }

    @Bean
    public SimpleBean simpleBean(){
        return new SimpleBean();
    }
    
}

(4)resources下創建/META-INF/spring.factories:

注意:META-INF是我們自己手動創建的目錄,spring.factories也是我們自己手動創建的文件,在該文件中配置自己的自動配置類:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hardy.config.MyAutoConfiguration

使用自定義starter

(1)導入自定義starter的依賴:

<dependency>
    <groupId>com.hardy</groupId>
    <artifactId>hardy-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

(2)在全局配置文件中配置屬性值:

simplebean.id=1
simplebean.name=自定義starter 

(3)編寫測試方法:

// 測試自定義starter
@Autowired
private SimpleBean simpleBean;

@Test
public void hardyStarterTest(){
    System.out.println(simpleBean);
} 

(4)運行結果:

SpringBoot執行原理

每個SpringBoot項目都有一個主程序啟動類,在主程序啟動類中有一個啟動項目的main()方法,在該方法中通過執行SpringApplication.run()即可啟動整個SpringBoot項目。

問題:那么SpringApplication.run()方法到底是如何做到啟動整個SpringBoot項目的呢?

下面我們來看run()方法內部的源碼,核心代碼具體如下:

@SpringBootApplication
public class SpringbootDemoApplication {

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

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

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

從上述源碼可以看出,SpringApplication.run()方法內部執行了兩個操作,分別是SpringApplication實例的初始化創建和調用run()方法啟動項目,這兩個階段的實現具體說明如下:

1、SpringApplication實例的初始化創建

查看SpringApplication實例對象初始化創建的源碼信息,核心代碼如下:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 把項目啟動類.class設置為屬性存儲起來
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    // 判斷當前webApplicationType應用的類型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
    // 設置初始化器(Initializer),最后會調用這些初始化器
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 設置監聽器(Listener)
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    // 用於推斷並設置項目main()方法啟動的主程序啟動類
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

從上述源碼可以看出,SpringApplication的初始化過程主要包括四部分,具體說明如下所示:

(1)this.webApplicationType = WebApplicationType.deduceFromClasspath()

用於判斷當前webApplicationType應用的類型。deduceFromClasspath()方法用於查看Classpath類路徑下是否存在某個特征類,從而判斷當前webApplicationType類型是SERVLET應用(Spring5之前的傳統MVC應用)還是REACTIVE應用(Spring5之后的WebFlux交互式應用)。

(2)this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))

用於SpringApplication應用的初始化器設置。在初始化器設置過程中,會使用Spring類加載器SpringFactoriesLoader從META-INF類路徑下的spring.factories文件中獲取所有可用的應用初始化器類ApplicationContextInitializer。

(3)this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))

用於SpringApplication應用的監聽器設置。監聽器設置的過程與上一步初始化器設置的過程基本一樣,也是使用SpringFactoriesLoader從META-INF類路徑下的spring.factories文件中獲取所有可用的監聽器類ApplicationListener。

(4)this.mainApplicationClass = this.deduceMainApplicationClass()

用於推斷並設置項目main()方法啟動的主程序啟動類。

2、項目的初始化啟動

分析完(new SpringApplication(primarySources)).run(args)源碼前一部分SpringApplication實例對象的初始化創建后,我們來查看run(args)方法執行的項目初始化啟動過程,其核心代碼具體如下:

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    ConfigurableApplicationContext context = null;
    this.configureHeadlessProperty();
    // 1、獲取並啟動監聽器
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 2、根據SpringApplicationRunListeners以及參數來准備環境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        // 准備Banner打印器——即啟動SpringBoot的時候打印在控制台的ASCII藝術字體
        Banner printedBanner = this.printBanner(environment);
        // 3、創建Spring容器
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 4、Spring容器前置處理
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        // 5、刷新容器
        this.refreshContext(context);
        // 6、Spring容器后置處理
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        // 7、發出結束執行的事件
        listeners.started(context);
        // 8、執行Runners,返回容器
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        this.handleRunFailure(context, var10, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

1、獲取並啟動監聽器

this.getRunListeners(args)和listeners.starting()方法主要用於獲取SpringApplication實例初始化過程中 初始化的SpringApplicationRunListener監聽器並運行。

2、根據SpringApplicationRunListeners以及參數來准備環境

this.prepareEnvironment(listeners, applicationArguments)方法主要用於對項目運行環境進行預設值,同時通過this.configureIgnoreBeanInfo(environment)方法排除一些不需要的運行環境。

3、創建Spring容器

根據webApplicationType進行判斷,確定容器類型,如果該類型為SERVLET類型,會通過反射加載對應的字節碼,也就是AnnotationConfigServletWebServerApplicationContext,接着使用之前初始化設置的context(應用上下文環境)、environment(項目運行環境)、listeners(運行監聽器)、applicationArguments(項目參數)和 printedBanner(項目圖標信息)進行上下文的組裝配置,並刷新配置。

4、Spring容器前置處理

這一步主要是在容器刷新之前的准備動作。設置容器環境,包括各種變量等等,其中包含一個非常關鍵的操作:將啟動類注入容器,為后續開啟自動化配置奠定基礎。

5、刷新容器

開啟刷新Spring容器,通過refresh方法對整個IOC容器的初始化(包括bean資源的定位、解析、注冊等等),同時向JVM運行時注冊一個關機鈎子(shutdown hook),在JVM關機時會關閉這個上下文,除非當時它已經關閉。

6、Spring容器后置處理

擴展接口,設計模式中的模板方法,默認為空實現。如果有自定義需求,可以重寫該方法。比如打印一些啟動結束log,或者一些其他的后置處理。

7、發出結束執行的事件

獲取EventPublishingRunListener監聽器,並執行其started方法,並且將創建的Spring容器傳送進去,創建一個ApplicationStartedEvent事件,並執行ConfigurableApplicationContext的publishEvent方法,也就是說這里是在Spring容器中發布事件,並不是在SpringApplication中發布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的監聽器發布啟動事件

8、執行Runners

用於調用項目中自定義的執行器XxxRunner類,使得在項目啟動完成后立即執行一些特定程序。其中,SpringBoot提供的執行器接口有ApplicationRunner和CommandLineRunner兩種,在使用時只需要自定義一個執行器類實現其中一個接口並重寫對應的run()方法接口,然后SpringBoot項目啟動后立即執行這些特定程序。

 

下圖是SpringBoot執行的流程圖,通過下圖,可以直觀地了解SpringBoot的整體執行流程和主要啟動階段:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM