從SpringBoot啟動過程分析到自定義一個springboot-starter


一.應用啟動類

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

開發SpirngBoot應用時,入口類就這簡單的幾行。但是卻完成了N多服務的初始化、加載和發布。那么這幾行代碼究竟干了什么呢,SpringBoot應用到底是怎么啟動的。
本文中相關源碼來自Springboot2.3.3, spring不同版本之間的代碼可能有些許差別,但整體的過程是大同小異的

二.@SpringBootApplication注解

2.1.SpringBootApplication注解

@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 {

@SpringBootApplication=@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan,是這三個注解的復合注解

2.2.@SpringBootConfiguration

/**
 * Indicates that a class Spring Boot application
 * {@link Configuration @Configuration}. Can be used as an alternative to the Spring's
 * standard {@code @Configuration} annotation so that configuration can be found
 * automatically (for example in tests).
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}

SpringBootConfiguration注解和Spring的@Configuration注解作用一樣。標注當前類是配置類,并會將當前類內聲明的一個或多個以@Bean注解標記的方法的實例納入到spring容器中.

2.3.@ComponentScan

@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
/**
 * Configures component scanning directives for use with @{@link Configuration} classes.
 * Provides support parallel with Spring XML's {@code <context:component-scan>} element.
 *
 * <p>Either {@link #basePackageClasses} or {@link #basePackages} (or its alias
 * {@link #value}) may be specified to define specific packages to scan. If specific
 * packages are not defined, scanning will occur from the package of the
 * class that declares this annotation.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan{}

@ComponentScan掃描指定的包路徑,若未指定包路徑,則以聲明這個注解的類作為基本包路徑。比如@SpringBootApplication就沒有指定包路徑,則DemoApplication的包路徑將作為掃描的基本包路徑,因此強烈建議將主類放在頂層目錄下。

excludeFilters屬性指定哪些類型不符合組件掃描的條件,會在掃描的時候過濾掉。

@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)

比如上面這段代碼。@Filter聲明了過濾器類型類為自定義類型(需要實現TypeFilter接口),過濾器為AutoConfigurationExcludeFilter。當match方法為true,返回掃描類對象,否則過濾掉。但是要注意@ComponentScan的key為excludeFilters,因此ComponentScan在掃描時滿足過濾器條件(match返回true)的這些類型將在包掃描的時候過濾掉,是不會將該類加載到容器的。

@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
    throws IOException {
//如果是Springboot自動配置類,則不將其加載到Bean容器
    return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
}
//判斷是否是配置類
private boolean isConfiguration(MetadataReader metadataReader) {
    return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}
//判斷是否為EnableAutoConfiguration類
private boolean isAutoConfiguration(MetadataReader metadataReader) {
    return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}

2.3.@EnableAutoConfiguration

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

這個注解是SpringBoot能進行自動配置的關鍵

selectImports方法:根據導入Configuration類的AnnotationMetadata選擇并返回應導入的類的名稱

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        //返回自動配置類名數組
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
    //根據導入Configuration類的AnnotationMetadata返回AutoConfigurationEntry
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return EMPTY_ENTRY;
            }
            //返回@EnableAutoConfiguration 注解上的排除屬性
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            //通過SPI加載候選的自動配置類名
            List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
            //移除重復的類名
            configurations = removeDuplicates(configurations);
            //獲取注解上和 spring.autoconfigure.exclude 配置的排除類名
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            //檢查加載到的類名的合法性
            checkExcludedClasses(configurations, exclusions);
            //排除需要移除的類
            configurations.removeAll(exclusions);
            //通過元數據再次過濾
            configurations = getConfigurationClassFilter().filter(configurations);
            //監聽器發布自動配置導入事件并進行相應的處理
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }

@Import注解用于導入配置類,導入類AutoConfigurationImportSelector。
在 AbstractApplicationContext--->refresh()--->invokeBeanFactoryPostProcessors()中,會調用AutoConfigurationImportSelector類的selectImports方法,最終通過調SpringFactoriesLoader.loadFactoryNames方法,
掃描META-INF/spring.factories文件自動配置類(key為EnableAutoConfiguration),通過對其全類名的反射獲取到自動導入類的類元信息,并注冊到Bean工廠

三.從SpringApplication.run開始解析

3.1 Springboot的啟動流程主要分為三個部分:

  • SpringApplication的創建和初始化以及啟動之前的一些配置(啟動前)
  • SpringApplication的具體啟動過程(啟動過程)
  • SpringBoot的核心即自動配置模塊

3.2.SpringApplication的創建和初始化

3.2.1.構造器
    /**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified primary sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param resourceLoader the resource loader to use
     * @param primarySources the primary bean sources
     * @see #run(Class, String[])
     * @see #setSources(Set)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //1.根據應用是否存在某些類推斷應用類型,分為響應式web應用,servlet類型web應用和非web應用,在后面用于確定實例化applicationContext的類型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //2.設置初始化器,讀取spring.factories文件key ApplicationContextInitializer對應的value并實例化.ApplicationContextInitializer接口用于在Spring上下文被刷新之前進行初始化的操作
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //3.設置監聽器,讀取spring.factories文件key ApplicationListener對應的value并實例化
        //interface ApplicationListener<E extends ApplicationEvent> extends EventListener
        //ApplicationListener繼承EventListener,實現了觀察者模式。對于Spring框架的觀察者模式實現,它限定感興趣的事件類型需要是ApplicationEvent類型事件
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //4.配置應用入口類class對象
        this.mainApplicationClass = deduceMainApplicationClass();
    }

如上源碼,在構造器里主要干了2件事,一是設置初始化器,二是設置監聽器

3.2.2.設置初始化器ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
      ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
   return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   //從類路徑的META-INF處讀取相應配置文件spring.factories,然后進行遍歷,讀取配置文件中Key(type)對應的value
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   //通過反射將names的對象實例化
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

根據入參type類型ApplicationContextInitializer.class從類路徑的META-INF處讀取相應配置文件spring.factories并實例化對應Initializer。
SpringApplication啟動中獲取指定自動配置類型的實例時反復用到了上面這2個函數。

  • ApplicationContextInitializer是Spring框架原有的東西,這個類的主要作用就是在ConfigurableApplicationContext類型(或者子類型)的ApplicationContext做refresh之前,允許我們對ConfiurableApplicationContext的實例做進一步的設置和處理。關于Spring中具體的ApplicationContextInitializer介紹請移步這里:Spring中的ApplicationContextInitializer

    SpringBoot默認META-INF/spring.factories中的ApplicationContextInitializer配置如下:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
3.2.3.設置監聽器ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

和設置初始化器是相同的過程,通過getSpringFactoriesInstances函數實例化監聽器。

  • ApplicationListener使用了觀察者設計模式,主要作用是在springboot啟動過程的不同階段,通過監聽到發布的不同的事件從而去執行一些相應的操作。關于Spring中具體的ApplicationListener介紹請移步這里:Spring中的監聽器詳解與觀察者模式

    SpringBoot默認META-INF/spring.factories中的ApplicationListener配置如下:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

3.3.SpringApplication具體的啟動過程分析

3.3.1.啟動過程的核心 run方法

/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
   //計時器,記錄程序的運行時間
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   //設置java.awt.headless系統屬性為true,Headless模式是系統的一種配置模式。
   // 在該模式下,系統缺少了顯示設備、鍵盤或鼠標。但是服務器生成的數據需要提供給顯示設備等使用。
   // 因此使用headless模式,一般是在程序開始激活headless模式,告訴程序,現在你要工作在Headless mode下,依靠系統的計算能力模擬出這些特性來
   configureHeadlessProperty();
   //獲取監聽器集合對象
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //1.發出開始執行的事件。
   listeners.starting();
   try {
      //根據main函數傳入的參數,創建DefaultApplicationArguments對象
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //2.根據掃描到的監聽器對象和函數傳入參數,進行環境準備。
      ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
      //讀取spring.beaninfo.ignore,并加入到Spring內部的Bean信息緩存中
      configureIgnoreBeanInfo(environment);
      //通過Banner.Model和相關的配置打印Banner信息
      Banner printedBanner = printBanner(environment);
      //創建ApplicationContext,類型為ConfigurableApplicationContext
      context = createApplicationContext();
      //和上面套路一樣,讀取spring.factories文件key SpringBootExceptionReporter對應的Class,用于支持SpringbootApplication啟動過程中異常的回調
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
      //準備和加載運行環境
      prepareContext(context, environment, listeners, applicationArguments,printedBanner);
      //和上面的一樣,context準備完成之后,將觸發SpringApplicationRunListener的contextPrepared執行
      refreshContext(context);
      //其實啥也沒干。但是老版本的callRunners好像是在這里執行的。
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      //發布ApplicationStartedEvent事件,發出結束執行的事件
      listeners.started(context);
      //在某些情況下,我們希望在容器bean加載完成后執行一些操作,會實現ApplicationRunner或者CommandLineRunner接口
      //后置操作,就是在容器完成刷新后,依次調用注冊的Runners,多個Runner時可以通過@Order注解設置各runner的執行順序。
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }try {
      //發布ApplicationReadyEvent事件。上下文已刷新并且所有的CommandLineRunners和ApplicationRunner都已被調用,應用上下文創建完成
      listeners.running(context);
   }catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

SpringApplication核心的啟動運行方法如上所示,過程分析:

  • listeners.starting(): 發布ApplicationStartingEvent,運行過程開始
  • 創建并配置運行環境:
    • 程序運行的環境,主要包含了兩種信息,一種是profiles,用來描述哪些bean definitions是可用的;一種是properties,用來描述系統的配置,其來源可能是配置文件、JVM屬性文件、操作系統環境變量等等。
    • 發布ApplicationEnvironmentPreparedEvent環境準備就緒事件
        protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
            if (this.addConversionService) {
                ConversionService conversionService = ApplicationConversionService.getSharedInstance();
                environment.setConversionService((ConfigurableConversionService) conversionService);
            }
            configurePropertySources(environment, args);
            configureProfiles(environment, args);
        }
    
  • context = createApplicationContext():創建ApplicationContext
         protected ConfigurableApplicationContext createApplicationContext() {
             Class<?> contextClass = this.applicationContextClass;
             if (contextClass == null) {
                 try {
                     switch (this.webApplicationType) {
                     case SERVLET:
                         contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                         break;
                     case REACTIVE:
                         contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                         break;
                     default:
                         contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                     }
                 }
                 catch (ClassNotFoundException ex) {
                     throw new IllegalStateException(
                             "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
                 }
             }
             return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
         }
    
    這里通過this.webApplicationType判斷具體要創建哪種類型的ApplicationContext.

    比如web類型為servlet類型,就會實例化org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext類型的context。
    在這里BeanUtils.instantiateClass(contextClass)是通過instantiateClass(clazz.getDeclaredConstructor())進行初始化,也就是說這里是用來空參構造函數來進行實例化的。下面是其構造函數:
     /**
      * Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs
      * to be populated through {@link #register} calls and then manually
      * {@linkplain #refresh refreshed}.
      */
     public AnnotationConfigServletWebServerApplicationContext() {
         this.reader = new AnnotatedBeanDefinitionReader(this);
         this.scanner = new ClassPathBeanDefinitionScanner(this);
     }
    
    構造方法中初始化了兩個成員變量,類型分別為AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner用以加載使用注解的bean定義:
    // 調用父類AnnotationConfigApplicationContext的構造函數
    public AnnotationConfigApplicationContext() {
       // 實例化一個注解bena定義讀取器
       this.reader = new AnnotatedBeanDefinitionReader(this);
       this.scanner = new ClassPathBeanDefinitionScanner(this);
    }
    
    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
       Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
       Assert.notNull(environment, "Environment must not be null");
       // 維護一個ApplicationContext上下文索引
       this.registry = registry;
       // 聲明一個條件評估器,用來評估一個@Condition注解的類是否符合注入條件
       this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
       // 這里會事先向beanFacroty注入幾個核心后處理器
       AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
    
    在實例化上下文的時候,會向ApplicationContext.beanFacroty內部注冊幾個核心的后處理器:
    • internalConfigurationAnnotationProcessor:ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor: 用于@Configuration配置類的處理,包括postProcessBeanDefinitionRegistry,postProcessBeanFactory,processConfigBeanDefinitions三個主要方法
    • internalAutowiredAnnotationProcessor:AutowiredAnnotationBeanPostProcessor:處理@Autowired,@Injected注解字段,向其注入實際依賴的bean
    • internalCommonAnnotationProcessor:CommonAnnotationBeanPostProcessor: 處理@PostConstruct,@PreDestroy,@Resource等注解
    • internalPersistenceAnnotationProcessor:PersistenceAnnotationBeanPostProcessor:如果引入了JPA,這個類時處理JPA注解
    • internalEventListenerProcessor:EventListenerMethodProcessor:將EventListenner方法注冊為ApplicationListener
    • internalEventListenerFactory:DefaultEventListenerFactory:默認的EventListenerFactory實現,支持@EventListener注解
  • prepareContext(context, environment, listeners, applicationArguments,printedBanner):context前置處理階段
     private void prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {
         //關聯環境
         context.setEnvironment(environment);
         
         //ApplicationContext預處理,主要配置Bean生成器以及資源加載器
         postProcessApplicationContext(context);
          
         //調用初始化器,執行initialize方法,前面set的初始化器終于用上了
         applyInitializers(context);
         //發布contextPrepared事件
         listeners.contextPrepared(context);
         if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
         }
      
         // Add boot specific singleton beans
         ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
         //注冊單例Bean ApplicationArguments到ConfigurableListableBeanFactory,用于獲取啟動application所需的參數
         beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
          
         //加載打印Banner的Bean
         if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
         }
         
         if (beanFactory instanceof DefaultListableBeanFactory) {
         //設置是否允許重載Bean定義
            ((DefaultListableBeanFactory) beanFactory)
                  .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
         }
         // Load the sources,根據primarySources加載resource。primarySources:一般為主類的class對象
         Set<Object> sources = getAllSources();
         Assert.notEmpty(sources, "Sources must not be empty");
         //構造BeanDefinitionLoader并完成定義的Bean的加載
         load(context, sources.toArray(new Object[0]));
         //發布ApplicationPreparedEvent事件,表示application已準備完成
         listeners.contextLoaded(context);
     }
    
  • refreshContext: 調用父類AbstractApplicationContext刷新容器的操作.這里的refresh()方法就是Spring IOC容器加載的核心過程。Spring核心源碼分析請看Spring IOC 容器源碼分析

3.3.2.啟動過程中的監聽器的使用

SpringApplicationRunListeners listeners = getRunListeners(args);

和構造器設置初始化器一個套路,根據傳入type SpringApplicationRunListener去掃描spring.factories文件,讀取type對應的value并實例化。然后利用實例化對象創建SpringApplicationRunListeners對象。
查看spring.factories中的配置如下:

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

EventPublishingRunListener的作用是在SpringApplication加載的不同階段發布不同的SpringApplicationEvent。如下是其調用各個方法相應的階段,也對應了run方法運行過程中的多個階段:

    @Override
    //在run方法首次啟動時立即調用。可用于非常早期的初始化
    public void starting() {
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }

    @Override
    //在準備好環境之后,ApplicationContext創建之前調用
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster
                .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    }

    @Override
    //在ApplicationContext已經被創建和準備完畢之后,在加載資源前被調用
    public void contextPrepared(ConfigurableApplicationContext context) {
        this.initialMulticaster
                .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
    }

    @Override
    //ApplicationContext上下文被加載后,刷新之前
    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));
    }

    @Override
    //上下文已刷新,應用程序已啟動,但尚未調用CommandLineRunners和ApplicationRunners
    public void started(ConfigurableApplicationContext context) {
        context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
        AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
    }

    @Override
    //在上下文已刷新并且所有的CommandLineRunners和ApplicationRunner都已被調用,run方法完成之前
    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);
        }
    }

三、自動配置的奧秘---SpringBoot啟動過程中自動配置Bean如何注冊到BeanFactory

Spring-IOC容器源碼分析一文中,分析到SpringBoot基于注解的運行方式是在 refresh()--->invokeBeanFactoryPostProcessors()方法中進行了Bean定義的解析和收集,那么自動配置類導入的Bean或直接使用或間接的去構建成其他對象,必然也需要在這一階段進行Bean定義的注冊以便在之后的過程中進行實例化

承接 Spring-IOC容器源碼分析一文中 ConfigurationClassParser.java 265--->doProcessConfigurationClass方法對自動配置相關的源碼繼續分析:

@EnableAutoConfiguration注解中通過@Import(AutoConfigurationImportSelector.class)導入了自動配置類,在這里我們直接從處理@Import注解的
processImports(configClass, sourceClass, getImports(sourceClass), filter, true)開始分析

ConfigurationClassParser.java 552

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
            boolean checkForCircularImports) {

        if (importCandidates.isEmpty()) {
            return;
        }

        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                //對ImportSelector的處理
                for (SourceClass candidate : importCandidates) {
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                this.environment, this.resourceLoader, this.registry);
                        Predicate<String> selectorFilter = selector.getExclusionFilter();
                        if (selectorFilter != null) {
                            exclusionFilter = exclusionFilter.or(selectorFilter);
                        }
                        if (selector instanceof DeferredImportSelector) {
                            //如果是 DefferredImportSelector,則使用deferredImportSelectorHandler進行延遲處理
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                        }
                        else {
                            //根據ImportSelector方法的返回值來進行遞歸操作
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                            processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                        }
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                        this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        //如果當前的類既不是ImportSelector也不是ImportBeanDefinitionRegistar就進行@Configuration的解析處理
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }

可以看到 DeferredImportSelectorHandlerConfigurationClassParser一個專門用來處理延遲導入選擇器的內部類
關于SpringBoot中的其他的 ImportSelctor類的使用和分析 spring中的ImportSelector接口原理與使用

真正的對于延遲ImportSelector的處理則是在下面的process()方法:

ConfigurationClassParser.java 169

   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);
           }
       }
        
        //延遲導入選擇器的處理,SpringBoot自動配置類的加載處理的關鍵
        //因為有些自動配置類是有條件的,需要根據@Condition注解判斷是否已經有指定類再進行注入
        //所以在這里需要等到所有的配置類都處理完以后,最后處理這些 DeferredImportSelector類
       this.deferredImportSelectorHandler.process();
   }

ConfigurationClassParser.java 746

    private class DeferredImportSelectorHandler {

        @Nullable
        private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();

        /**
         * Handle the specified {@link DeferredImportSelector}. If deferred import
         * selectors are being collected, this registers this instance to the list. If
         * they are being processed, the {@link DeferredImportSelector} is also processed
         * immediately according to its {@link DeferredImportSelector.Group}.
         * //處理指定的 DeferredImportSelector。如果正在收集延遲導入選擇器,則會將此實例注冊到列表中。如果正在處理它們,將會根據DeferredImportSelector.Group組立即處理
         * @param configClass the source configuration class
         * @param importSelector the selector to handle
         */
        public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
            DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
            //根據私有變量deferredImportSelectors初始化值,如果直接執行該handle方法時,this.deferredImportSelectors == null條件比不成立
            if (this.deferredImportSelectors == null) {
                DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                handler.register(holder);
                handler.processGroupImports();
            }
            else {
                //將加入的importSelector封裝后添加到DeferredImportSelectorHolder集合
                this.deferredImportSelectors.add(holder);
            }
        }

        public void process() {
            List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
            this.deferredImportSelectors = null;
            try {
                if (deferredImports != null) {
                    //創建一個組處理器
                    DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                    //根據@Order注解進行排序
                    deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                    //循環注冊所有的 ImportSelector到相應的組中
                    deferredImports.forEach(handler::register);
                    //所有組分別處理相應的ImportSelector
                    //DeferredImportSelector會根據Group進行分組,即封裝成 DeferredImportSelectorGrouping 類,并且以組為單位對同一組中的ImportSelector進行統一處理
                    handler.processGroupImports();
                }
            }
            finally {
                this.deferredImportSelectors = new ArrayList<>();
            }
        }
    }

接著上面的process方法分析:

ConfigurationClassParser.java 795

        public void register(DeferredImportSelectorHolder deferredImport) {
                    // key:組類型(在這里 AutoConfigurationGroup) value:組
                    private final Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
                    // key:配置類的注解屬性 value:配置類信息(在這里是入口類即具有@SpringBootApplication類的信息)
                    private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
                    //注冊分組
                    public void register(DeferredImportSelectorHolder deferredImport) {
                        Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup(); // 這個方法有默認(default)實現,返回的是 null
            
                        /*
                        創建組
                        1. 其中 createGroup(group) 就是創建了上面的 group 對象,如果為空,則創建一個默認的組對象 DefaultDeferredImportSelectorGroup。
                        2. 這個方法的意思是,如果 map 中沒有這個元素則用后面的方法創建,如果有則直接取出來
                        */
                        DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
                                (group != null ? group : deferredImport),
                                key -> new DeferredImportSelectorGrouping(createGroup(group)));
                        grouping.add(deferredImport);//創建一個組,并加入DeferredImportSelectorHolder
                        this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
                                deferredImport.getConfigurationClass());//將注解屬性和ConfigurationClass映射
        }

ConfigurationClassParser.java 805

        public void processGroupImports() {
            //遍歷其中的ImportSelectorGroup進行處理
            for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
                Predicate<String> exclusionFilter = grouping.getCandidateFilter();
                //關鍵點  對于默認DefaultDeferredImportSelectorGroup組下的selector直接將其類信息封裝成Entry信息返回,對于AutoConfigurationGroup組下的在下面分析
                grouping.getImports().forEach(entry -> {
                    ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
                    try {
                        //遞歸調用處理 @Import的方法
                        processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
                                Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
                                exclusionFilter, false);
                    }
                    catch (BeanDefinitionStoreException ex) {
                        throw ex;
                    }
                    catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                "Failed to process import candidates for configuration class [" +
                                        configurationClass.getMetadata().getClassName() + "]", ex);
                    }
                });
            }
        }

ConfigurationClassParser.java 874

        public Iterable<Group.Entry> getImports() {
            //遍歷使用指定的DeferredImportSelector處理導入Configuration類的AnnotationMetadata
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            //返回該組中需要被導入的Entries
            return this.group.selectImports();
        }

在這里繼續看AutoConfigurationGroup類中對于上述 void process(AnnotationMetadata metadata, DeferredImportSelector selector)類的實現:

AutoConfigurationImportSelector.java 428

        @Override
        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()));
            //關鍵點  基于導入@Configuration類的AnnotationMetadata返回AutoConfigurationEntry,與最開始通過SPI獲取自動配置類信息的分析銜接
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }

DeferredImportSelector處理相關的主要有這幾個類:

  • DeferredImportSelectorHandler:持有一個List<DeferredImportSelectorHolder>類型的list,是對 DeferredImportSelector 類型的處理類
  • DeferredImportSelectorGroupingHandler:DeferredImportSelector的實際分組處理類,持有如下的兩個屬性,其 register 和 processGroupImports 方法處理 DeferredImportSelector 并填充這兩個屬性
         private class DeferredImportSelectorGroupingHandler {
     
             private final Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
     
             private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
             ...
  }
  • DeferredImportSelectorGrouping:持有一個DeferredImportSelector.Group組對象和DeferredImportSelectorHolder的List,存放該組中要處理的 DeferredImportSelector
     private static class DeferredImportSelectorGrouping {
 
         private final DeferredImportSelector.Group group;
 
         private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();
         ...
}
  • DeferredImportSelectorHolder:DeferredImportSelector的封裝,持有DeferredImportSelector實例及其對應的Configuration類元信息
     private static class DeferredImportSelectorHolder {
 
         private final ConfigurationClass configurationClass;
 
         private final DeferredImportSelector importSelector;
         ...
}

總體上的處理過程如下:
DeferredImportSelector處理結構流程.png

四.開發一個自己的Starter

與Starter相關的內容其實是Springboot自動配置的部分,下面將之前的使用Netty-websocket構建一個簡易的聊天室改造成一個Starter

3.1編寫一個Starter主要是這么幾步:

  1. 加入spring-boot-autoconfigure配置
  2. 編寫自動配置類
    /**
     * 自動裝配引導類
     *
     * @author duwenxu
     * @create 2021-01-21 18:29
     */
    @Configuration
    //僅當ChatServerStarter存在于類路徑上時才會實例化Bean
    @ConditionalOnClass(ChatServerStarter.class)
    public class NettyWsAutoConfiguration {
    
        @Bean
        //僅當該BeanFactory中不存在chatServerStarter類型的Bean時注入該Bean
        @ConditionalOnMissingBean
        public ChatServerStarter chatServerStarter(){
            return new ChatServerStarter();
        }
    }
    
  3. 在META-INF文件夾下添加spring.factories添加自動配置類
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.netty.websocket.autoconf.NettyWsAutoConfiguration
    
  4. maven構建jar包:
    mvn clean install
    

3.2.需要注意:

  • starter是具有一個工具包的性質,因次應該去掉主類以及pom中的mainClass配置,否則會出現不能夠注入bean的問題

3.3. 引用后的效果:

  • 加入依賴


    maven依賴信息.png
  • 日志打印websocket端口的綁定信息


    打印端口綁定信息.png
  • 打印新Channel連入信息


    打印新的channel連接信息.png
  • 可以實現websocket長連接推送功能


    在線測試.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容