springBoot啓動源碼解析(一)

前言:近期打算重擼幾行spring代碼,順手記記流水帳,基於

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
複製代碼

啓動一個springBoot程序(Servlet環境):java

@Slf4j
@EnableDiscoveryClient
@SpringBootApplication
public class KeplerPostLoanApplication {

	/** * 項目啓動類 * * @param args 啓動參數 */
	public static void main(String[] args) {
		SpringApplication.run(KeplerPostLoanApplication.class, args)
	}
}
複製代碼

調用SpringApplication.run方法web

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
		String[] args) {
	return new SpringApplication(primarySources).run(args);
}
複製代碼

先執行SpringApplication的構造方法,進行初始化動做,包括:spring

  • 一、識別webApplicationType類型 = Servlet
  • 二、識別mainApplicationClass類=KeplerPostLoanApplication.class
  • 三、加載META-INF/spring.factories文件中定義的定義的ApplicationContextInitializer、ApplicationListener
public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	
	// 加載META-INF/spring.factories文件中,定義的ApplicationContextInitializer
	setInitializers((Collection)getSpringFactoriesInstances(
			ApplicationContextInitializer.class));
			
	// 加載META-INF/spring.factories文件中,定義的ApplicationListener
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}
複製代碼

接下來看一下run作了什麼bootstrap

/** * 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<>();
	configureHeadlessProperty();
	
	// 獲取SpringApplicationRunListener
	// 默認只有EventPublishingRunListener,用來結合spring啓動流程,發佈SpringApplicationEvent
	SpringApplicationRunListeners listeners = getRunListeners(args);
	
	// 發佈ApplicationStartingEvent,例如
	// ApplicationPidFileWriter(saves application PID into file)
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
		//建立容器環境
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
				applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		
		// 建立容器上下文
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(
				SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		// 準備上下文
		prepareContext(context, environment, listeners, applicationArguments,
				printedBanner);
			
		// 刷新上下文 
		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 {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

複製代碼

概括下SpringApplication.run方法的關鍵動做設計模式

  • 第一步:構造容器環境(prepareEnvironment)
  • 第二步:建立容器(createApplicationContext)
  • 第三步:準備容器(prepareContext)
  • 第四步:刷新容器(refreshContext)
  • 第五步:刷新容器後的擴展接口(afterRefresh)
  • 第六步:回調Runner類(callRunners)

其中:執行過程當中,經過SpringApplicationRunListener的實現類EventPublishingRunListener在對應動做的時間點,Spring啓動事件。tomcat

附上,springApplicationEvent事件列表bash

主要以上6步,下面具體分析。

第一步:構造容器環境

/** SpringApplication.java **/

private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
	// 一、初始化environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	
	// 二、加載默認配置,defaultProperties、springApplicationCommandLineArgs
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	
	// 3.發佈ApplicationEnvironmentPreparedEvent,例如
	// (若是是spring Cloud環境的話)BootstrapApplicationListener,建立bootstrapContext上下文,加載bootstrap.properties,
	// 補充下,BootstrapApplicationListener會初始化Spring Cloud上下文,初始化過程當中,同樣會調用SpringApplication.run方法(即當前方法)
	// ConfigFileApplicationListener,加載application的YamlProperty文件與PropertiesProperty文件,添加PropertySourceOrderingPostProcessor
	// 補充下,有須要擴展,須要注意優先級,保證是否定期望的在bootstrap.properties、application.properties加載前或者加載後

	listeners.environmentPrepared(environment);
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader())
				.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}
複製代碼

首先getOrCreateEnvironment方法實現以下: new 一個StandardXXXEnvironment(),其中,構造函數中會執行customizePropertySources方法,加載基礎的的PropertySource(系統變量、環境變量、web變量)app

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	switch (this.webApplicationType) {
	case SERVLET:
		return new StandardServletEnvironment();
	case REACTIVE:
		return new StandardReactiveWebEnvironment();
	default:
		return new StandardEnvironment();
	}
}

public AbstractEnvironment() {
	customizePropertySources(this.propertySources);
}

複製代碼

附上StandardEnvironment的customizePropertySources實現less

@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    // 屬性獲取時,for each propertySources,先匹配到就返回,
    // 因此systemProperties(如:java -Dsource=xxx -jar xxx.jar)優先級高於systemEnvironment(環境變量)
	propertySources.addLast(new MapPropertySource("systemProperties", getSystemProperties()));
	propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment", getSystemEnvironment()));
}
複製代碼

第二步:建立容器

實際上,就是new AnnotationConfigServletWebServerApplicationContext()dom

// SpringApplication.java
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);
	
}
複製代碼

重點關注:構造方法中, 建立了AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner兩個對象

// AnnotationConfigServletWebServerApplicationContext.java
public AnnotationConfigServletWebServerApplicationContext() {
	this.reader = new AnnotatedBeanDefinitionReader(this);
	this.scanner = new ClassPathBeanDefinitionScanner(this);
}
複製代碼

其中,AnnotatedBeanDefinitionReader 建立過程當中,註冊了多個十分重要的BeanPostProcessor,包括處理@Configuration註解的ConfigurationClassPostProcessor等。

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
	this(registry, getOrCreateEnvironment(registry));
}

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	Assert.notNull(environment, "Environment must not be null");
	this.registry = registry;
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
	
	// Register all relevant annotation post processors in the given registry,包括:
	// ConfigurationClassPostProcessor
	// AutowiredAnnotationBeanPostProcessor
	// RequiredAnnotationBeanPostProcessor
	// CommonAnnotationBeanPostProcessor
	// PersistenceAnnotationBeanPostProcessor
	// EventListenerMethodProcessor
	// DefaultEventListenerFactory
	AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
複製代碼

第三步:準備容器

/** SpringApplication.java **/

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	context.setEnvironment(environment);
	
	postProcessApplicationContext(context);
	
	// 調用ApplicationContextInitializer.initialize(),例如:
	// EnvironmentDecryptApplicationInitializer
	// PropertySourceBootstrapConfiguration
	applyInitializers(context);
	
	// ApplicationContextInitializedEvent,暫無例子
	listeners.contextPrepared(context);
	
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	// Load the sources
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	
	// 加載springBoot啓動類注入到spring容器中bean map中
    // AnnotatedBeanDefinitionReader.doRegisterBean()
	load(context, sources.toArray(new Object[0]));
	
	// 發佈ApplicationPreparedEvent
	// ConfigFileApplicationListener,注入PropertySourceOrderingPostProcessor,調整defaultProperties到尾部
	listeners.contextLoaded(context);
}
複製代碼

第四步:刷新容器

refresh方法在spring整個源碼體系中舉足輕重,後續單獨講解。

private void refreshContext(ConfigurableApplicationContext context) {
	refresh(context);
	if (this.registerShutdownHook) {
		try {
			context.registerShutdownHook();
		}
		catch (AccessControlException ex) {
			// Not allowed in some environments.
		}
	}
}

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}

		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}
複製代碼

第五步:刷新容器後的擴展接口(afterRefresh)

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
複製代碼

擴展接口,設計模式中的模板方法,默認爲空實現,子類擴展。

第六步:回調Runner類(callRunners)

容器就緒後,觸發回調動做,目前見到的實現有 JobLauncherCommandLineRunner,啓動jobLauncher

private void callRunners(ApplicationContext context, ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
	AnnotationAwareOrderComparator.sort(runners);
	for (Object runner : new LinkedHashSet<>(runners)) {
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		if (runner instanceof CommandLineRunner) {
			callRunner((CommandLineRunner) runner, args);
		}
	}
}
複製代碼

本章節經常使用擴展接口以及使用案例

執行順序(重點關注):

  1. ApplicationStartingEvent
  2. ApplicationEnvironmentPreparedEvent
  3. ApplicationContextInitializer
  4. ApplicationContextInitializedEvent
  5. ApplicationPreparedEvent
  6. ApplicationStartedEvent
  7. ApplicationRunner、CommandLineRunner
  8. ApplicationReadyEvent

使用案例:

1. ApplicationStartingEvent

  • 簡單描述:程序啓動時,最先觸發的動做
  • 應用場景:
    • ApplicationPidFileWriter(saves application PID into file)
    • LoggingApplicationListener(configures the {@link LoggingSystem})

2. ApplicationEnvironmentPreparedEvent

  • 簡單描述: 建立環境Environment完成,而且已經載默認的配置,例如systemEnvironment、systemProperties
  • 應用場景:比較多的是加載自定義的配置、spring Cloud也經過這個事件,去初始化Cloud上下文。
    • BootstrapApplicationListener:初始化BootstrapApplicationContext上下文,包括加載bootstrap.properties、載入spring.factories 定義的BootstrapConfiguration
    • ConfigFileApplicationListener:加載application.properties、application.yml、RandomValuePropertySource

3. ApplicationContextInitializer

  • 描述:
  • 應用場景:例如對properties進行進一步的處理,好比解密
    • EnvironmentDecryptApplicationInitializer

4. ApplicationContextInitializedEvent

5. ApplicationPreparedEvent

6. ApplicationStartedEvent

  • 簡述:applicationStarted
  • 應用場景:好比能夠增長監控
    • TomcatMetricsBinder:暴露tomcat線程信息

7. ApplicationRunner、CommandLineRunner

  • 簡述:觸發回調動做。
  • 應用場景:
    • JobLauncherCommandLineRunner:springBatch,啓動JobLauncher

8. ApplicationReadyEvent

  • 簡述:標誌着容器啓動完成
  • 應用場景:
相關文章
相關標籤/搜索