天天看點

28.SpringBoot入口類原了解析

1.SpringBoot入口類

通過Spring Initializr建立項目,SpringBoot會自動生成如下所示的入口啟動類。

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

2.@SpringBootApplication原理

@SpringBootApplication開啟了Spring的元件掃描和SpringBoot自動配置功能。實際上,它是一個複合注解,包含3個重要的注解@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。在SpringBoot早期版本中,需要在入口類中同時添加這3個注解,但是從1.2.0版本之後,隻需要在入口類添加@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 {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "nameGenerator"
    )
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}      
  • @SpringBootConfiguration:表明該類使用基于Java的注解,SpringBoot推薦使用基于Java的注解而不是XML配置。檢視@SpringBootConfiguration的源代碼可知,其是對@Configuration進行的簡單包裝,然後取名為SpringBootConfiguration。
  • @EnableAutoConfiguration:開啟自動配置功能。檢視@EnableAutoConfiguration的源代碼可知,其包含@Import注解。@Import注解的主要作用是借助EnableAutoConfigurationImportSelector将SpringBoot應用所有符合條件的@Configuration配置加載到目前SpringBoot建立并使用的IOC容器中(Spring應用程式上下文ApplicationContext)。Spring架構提供了很多@Enable開頭的注解,這些注解都是借助@Import的支援,來收集和注冊特定場景相關的bean。
  • @ComponentScan:啟動元件掃描注解,開發的元件或bean定義能自動發現并注入到Spring應用程式上下文。如控制層注解@Controller、服務層注解@Service和@Component等,這些注解都可以被@ComponentScan注解掃描到。

3.SpringApplication的run方法

除了@SpringBootConfiguration注解,入口類中還有一個重要的内容是SpringApplication.run方法。在run方法中,首先建立一個SpringApplication的對象執行個體,然後調用SpringApplication的run方法,其源碼如下所示。

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
  this.configureHeadlessProperty();
  //開啟監聽器
  SpringApplicationRunListeners listeners = this.getRunListeners(args);
  listeners.starting();
  
  Collection exceptionReporters;
  try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
      this.configureIgnoreBeanInfo(environment);
      Banner printedBanner = this.printBanner(environment);
      //建立應用上下文
      context = this.createApplicationContext();
      exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
      //準備應用上下文
      this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      //重新整理應用上下文
      this.refreshContext(context);
      //重新整理後操作
      this.afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
          (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
      }
  
      listeners.started(context);
      this.callRunners(context, applicationArguments);
  } catch (Throwable var10) {
      this.handleRunFailure(context, var10, exceptionReporters, listeners);
      throw new IllegalStateException(var10);
  }
  
  try {
      listeners.running(context);
      return context;
  } catch (Throwable var9) {
      this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
      throw new IllegalStateException(var9);
  }
}      

從源代碼中可以看出,SpringBoot首先開啟了一個SpringApplicationRunListeners監聽器,然後通過createApplicationContext、prepareContext和refreshContext方法建立、準備和重新整理應用上下文ConfigurableApplicationContext,通過應用上下文加載應用所需要的類和各種配置環境等,最後啟動一個應用執行個體。

4.SpringApplicationRunListeners監聽器

SpringApplicationRunListeners接口規定了SpringBoot的生命周期,在各個生命周期廣播相應的ApplicationEvent事件,實際調用的是ApplicationListener,其源代碼如下所示。

public interface SpringApplicationRunListener {
    //執行run方法時觸發
    default void starting() {
    }

    //環境建立完成時觸發
    default void environmentPrepared(ConfigurableEnvironment environment) {
    }

    //上下文建立完成時觸發
    default void contextPrepared(ConfigurableApplicationContext context) {
    }

    //上下文載入配置時觸發
    default void contextLoaded(ConfigurableApplicationContext context) {
    }

    default void started(ConfigurableApplicationContext context) {
    }

    default void running(ConfigurableApplicationContext context) {
    }

    default void failed(ConfigurableApplicationContext context, Throwable exception) {
    }
}      

ApplicationListener是Spring架構對Java監聽器模式的一種架構實作,其源代碼如下所示。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}      

ApplicationListener接口隻有一個方法onApplicationEvent。如果在上下文中部署實作了一個ApplicationListener接口的監聽器,每當ApplicationEvent事件釋出到ApplicationContext時,該監聽器就會得到通知。如果要為SpringBoot應用添加自定義的ApplicationListener,可通過SpringApplication.addListeners()或者SpringApplication.setListeners()方法添加一個或者多個自定義的ApplicationListener。

5.ApplicationContextInitializer接口

在SpringBoot準備上下文prepareContext的時候,會對ConfigurableApplicationContext執行個體做進一步的設定或處理,prepareContext源碼如下所示。

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
  context.setEnvironment(environment);
  this.postProcessApplicationContext(context);
  //對上下文進行設定和處理
  this.applyInitializers(context);
  listeners.contextPrepared(context);
  if (this.logStartupInfo) {
      this.logStartupInfo(context.getParent() == null);
      this.logStartupProfileInfo(context);
  }
  
  ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
  if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
  }
  
  if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
  }
  
  if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
  }
  
  Set<Object> sources = this.getAllSources();
  Assert.notEmpty(sources, "Sources must not be empty");
  this.load(context, sources.toArray(new Object[0]));
  listeners.contextLoaded(context);
}      

在準備上下文prepareContext的方法中,通過applyInitializers方法對context上下文進行設定和處理,applyInitializers的源碼如下所示。

protected void applyInitializers(ConfigurableApplicationContext context) {
  for (ApplicationContextInitializer initializer : getInitializers()) {
    Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
        ApplicationContextInitializer.class);
    Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
    initializer.initialize(context);
  }
}      

在applyInitializers方法中,主要是調用ApplicationContextInitializer的initialize方法對應用上下文進行設定和處理。ApplicationContextInitializer本質上是一個回調接口,用于在ConfigurableApplicationContext執行refresh操作之前對它進行一些初始化操作。一般情況下,開發者無需自定義一個ApplicationContextInitializer,如果需要自定義一個ApplicationContextInitializer,則可以通過SpringApplication.addInitializers()來實作。

@FunctionalInterface
public interface ApplicationRunner {
  /**
   * Callback used to run the bean.
   * @param args incoming application arguments
   * @throws Exception on error
   */
  void run(ApplicationArguments args) throws Exception;
}      
@FunctionalInterface
public interface CommandLineRunner {
  /**
   * Callback used to run the bean.
   * @param args incoming main method arguments
   * @throws Exception on error
   */
  void run(String... args) throws Exception;
}      
public class MyCommandRunner implements CommandLineRunner {
  @Override
  public void run(String... args) throws Exception{
    //todo
  }
}