SpringBoot啟動原理(基於2.3.9.RELEASE版本)


版本

以下源碼的 SpringBoot 版本:2.3.9.RELEASE。

總體上

分為兩大步:

  • 啟動類上注解@SpringBootApplication
  • 啟動類中的main方法:org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)

main方法上的注解:@SpringBootApplication

源碼

三個注解核心注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@SpringBootConfiguration

根據Javadoc可知,該注解作用就是將當前的類作為一個JavaConfig,然后觸發注解@EnableAutoConfiguration@ComponentScan處理,本質上與@Configuration注解沒有區別。

@ComponentScan

掃描的 Spring 對應的組件,如 @Componet@Repository

我們可以通過 basePackages 等屬性來細粒度的定制 @ComponentScan 自動掃描的范圍,如果不指定,則默認Spring框架實現會從聲明 @ComponentScan 所在類的package進行掃描,所以 SpringBoot啟動類最好是放在根package下,我們自定義的類就放在對應的子package下,這樣就可以不指定 basePackages

@EnableAutoConfiguration

@EnableAutoConfiguration總結

  • @AutoConfigurationPackage

    • 注冊當前啟動類的根 package
    • 注冊 org.springframework.boot.autoconfigure.AutoConfigurationPackagesBeanDefinition
  • @Import(AutoConfigurationImportSelector.class)

    • 可以看到實現了 DeferredImportSelector 接口,該接口繼承自 ImportSelector,根據 Javadoc 可知,多用於導入被 @Conditional 注解的Bean,之后會進行 filter 操作
    • AutoConfigurationImportSelector.AutoConfigurationGroup#process 方法,SpringBoot 啟動時會調用該方法,進行自動裝配的處理,見SpringApplication#run(java.lang.String...)源碼解析
      • SpringApplication#run(java.lang.String...)
      • SpringApplication#refreshContext(即 Spring IOC 容器初始化的過程中
      • ConfigurationClassParser#parse
      • AutoConfigurationImportSelector.AutoConfigurationGroup#process
    • 通過SpringFactoriesLoader#loadFactoryNames獲取應考慮的自動配置名稱,例如來源於 spring-boot-autoconfigure jar包下的 META-INF/spring.factories 文件下的配置
    • 通過 filter 過濾掉當前環境不需要自動裝配的類,各種 @Conditional 不滿足就被過濾掉
    • 需要自動裝配的全路徑類名注冊到 SpringIOC 容器,自此 SpringBoot 自動裝配完成!

源碼

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

SpringBoot 自動裝配的核心注解,在 Spring 框架中就提供了各種以@Enable開頭的注解,例如: @EnableCircuitBreaker@EnableScheduling等;

@EnableAutoConfiguration借助@Import的支持,收集和注冊特定場景相關的bean定義;

自動裝配的類,通常是 @Configuration 類,通過 SpringFactoriesLoader 加載到 Spring 容器。

@AutoConfigurationPackage

注冊當前啟動類的根package

注冊 org.springframework.boot.autoconfigure.AutoConfigurationPackagesBeanDefinition

AutoConfigurationImportSelector

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
  ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
  • 可以看到實現了 DeferredImportSelector 接口,該接口繼承自ImportSelector,根據Javadoc可知,多用於導入被@Conditional注解的Bean

  • DeferredImportSelector接口中有個process方法,SpringBoot啟動時會調用該方法,進行自動裝配的處理,大體流程如下:

    • SpringApplication#run(java.lang.String...)
    • SpringApplication#refreshContext
    • ConfigurationClassParser#parse
    • AutoConfigurationImportSelector.AutoConfigurationGroup#process
  • AutoConfigurationImportSelector.AutoConfigurationGroup#process方法的源碼:

    調用了AutoConfigurationImportSelector#getAutoConfigurationEntry方法,獲取需要自動裝配類

    public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
     Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
       () -> String.format("Only %s implementations are supported, got %s",
         AutoConfigurationImportSelector.class.getSimpleName(),
         deferredImportSelector.getClass().getName()));
        // 獲取需要自動裝配類
     AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
       .getAutoConfigurationEntry(annotationMetadata);
     this.autoConfigurationEntries.add(autoConfigurationEntry);
     for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
     }
    }
    
  • AutoConfigurationImportSelector#getAutoConfigurationEntry大體流程如下:

    • 通過SpringFactoriesLoader#loadFactoryNames獲取應考慮的自動配置名稱,例如來源於 spring-boot-autoconfigure jar包下的META-INF/spring.factories文件下的配置

    • 通過filter過濾掉當前環境不需要自動裝配的類,比如沒有集成RabbitMQ,就不需要,或者有的條件@Conditional不滿足也不需要自動裝配

    • 返回需要自動裝配的全路徑類名

    • 源碼如下:

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
     if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
     }
     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);
        // 通過filter過濾掉當前環境不需要自動裝配的類,比如沒有集成RabbitMQ,就不需要,或者有的條件@Conditional不滿足也不需要自動裝配
     configurations = getConfigurationClassFilter().filter(configurations);
     fireAutoConfigurationImportEvents(configurations, exclusions);
        // 返回需要自動裝配的全路徑類名
     return new AutoConfigurationEntry(configurations, exclusions);
    }
    
  • AutoConfigurationImportSelector#getCandidateConfigurations源碼如下:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
     List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
       getBeanClassLoader());
     Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
       + "are using a custom packaging, make sure that file is correct.");
     return configurations;
    }
    
  • 通過SpringFactoriesLoader#loadFactoryNames獲取應考慮的自動配置名稱,通過META-INF/spring.factories下的配置,例如:

    image-20210418175725765

    spring-boot-autoconfigure jar包下的 spring.factories 文件:

  • 執行完configurations = getConfigurationClassFilter().filter(configurations);之后,各種@Conditional不滿足就被過濾掉,剩下35個了

  • 可以通過如下方法進行驗證,結果沒有 RabbitAutoConfiguration 相關的Bean,拋出異常 NoSuchBeanDefinitionException

    @SpringBootApplication
    public class SpringDemosApplication implements ApplicationContextAware {
      private static ApplicationContext applicationContext;
    
      public static void main(String[] args) {
        SpringApplication.run(SpringDemosApplication.class, args);
        System.out.println(applicationContext.getBean(AopAutoConfiguration.class));
        System.out.println(applicationContext.getBean(RabbitAutoConfiguration.class));
      }
    
      @Override
      public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringDemosApplication.applicationContext = applicationContext;
      }
    }
    

main方法

例子

main方法里調用org.springframework.boot.SpringApplication#run(java.lang.Class<?>, java.lang.String...)方法

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

SpringApplication#run

調用另外一個同名的重載方法run

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

實例化SpringApplication對象

  1. 首先會實例化SpringApplication一個對象
  2. 構造方法里初始化一些屬性,比如webApplicationType,比如"SERVLET",初始化一些listeners
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
 return new SpringApplication(primarySources).run(args);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
 this.resourceLoader = resourceLoader;
 Assert.notNull(primarySources, "PrimarySources must not be null");
 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 初始化webApplicationType,比如"SERVLET"
 this.webApplicationType = WebApplicationType.deduceFromClasspath();
 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 初始化一些listeners
 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
 this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication#run(java.lang.String...)源碼解析

經典的觀察者模式,只要你把事件廣播的順序理解了,那整個流程就很容易串起來了:

  1. 創建一個StopWatch實例,用來記錄SpringBoot的啟動時間
  2. 通過SpringFactoriesLoader加載listeners:比如EventPublishingRunListener
  3. 發布SprintBoot開始啟動事件(EventPublishingRunListener#starting()
  4. 創建和配置environment(environmentPrepared()
  5. 打印SpringBoot的banner和版本
  6. 創建對應的ApplicationContext:Web類型,Reactive類型,普通的類型(非Web)
  7. prepareContext
    1. 准備ApplicationContext,Initializers設置到ApplicationContext(contextPrepared())
    2. 打印啟動日志,打印profile信息(如dev, test, prod)
    3. 最終會調用到AbstractApplicationContext#refresh方法,實際上就是Spring IOC容器的創建過程,並且會進行自動裝配的操作,以及發布ApplicationContext已經refresh事件,標志着ApplicationContext初始化完成(contextLoaded())
  8. afterRefresh hook方法
  9. stopWatch停止計時,日志打印總共啟動的時間
  10. 發布SpringBoot程序已啟動事件(started())
  11. 調用ApplicationRunner和CommandLineRunner
  12. 最后發布就緒事件ApplicationReadyEvent,標志着SpringBoot可以處理就收的請求了(running())
public ConfigurableApplicationContext run(String... args) {
    // 創建一個StopWatch實例,用來記錄SpringBoot的啟動時間
 StopWatch stopWatch = new StopWatch();
 stopWatch.start();
 ConfigurableApplicationContext context = null;
 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 configureHeadlessProperty();
    // 通過SpringFactoriesLoader加載listeners:比如EventPublishingRunListener
 SpringApplicationRunListeners listeners = getRunListeners(args);
    // 發布SprintBoot啟動事件:ApplicationStartingEvent
 listeners.starting();
 try {
  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
     // 創建和配置environment,發布事件:SpringApplicationRunListeners#environmentPrepared
  ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  configureIgnoreBeanInfo(environment);
     // 打印SpringBoot的banner和版本
  Banner printedBanner = printBanner(environment);
     // 創建對應的ApplicationContext:Web類型,Reactive類型,普通的類型(非Web)
  context = createApplicationContext();
  exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    new Class[] { ConfigurableApplicationContext.class }, context);
     // 准備ApplicationContext,Initializers設置到ApplicationContext后發布事件:ApplicationContextInitializedEvent
     // 打印啟動日志,打印profile信息(如dev, test, prod)
     // 調用EventPublishingRunListener發布ApplicationContext加載完畢事件:ApplicationPreparedEvent
  prepareContext(context, environment, listeners, applicationArguments, printedBanner);
     // 最終會調用到AbstractApplicationContext#refresh方法,實際上就是Spring IOC容器的創建過程,並且會進行自動裝配的操作
     // 以及發布ApplicationContext已經refresh事件,標志着ApplicationContext初始化完成
  refreshContext(context);
     // hook方法
  afterRefresh(context, applicationArguments);
     // stopWatch停止計時,日志打印總共啟動的時間
  stopWatch.stop();
  if (this.logStartupInfo) {
   new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  }
     // 發布SpringBoot程序已啟動事件ApplicationStartedEvent
  listeners.started(context);
     // 調用ApplicationRunner和CommandLineRunner
  callRunners(context, applicationArguments);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, listeners);
  throw new IllegalStateException(ex);
 }

 try {
     // 最后發布就緒事件ApplicationReadyEvent,標志着SpringBoot可以處理就收的請求了
  listeners.running(context);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, null);
  throw new IllegalStateException(ex);
 }
 return context;
}

SpringBoot啟動事件

  • SpringApplicationRunListeners的唯一實現是EventPublishingRunListener;

  • 整個SpringBoot的啟動,流程就是各種事件的發布,調用EventPublishingRunListener中的方法。

  • 只要明白了EventPublishingRunListener中事件發布的流程,也就明白了SpringBoot啟動的大體流程

EventPublishingRunListener

方法說明如下:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

 private final SpringApplication application;

 private final String[] args;

 private final SimpleApplicationEventMulticaster initialMulticaster;

 public EventPublishingRunListener(SpringApplication application, String[] args) {
  this.application = application;
  this.args = args;
  this.initialMulticaster = new SimpleApplicationEventMulticaster();
  for (ApplicationListener<?> listener : application.getListeners()) {
   this.initialMulticaster.addApplicationListener(listener);
  }
 }

 @Override
 public int getOrder() {
  return 0;
 }

    // SpringBoot啟動事件
 @Override
 public void starting() {
  this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
 }

    // 創建和配置環境
 @Override
 public void environmentPrepared(ConfigurableEnvironment environment) {
  this.initialMulticaster
    .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
 }

    // 准備ApplicationContext
 @Override
 public void contextPrepared(ConfigurableApplicationContext context) {
  this.initialMulticaster
    .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
 }

    // 發布ApplicationContext已經refresh事件,標志着ApplicationContext初始化完成
 @Override
 public void contextLoaded(ConfigurableApplicationContext context) {
  for (ApplicationListener<?> listener : this.application.getListeners()) {
   if (listener instanceof ApplicationContextAware) {
    ((ApplicationContextAware) listener).setApplicationContext(context);
   }
   context.addApplicationListener(listener);
  }
  this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
 }

    // SpringBoot已啟動事件
 @Override
 public void started(ConfigurableApplicationContext context) {
  context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
  AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
 }

    // "SpringBoot現在可以處理接受的請求"事件
 @Override
 public void running(ConfigurableApplicationContext context) {
  context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
  AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
 }

 @Override
 public void failed(ConfigurableApplicationContext context, Throwable exception) {
  ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
  if (context != null && context.isActive()) {
   // Listeners have been registered to the application context so we should
   // use it at this point if we can
   context.publishEvent(event);
  }
  else {
   // An inactive context may not have a multicaster so we use our multicaster to
   // call all of the context's listeners instead
   if (context instanceof AbstractApplicationContext) {
    for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
      .getApplicationListeners()) {
     this.initialMulticaster.addApplicationListener(listener);
    }
   }
   this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
   this.initialMulticaster.multicastEvent(event);
  }
 }

 private static class LoggingErrorHandler implements ErrorHandler {

  private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);

  @Override
  public void handleError(Throwable throwable) {
   logger.warn("Error calling ApplicationEventListener", throwable);
  }

 }

}

SpringIOC 容器初始化過程

由於現在大都是用SpringBoot開發,所以呢,Spring IOC 初始化的源碼,就是AnnotationConfigApplicationContext中的源碼,IOC的初始化就是該類實例創建的過程。

創建的過程(AnnotationConfigApplicationContext的構造方法),由於debug過這個源碼我個人把它分為兩大步(暫時我先寫出我的總結,后續看是否有時間能寫一篇關於debug的過程):

  1. 給我們的Bean,創建與之對應的BeanDefinition,然后把他們放入ConcurrentHashMap(key:beanName和value:beanDefinition)中;BeanDefinition實際上包括一些Bean的信息,比如BeanName, Scope, 是否被@Primary注解修飾,是否是@Lazy,以及@Description等注解
  2. refresh()方法: 創建IOC需要的資源
  • 初始化BeanFactory, set一些屬性,如BeanClassLoadersystemEnvironment
  • 如果是SpringBoot程序,會調用方法進行自動裝配:AutoConfigurationImportSelector.AutoConfigurationGroup#process,見:@EnableAutoConfiguration的總結
  • 注冊MessageSource,國際化相關的資源,到ApplicationContext
  • 注冊ApplicationListener到ApplicationContext
  • 實例化化lazy-init的Bean
  • 最后,publish相關的事件,ApplicationContext 就初始化完成,整個IOC容器初始化完成(IOC容器的本質就是初始化BeanFactory和ApplicationContext),就可以從IOC容器中獲取Bean自動注入了


免責聲明!

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



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