SpringBoot 源碼解析筆記


作者筆記倉庫https://github.com/seazean/javanotes

歡迎各位關注我的筆記倉庫,clone 倉庫到本地后使用 Typora 閱讀效果更好。

如果大家只關注 SpringBoot 如何自動裝配,可以只看“注解分析”和“裝配流程”兩個小節


一、啟動流程

應用啟動:

@SpringBootApplication
public class BootApplication {
    public static void main(String[] args) {
        // 啟動代碼
        SpringApplication.run(BootApplication.class, args);
    }
}

SpringApplication 構造方法:

  • this.resourceLoader = resourceLoader:資源加載器,初始為 null

  • this.webApplicationType = WebApplicationType.deduceFromClasspath():判斷當前應用的類型,是響應式還是 Web 類

  • this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories()獲取引導器

    • META-INF/spring.factories 文件中找 org.springframework.boot.Bootstrapper
    • 尋找的順序:classpath → spring-beans → boot-devtools → springboot → boot-autoconfigure
  • setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class))獲取初始化器

    • META-INF/spring.factories 文件中找 org.springframework.context.ApplicationContextInitializer
  • setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))獲取監聽器

    • META-INF/spring.factories 文件中找 org.springframework.context.ApplicationListener
  • this.mainApplicationClass = deduceMainApplicationClass():獲取出 main 程序類

SpringApplication#run(String... args):

  • StopWatch stopWatch = new StopWatch():停止監聽器,監控整個應用的啟停

  • stopWatch.start():記錄應用的啟動時間

  • bootstrapContext = createBootstrapContext()創建引導上下文環境

    • bootstrapContext = new DefaultBootstrapContext():創建默認的引導類環境
    • this.bootstrapRegistryInitializers.forEach():遍歷所有的引導器調用 initialize 方法完成初始化設置
  • configureHeadlessProperty():讓當前應用進入 headless 模式

  • listeners = getRunListeners(args):獲取所有 RunListener(運行監聽器)

    • META-INF/spring.factories 文件中找 org.springframework.boot.SpringApplicationRunListener
  • listeners.starting(bootstrapContext, this.mainApplicationClass)遍歷所有的運行監聽器調用 starting 方法

  • applicationArguments = new DefaultApplicationArguments(args):獲取所有的命令行參數

  • environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments)准備環境

    • environment = getOrCreateEnvironment():返回或創建基礎環境信息對象

      • switch (this.webApplicationType):根據當前應用的類型創建環境
        • case SERVLET:Web 應用環境對應 ApplicationServletEnvironment
        • case REACTIVE:響應式編程對應 ApplicationReactiveWebEnvironment
        • default:默認為 Spring 環境 ApplicationEnvironment
    • configureEnvironment(environment, applicationArguments.getSourceArgs()):讀取所有配置源的屬性值配置環境

    • ConfigurationPropertySources.attach(environment):屬性值綁定環境信息

      • sources.addFirst(ATTACHED_PROPERTY_SOURCE_NAME,..):把 configurationProperties 放入環境的屬性信息頭部
    • listeners.environmentPrepared(bootstrapContext, environment)運行監聽器調用 environmentPrepared(),EventPublishingRunListener 發布事件通知所有的監聽器當前環境准備完成

    • DefaultPropertiesPropertySource.moveToEnd(environment):移動 defaultProperties 屬性源到環境中的最后一個源

    • bindToSpringApplication(environment):與容器綁定當前環境

    • ConfigurationPropertySources.attach(environment):重新將屬性值綁定環境信息

      • sources.remove(ATTACHED_PROPERTY_SOURCE_NAME):從環境信息中移除 configurationProperties

      • sources.addFirst(ATTACHED_PROPERTY_SOURCE_NAME,..):把 configurationProperties 重新放入環境信息

  • configureIgnoreBeanInfo(environment):配置忽略的 bean

  • printedBanner = printBanner(environment):打印 SpringBoot 標志

  • context = createApplicationContext()創建 IOC 容器

    switch (this.webApplicationType):根據當前應用的類型創建 IOC 容器

    • case SERVLET:Web 應用環境對應 AnnotationConfigServletWebServerApplicationContext
    • case REACTIVE:響應式編程對應 AnnotationConfigReactiveWebServerApplicationContext
    • default:默認為 Spring 環境 AnnotationConfigApplicationContext
  • context.setApplicationStartup(this.applicationStartup):設置一個啟動器

  • prepareContext():配置 IOC 容器的基本信息

    • postProcessApplicationContext(context):后置處理流程

    • applyInitializers(context):獲取所有的初始化器調用 initialize() 方法進行初始化

    • listeners.contextPrepared(context):所有的運行監聽器調用 environmentPrepared() 方法,EventPublishingRunListener 發布事件通知 IOC 容器准備完成

    • listeners.contextLoaded(context):所有的運行監聽器調用 contextLoaded() 方法,通知 IOC 加載完成

  • refreshContext(context)刷新 IOC 容器

    • Spring 的容器啟動流程
    • invokeBeanFactoryPostProcessors(beanFactory)實現了自動裝配
    • onRefresh()創建 WebServer 使用該接口
  • afterRefresh(context, applicationArguments):留給用戶自定義容器刷新完成后的處理邏輯

  • stopWatch.stop():記錄應用啟動完成的時間

  • callRunners(context, applicationArguments):調用所有 runners

  • listeners.started(context):所有的運行監聽器調用 started() 方法

  • listeners.running(context):所有的運行監聽器調用 running() 方法

    • 獲取容器中的 ApplicationRunner、CommandLineRunner

    • AnnotationAwareOrderComparator.sort(runners):合並所有 runner 並且按照 @Order 進行排序

    • callRunner():遍歷所有的 runner,調用 run 方法

  • handleRunFailure(context, ex, listeners)處理異常,出現異常進入該邏輯

    • handleExitCode(context, exception):處理錯誤代碼
    • listeners.failed(context, exception):運行監聽器調用 failed() 方法
    • reportFailure(getExceptionReporters(context), exception):通知異常

二、注解分析

SpringBoot 定義了一套接口規范,這套規范規定 SpringBoot 在啟動時會掃描外部引用 jar 包中的 META-INF/spring.factories 文件,將文件中配置的類型信息加載到 Spring 容器,並執行類中定義的各種操作,對於外部的 jar 包,直接引入一個 starter 即可

@SpringBootApplication 注解是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的集合

  • @SpringBootApplication 注解

    @Inherited
    @SpringBootConfiguration	//代表 @SpringBootApplication 擁有了該注解的功能
    @EnableAutoConfiguration	//同理
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    // 掃描被 @Component (@Service,@Controller)注解的 bean,容器中將排除TypeExcludeFilter 和 AutoConfigurationExcludeFilter
    public @interface SpringBootApplication { }
    
  • @SpringBootConfiguration 注解:

    @Configuration	// 代表是配置類
    @Indexed
    public @interface SpringBootConfiguration {
    	@AliasFor(annotation = Configuration.class)
    	boolean proxyBeanMethods() default true;
    }
    

    @AliasFor 注解:表示別名,可以注解到自定義注解的兩個屬性上表示這兩個互為別名,兩個屬性其實是同一個含義相互替代

  • @ComponentScan 注解:默認掃描當前包及其子級包下的所有文件

  • @EnableAutoConfiguration 注解:啟用 SpringBoot 的自動配置機制

    @AutoConfigurationPackage	
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration { 
    	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
        Class<?>[] exclude() default {}; 
        String[] excludeName() default {};
    }
    
    • @AutoConfigurationPackage:將添加該注解的類所在的 package 作為自動配置 package 進行管理,把啟動類所在的包設置一次,為了給各種自動配置的第三方庫掃描用,比如帶 @Mapper 注解的類,Spring 自身是不能識別的,但自動配置的 Mybatis 需要掃描用到,而 ComponentScan 只是用來掃描注解類,並沒有提供接口給三方使用

      @Import(AutoConfigurationPackages.Registrar.class)	// 利用 Registrar 給容器中導入組件
      public @interface AutoConfigurationPackage { 
      	String[] basePackages() default {};	//自動配置包,指定了配置類的包
          Class<?>[] basePackageClasses() default {};
      }
      

      register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])):注冊 BD

      • new PackageImports(metadata).getPackageNames():獲取添加當前注解的類的所在包
      • registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames)):存放到容器中
        • new BasePackagesBeanDefinition(packageNames):把當前主類所在的包名封裝到該對象中
    • @Import(AutoConfigurationImportSelector.class):首先自動裝配的核心類

      容器刷新時執行:invokeBeanFactoryPostProcessors() → invokeBeanDefinitionRegistryPostProcessors() → postProcessBeanDefinitionRegistry() → processConfigBeanDefinitions() → parse() → process() → processGroupImports() → getImports() → process() → AutoConfigurationImportSelector#getAutoConfigurationEntry()

      protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
          if (!isEnabled(annotationMetadata)) {
              return EMPTY_ENTRY;
          }
          // 獲取注解屬性,@SpringBootApplication 注解的 exclude 屬性和 excludeName 屬性
          AnnotationAttributes attributes = getAttributes(annotationMetadata);
          // 獲取所有需要自動裝配的候選項
          List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
          // 去除重復的選項
          configurations = removeDuplicates(configurations);
          // 獲取注解配置的排除的自動裝配類
          Set<String> exclusions = getExclusions(annotationMetadata, attributes);
          checkExcludedClasses(configurations, exclusions);
          // 移除所有的配置的不需要自動裝配的類
          configurations.removeAll(exclusions);
          // 過濾,條件裝配
          configurations = getConfigurationClassFilter().filter(configurations);
          // 獲取 AutoConfigurationImportListener 類的監聽器調用 onAutoConfigurationImportEvent 方法
          fireAutoConfigurationImportEvents(configurations, exclusions);
          // 包裝成 AutoConfigurationEntry 返回
          return new AutoConfigurationEntry(configurations, exclusions);
      }
      

      AutoConfigurationImportSelector#getCandidateConfigurations:獲取自動配置的候選項

      • List<String> configurations = SpringFactoriesLoader.loadFactoryNames():加載自動配置類

        參數一:getSpringFactoriesLoaderFactoryClass() 獲取 @EnableAutoConfiguration 注解類

        參數二:getBeanClassLoader() 獲取類加載器

        • factoryTypeName = factoryType.getName():@EnableAutoConfiguration 注解的全類名
        • return loadSpringFactories(classLoaderToUse).getOrDefault():加載資源
          • urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION):獲取資源類
          • FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"加載的資源的位置
      • return configurations:返回所有自動裝配類的候選項

    • 從 spring-boot-autoconfigure-2.5.3.jar/META-INF/spring.factories 文件中獲取自動裝配類,進行條件裝配,按需裝配


三、裝配流程

Spring Boot 通過 @EnableAutoConfiguration 開啟自動裝配,通過 SpringFactoriesLoader 加載 META-INF/spring.factories 中的自動配置類實現自動裝配,自動配置類其實就是通過 @Conditional 注解按需加載的配置類(JVM 類加載機制),想要其生效必須引入 spring-boot-starter-xxx 包實現起步依賴

  • SpringBoot 先加載所有的自動配置類 xxxxxAutoConfiguration
  • 每個自動配置類進行條件裝配,默認都會綁定配置文件指定的值(xxxProperties 和配置文件進行了綁定)
  • SpringBoot 默認會在底層配好所有的組件,如果用戶自己配置了以用戶的優先
  • 定制化配置:
    • 用戶可以使用 @Bean 新建自己的組件來替換底層的組件
    • 用戶可以去看這個組件是獲取的配置文件前綴值,在配置文件中修改

以 DispatcherServletAutoConfiguration 為例:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 類中的 Bean 默認不是單例
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
// 條件裝配,環境中有 DispatcherServlet 類才進行自動裝配
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
	// 注冊的 DispatcherServlet 的 BeanName
	public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

	@Configuration(proxyBeanMethods = false)
	@Conditional(DefaultDispatcherServletCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
    // 綁定配置文件的屬性,從配置文件中獲取配置項
	@EnableConfigurationProperties(WebMvcProperties.class)
	protected static class DispatcherServletConfiguration {
		
        // 給容器注冊一個 DispatcherServlet,起名字為 dispatcherServlet
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
            // 新建一個 DispatcherServlet 設置相關屬性
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
            // spring.mvc 中的配置項獲取注入,沒有就填充默認值
			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
			// ......
            // 返回該對象注冊到容器內
			return dispatcherServlet;
		}

		@Bean
        // 容器中有這個類型組件才進行裝配
		@ConditionalOnBean(MultipartResolver.class)
        // 容器中沒有這個名字 multipartResolver 的組件
		@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
        // 方法名就是 BeanName
		public MultipartResolver multipartResolver(MultipartResolver resolver) {
			// 給 @Bean 標注的方法傳入了對象參數,這個參數就會從容器中找,因為用戶自定義了該類型,以用戶配置的優先
            // 但是名字不符合規范,所以獲取到該 Bean 並返回到容器一個規范的名稱:multipartResolver
			return resolver;
		}
	}
}
//將配置文件中的 spring.mvc 前綴的屬性與該類綁定
@ConfigurationProperties(prefix = "spring.mvc")	
public class WebMvcProperties { }

四、內嵌容器

(補充內容,WebServer)

SpringBoot 嵌入式 Servlet 容器,默認支持的 webServe:Tomcat、Jetty、Undertow

配置方式:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion> <!--必須要把內嵌的 Tomcat 容器-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

創建 Web 容器:

  • SpringApplication.run(BootApplication.class, args):應用啟動

  • ConfigurableApplicationContext.run()

    • context = createApplicationContext()創建容器

      • applicationContextFactory = ApplicationContextFactory.DEFAULT

        ApplicationContextFactory DEFAULT = (webApplicationType) -> {
            try {
                switch (webApplicationType) {
                    case SERVLET:
                        // Servlet 容器,繼承自 ServletWebServerApplicationContext
                        return new AnnotationConfigServletWebServerApplicationContext();
                    case REACTIVE:
                        // 響應式編程
                        return new AnnotationConfigReactiveWebServerApplicationContext();
                    default:
                        // 普通 Spring 容器
                        return new AnnotationConfigApplicationContext();
                }
            } catch (Exception ex) {
                throw new IllegalStateException();
            }
        }
        
      • applicationContextFactory.create(this.webApplicationType):根據應用類型創建容器

    • refreshContext(context):容器啟動

內嵌容器工作流程:

  • Web 應用啟動,SpringBoot 導入 Web 場景包 tomcat,創建一個 Web 版的 IOC 容器 ServletWebServerApplicationContext
  • ServletWebServerApplicationContext 容器啟動時進入 refresh() 邏輯,Spring 容器啟動邏輯中,在實例化非懶加載的單例 Bean 之前有一個方法 onRefresh(),留給子類去擴展,該容器就是重寫這個方法創建 WebServer

    protected void onRefresh() {
        //省略....
    	createWebServer();
    }
    private void createWebServer() {
        ServletWebServerFactory factory = getWebServerFactory();
        this.webServer = factory.getWebServer(getSelfInitializer());
        createWebServer.end();
    }
    

    獲取 WebServer 工廠 ServletWebServerFactory,並且獲取的數量不等於 1 會報錯,Spring 底層有三種:

    TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory

  • 自動配置類 ServletWebServerFactoryAutoConfiguration 導入了 ServletWebServerFactoryConfiguration(配置類),根據條件裝配判斷系統中到底導入了哪個 Web 服務器的包,創建出服務器並啟動

  • 默認是 web-starter 導入 tomcat 包,容器中就有 TomcatServletWebServerFactory,創建出 Tomcat 服務器並啟動,

    public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
    	// 初始化
       	initialize();
    }
    

    初始化方法 initialize 中有啟動方法:this.tomcat.start()


免責聲明!

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



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