1. 程式人生 > >Spring Boot: 啟動過程分析

Spring Boot: 啟動過程分析

建立Spring Boot專案

建立Spring Boot專案非常簡單,只需要以下幾步

設定當前專案的parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.6.RELEASE</version>
</parent>

新增外掛

   <plugin>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-maven-plugin</artifactId>
       <version>2.0.6.RELEASE</version>
       <executions>
           <execution>
               <goals>
                   <goal>repackage</goal>
               </goals>
           </execution>
       </executions>
   </plugin>

新增依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

最後建立一個類、並啟動它:

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

這樣一個Spring Boot專案就建立完成了。但是,它的啟動過程是怎樣的?接著往下看。

啟動過程

最簡單的啟動Spring Boot的方式就是這一行程式碼

SpringApplication.run(MainApp.class, args);

也可以使用SpringApplicationBuilder或者使用SpringApplication的構造方法來更靈活的應用Spring Boot,甚至可以對SpringApplication進行適當的擴充套件。

跟蹤程式碼發現,run方法內部是構造了一個SpringApplication例項然後再呼叫該例項的run方法。

構造SpringApplication例項

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	//為primarySources屬性賦值
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	//確定web app的型別,REACTIVE、NONE、SERVLET
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	//從spring.factories載入並例項化所有ApplicationContextInitializer的實現類
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	//從spring.factories載入並例項化所有ApplicationListener的實現類
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	//確定呼叫main方法的那個類,目前是MainApp類
	this.mainApplicationClass = deduceMainApplicationClass();
}

spring.factories檔案可以看到,對於ApplicationContextInitializer,配置了以下幾個實現

# /spring-boot-2.0.6.RELEASE.jar!/META-INF/spring.factories
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

#/spring-boot-autoconfigure-2.0.6.RELEASE.jar!/META-INF/spring.factories
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

對於ApplicationListener,配置了以下幾個實現

# /spring-boot-2.0.6.RELEASE.jar!/META-INF/spring.factories
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
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

# /spring-boot-autoconfigure-2.0.6.RELEASE.jar!/META-INF/spring.factories
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

run方法

看下run方法的實現

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	//SpringApplication的run方法的監聽器。從spring.factories載入並例項化所有SpringApplicationRunListener的實現類
	//目前為止spring.factories中配置的SpringApplicationRunListener的實現只有EventPublishingRunListener,並且也僅有這一個實現類
	SpringApplicationRunListeners listeners = getRunListeners(args);
	//釋出ApplicationStartingEvent事件
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		//建立environment並呼叫EnvironmentPostProcessor#postProcessEnvironment進行配置檔案讀取
		//釋出ApplicationEnvironmentPreparedEvent事件給ApplicationListener進行配置檔案讀取
		ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		//根據this.webApplicationType載入並例項化對應的ApplicationContext實現類,當前專案是AnnotationConfigApplicationContext
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
		//呼叫ApplicationContextInitializer#initialize
		//將MainApp例項註冊到beanFactory
		//釋出ApplicationPreparedEvent事件
		prepareContext(context, environment, listeners, applicationArguments,printedBanner);
		//ApplicationContext#refresh
		refreshContext(context);
		//空方法,子類可以進行擴充套件
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		//釋出ApplicationStartedEvent事件
		listeners.started(context);
		//呼叫ApplicationRunner、CommandLineRunner
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}
	try {
		//釋出ApplicationReadyEvent事件
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

SpringApplicationRunListener

Spring Boot要求,SpringApplicationRunListener的實現必須有一個接受SpringApplication application, String[] args兩個引數的構造器。 看EventPublishingRunListener的構造器

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);
	}
}

在這裡,構造SpringApplication時從spring.factories檔案載入的ApplicationListener都被新增到initialMulticaster屬性中去了。 類SimpleApplicationEventMulticasterrefresh時還會用到,它的作用就相當於是一系列ApplicationListener的代理,當有事件發生時,直接傳送給ApplicationEventMulticaster就行。

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

建立並初始化ConfigurableEnvironment例項。 建立ConfigurableEnvironment例項完成後會發佈一個ApplicationEnvironmentPreparedEvent事件來呼叫相應的ApplicationListenerEnvironment做進一步的處理。 其中有一個ConfigFileApplicationListener,以這個類為例。

@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationEnvironmentPreparedEvent) {
		onApplicationEnvironmentPreparedEvent(
				(ApplicationEnvironmentPreparedEvent) event);
	}
	if (event instanceof ApplicationPreparedEvent) {
		onApplicationPreparedEvent(event);
	}
}

看看處理ApplicationEnvironmentPreparedEvent事件的邏輯。

private void onApplicationEnvironmentPreparedEvent(
		ApplicationEnvironmentPreparedEvent event) {
	//從spring.factories檔案中載入並例項化EnvironmentPostProcessor的實現類
	//postProcessors = {[email protected]}  size = 3
    //0 = {[email protected]} 
    //1 = {[email protected]} 
    //2 = {[email protected]} 
	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
	//ConfigFileApplicationListener也是一個EnvironmentPostProcessor的實現類
	postProcessors.add(this);
	//排序
	AnnotationAwareOrderComparator.sort(postProcessors);
	//呼叫EnvironmentPostProcessor的postProcessEnvironment方法
	for (EnvironmentPostProcessor postProcessor : postProcessors) {
		postProcessor.postProcessEnvironment(event.getEnvironment(),
				event.getSpringApplication());
	}
}

具體怎樣載入配置檔案就在這個ConfigFileApplicationListenerpostProcessEnvironment方法了。 最終載入使用的是從spring.factories檔案載入的PropertySourceLoader的兩個例項進行的,分別用於處理.properties/.xml檔案和.yml檔案。

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

context = createApplicationContext();

這裡建立了ApplicationContext例項,其實是AnnotationConfigServletWebServerApplicationContext的例項。 AnnotationConfigServletWebServerApplicationContext的無參構造器及其父類GenericApplicationContext的無參構造器

public AnnotationConfigServletWebServerApplicationContext() {
	this.reader = new AnnotatedBeanDefinitionReader(this);
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}
public GenericApplicationContext() {
	this.beanFactory = new DefaultListableBeanFactory();
}

this.scanner = new ClassPathBeanDefinitionScanner(this);這行程式碼沒什麼好說的。關鍵是看 this.reader = new AnnotatedBeanDefinitionReader(this); 跟蹤這行程式碼,最終發現這裡註冊了以下一個bean到beanFactory例項。

//處理@Configuration、@Bean、@Service、@Component
org.springframework.context.annotation.internalConfigurationAnnotationProcessor:ConfigurationClassPostProcessor 
//處理依賴注入,@Autowired、@Value,支援@Inject
org.springframework.context.annotation.internalAutowiredAnnotationProcessor:AutowiredAnnotationBeanPostProcessor 
//處理@Required
org.springframework.context.annotation.internalRequiredAnnotationProcessor:RequiredAnnotationBeanPostProcessor 
//處理@PostConstruct、@PreDestroy、@Resource、@WebServiceRef、@EJB
org.springframework.context.annotation.internalCommonAnnotationProcessor:CommonAnnotationBeanPostProcessor
//處理@EventListener
org.springframework.context.event.internalEventListenerProcessor:EventListenerMethodProcessor 
org.springframework.context.event.internalEventListenerFactory:DefaultEventListenerFactory

其中,ConfigurationClassPostProcessorBeanFactoryPostProcessor的實現,同時也實現了BeanDefinitionRegistryPostProcessorAutowiredAnnotationBeanPostProcessorRequiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorBeanPostProcessor的實現。 後面refresh時會用到這幾個類。

prepareContext(context, environment, listeners, applicationArguments,printedBanner);

private void prepareContext(ConfigurableApplicationContext context,
		ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments, Banner printedBanner) {
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	//呼叫ApplicationContextInitializer#initialize
	applyInitializers(context);
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	// 註冊applicationArguments到beanFactory
	context.getBeanFactory().registerSingleton("springApplicationArguments",applicationArguments);
	if (printedBanner != null) {
		context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
	}
	// Load the sources
	// this.primarySources
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	//使用BeanDefinitionLoader將MainApp例項註冊到beanFactory
	load(context, sources.toArray(new Object[0]));
    //將SpringApplication中的ApplicationListener例項新增到ApplicationContext中,併發布ApplicationPreparedEvent事件
	listeners.contextLoaded(context);
}

目前為止事件的釋出都是使用的EventPublishingRunListener#initialMulticaster來進行的。 後面的事件釋出都是用AbstractApplicationContext#applicationEventMulticaster來進行。 實際上,這兩個屬性是SimpleApplicationEventMulticaster的不同例項。

refreshContext(context);

這裡實際上呼叫了ApplicationContextrefresh方法,進行bean的掃描、註冊、例項化以及應用容器(tomcat、netty、jetty等)的建立、啟動等工作,內容較多,留在下一篇文章裡詳細說明。

callRunners(context, applicationArguments);

這裡也是Spring Boot留下的一個擴充套件點,在啟動完成後執行一些自定義的任務。

總結

Spring Boot 的啟動過程如下:

  • 構造SpringApplication例項 - 確定應用型別 - 載入並例項化ApplicationContextInitializer,在context建立之後、refresh之前對context做一些處理 - 載入並例項化ApplicationListener,用於處理ApplicationEvent
  • 呼叫run方法 - 載入並例項化SpringApplicationRunListener對啟動過程進行監聽 - 建立ConfigurableEnvironment例項 - 建立ApplicationContext例項(同時也建立了BeanFactory例項) - 使用ApplicationContextInitializer對ApplicationContext做處理 - 將MainApp註冊到beanFactory - 呼叫ApplicationContext的refresh方法 - 呼叫ApplicationRunner、CommandLineRunner

Spring Boot的五個擴充套件點

  • SpringApplication
  • ApplicationContextInitializer
  • ApplicationListener
  • SpringApplicationRunListener
  • ApplicationRunner、CommandLineRunner