springBoot2.1.5启动源码解析(一)

栏目: Java · 发布时间: 5年前

内容简介:调用SpringApplication.run方法先执行SpringApplication的构造方法,进行初始化动作,包括:接下来看一下run做了什么
@Slf4j
@EnableDiscoveryClient
@SpringBootApplication
public class KeplerPostLoanApplication {

	/**
	 * 项目启动类
	 *
	 * @param args 启动参数
	 */
	public static void main(String[] args) {
		SpringApplication.run(KeplerPostLoanApplication.class, args)
	}
}
复制代码

调用SpringApplication.run方法

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

先执行SpringApplication的构造方法,进行初始化动作,包括:

  • 1、识别webApplicationType类型
  • 2、识别mainApplicationClass类
  • 3、加载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做了什么

/**
 * 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,例如
	// BackgroundPreinitializer
	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,例如
		// TomcatMetricsBinder
		listeners.started(context);
		
		// 回调ApplicationRunner、CommandLineRunner,例如
		// JobLauncherCommandLineRunner,启动jobLauncher
		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启动事件。

附上,springApplicationEvent事件列表

springBoot2.1.5启动源码解析(一)

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

第一步:构造容器环境

/** SpringApplication.java **/

private ConfigurableEnvironment prepareEnvironment(
		SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// 1、初始化environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	
	// 2、加载默认配置,defaultProperties、springApplicationCommandLineArgs
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	
	// 3.发布ApplicationEnvironmentPreparedEvent,例如
	// BootstrapApplicationListener,创建bootstrapContext上下文,加载bootstrap.properties
	// ConfigFileApplicationListener,加载YamlProperty文件与PropertiesProperty文件,添加PropertySourceOrderingPostProcessor
	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。

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实现

@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()

// 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
	// SharedMetadataReaderFactoryContextInitializer,注入SharedMetadataReaderFactoryBean
	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

以上所述就是小编给大家介绍的《springBoot2.1.5启动源码解析(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

图解设计模式

图解设计模式

结城浩 / 杨文轩 / 人民邮电出版社 / 2017-1-1 / CNY 79.00

原版连续畅销12年、重印25次! 194张图表 + Java示例代码 = 轻松理解GoF的23种设计模式 《程序员的数学》《数学女孩》作者结城浩又一力作 ◆图文并茂 194张图表(包括57张UML类图)穿插文中,帮助理解各设计模式 ◆通俗易懂 用浅显的语言逐一讲解23种设计模式,读完此书会发现GoF书不再晦涩难懂 ◆专业实用 编写了Java程序代码来......一起来看看 《图解设计模式》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试