zoukankan      html  css  js  c++  java
  • Spring Boot 启动(一) SpringApplication 分析

    Spring Boot 启动(一) SpringApplication 分析

    Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

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

    本节重点分析 Spring Boot(2.1.3) 的 SpringApplication#run 方法是如何启动 Spring 容器。run 方法最终调用 new SpringApplication(primarySources).run(args)

    一、SpringApplication 初始化

    1.1 重要的属性说明

    // 1. 配置类,primarySources 是 run 方法传入的
    private Set<Class<?>> primarySources;
    private Set<String> sources = new LinkedHashSet<>();
    
    // 2. main 方法所在的启动类,日志输出用
    private Class<?> mainApplicationClass;
    
    // 3.environment 环境配置相关,addCommandLineProperties 添加 main 方法的命令行参数到 environment
    private boolean addCommandLineProperties = true;
    private boolean addConversionService = true;
    private Map<String, Object> defaultProperties;
    private Set<String> additionalProfiles = new HashSet<>();
    private boolean isCustomEnvironment = false;
    
    // 4. webmvc、webflux、非web 对应的 ApplicationContext 不同
    private Class<? extends ConfigurableApplicationContext> applicationContextClass;
    private WebApplicationType webApplicationType;
    
    // 5. 通过 spring.factories 配置
    private List<ApplicationContextInitializer<?>> initializers;
    private List<ApplicationListener<?>> listeners;
    

    1.2 SpringApplication 初始化

    public SpringApplication(Class<?>... primarySources) {
    	this(null, primarySources);
    }
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    	this.resourceLoader = resourceLoader;
    	Assert.notNull(primarySources, "PrimarySources must not be null");
    	// 1. primarySources 为配置类
    	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    	// 2. 根据加载的 jar 推断是 web、webflux、非web
    	this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	// 3. 加载 ApplicationContextInitializer 到 initializers 中。 spring.factories
    	setInitializers((Collection) getSpringFactoriesInstances(
    			ApplicationContextInitializer.class));
    	// 4. 加载监听器到 listeners 中。 spring.factories
    	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    	// 5. mainApplicationClass 启动类,即根据 new RuntimeException().getStackTrace() 
    	//    栈信息推断 main 方法所在的类,本例中即为 MyApplication,用于输出日志用
    	this.mainApplicationClass = deduceMainApplicationClass();
    }
    

    在构造方法中重点关注第三步和第四步,通过 Spring 的 SPI 技术(类似 JDK 的 ServiceLoader,在 Spring 中为 SpringFactoriesLoader)向容器中注入在 spring.factories 中配置的组件:

    • 第三步:注入 ApplicationContextInitializer 的配置类。Spring Boot 默认组装了 6 个实例,spring-boot-2.1.3.RELEASE.jar 下 4 个,spring-boot-autoconfigure-2.1.3.RELEASE.jar 下 2 个。

      0 = {DelegatingApplicationContextInitializer@1866} 
      1 = {SharedMetadataReaderFactoryContextInitializer@1867} 
      2 = {ContextIdApplicationContextInitializer@1868} 
      3 = {ConfigurationWarningsApplicationContextInitializer@1869} 
      4 = {ServerPortInfoApplicationContextInitializer@1870} 
      5 = {ConditionEvaluationReportLoggingListener@1871} 
      
    • 第四步:注入监听器,Spring 是基于事件驱动的,如配置文件的加载。Spring Boot 默认组装了 10 个实例,spring-boot-2.1.3.RELEASE.jar 下 9 个,spring-boot-autoconfigure-2.1.3.RELEASE.jar 下 1 个。

      0 = {ConfigFileApplicationListener@1771} 
      1 = {AnsiOutputApplicationListener@1772} 
      2 = {LoggingApplicationListener@1773} 
      3 = {ClasspathLoggingApplicationListener@1774} 
      4 = {BackgroundPreinitializer@1775} 
      5 = {DelegatingApplicationListener@1776} 
      6 = {ParentContextCloserApplicationListener@1777} 
      7 = {ClearCachesApplicationListener@1778} 
      8 = {FileEncodingApplicationListener@1779} 
      9 = {LiquibaseServiceLocatorApplicationListener@1780} 
      

    那 Spring 是如何保证这些组件的执行顺序的呢?在 getSpringFactoriesInstances 获取所有的实例后都会进行排序 AnnotationAwareOrderComparator.sort(instances)。 详见:https://www.cnblogs.com/binarylei/p/10426083.html

    二、run 方法主要流程分析

    run 方法主要是启动 ApplicationContext 容器,省略了一些非必须的代码。

    public ConfigurableApplicationContext run(String... args) {
    	ConfigurableApplicationContext context = null;
    	// 1. listeners 用户监听容器的运行,默认实现为 EventPublishingRunListener
    	SpringApplicationRunListeners listeners = getRunListeners(args);
    	listeners.starting();
    	try {
    		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    		// 2. 初始化环境变量 environment
    		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    		// 3. 仅仅实例化对应的 ApplicationContext,还没有任何配置
    		context = createApplicationContext();
    		// 4. 配置 context,为刷新容器做好准备
    		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    		// 5. AbstractApplicationContext#refresh
    		refreshContext(context);
    		afterRefresh(context, applicationArguments);
    		listeners.started(context);
    		callRunners(context, applicationArguments);
    	} catch (Throwable ex) {
    		throw new IllegalStateException(ex);
    	}
    
    	try {
    		listeners.running(context);
    	} catch (Throwable ex) {
    		throw new IllegalStateException(ex);
    	}
    	return context;
    }
    
    1. getRunListeners 初始化 SpringApplicationRunListeners,也是通过 spring.factories 配置 EventPublishingRunListener。SpringApplicationRunListeners 监听了容器启动、环境准备等事件。

    2. prepareEnvironment 初始化环境变量 environment,根据是否是 web 程序启动不同的 Environment 实现。同时将 ①配置的默认数据源 defaultProperties;②main 方法的参数;③application.properties 配置文件当作 Environment 的数据源。

    3. createApplicationContext 实例化 ApplicationContext

    4. prepareContext 配置 ApplicationContext

    5. refreshContext 刷新 ApplicationContext

    2.1 getRunListeners - 初始化 SpringApplicationRunListeners

    SpringApplicationRunListeners 默认实现为 EventPublishingRunListener。注意之所以不用 ApplicationContext 直接触发事件,是因为只有到第 4 步 contextLoaded 之后容器的初始化工作才完成,此时才能用 context.publishEvent() 触发相应的事件。

    public interface SpringApplicationRunListener {
    	// 1. 调用 run 方法后首先触发 starting 事件
    	void starting();
    	// 2. prepareEnvironment。初始化 environment 时调用,
    	void environmentPrepared(ConfigurableEnvironment environment);
    	// 3. prepareContext 开始时调用
    	void contextPrepared(ConfigurableApplicationContext context);
    	// 4. prepareContext 完成时调用
    	void contextLoaded(ConfigurableApplicationContext context);
    	// 5. refreshContext 后调用
    	void started(ConfigurableApplicationContext context);
    	// 6. started 后调用,run 方法调用完成
    	void running(ConfigurableApplicationContext context);
    	void failed(ConfigurableApplicationContext context, Throwable exception);
    }
    

    2.2 prepareEnvironment - 初始化环境变量 environment

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    		ApplicationArguments applicationArguments) {
    	// 1. 根据 webApplicationType 创建相应的 Environment
    	ConfigurableEnvironment environment = getOrCreateEnvironment();
    	// 2. 配置 Environment,主要有三点:一是 ConversionService;二是数据源,包括命令行参数;三是 Profiles
    	configureEnvironment(environment, applicationArguments.getSourceArgs());
    	// 3. 激活 environmentPrepared 事件,主要是加载 application.yml 等配置文件
    	//    ConfigFileApplicationListener#ApplicationEnvironmentPreparedEvent
    	listeners.environmentPrepared(environment);
    	bindToSpringApplication(environment);
    	if (!this.isCustomEnvironment) {
    		environment = new EnvironmentConverter(getClassLoader())
    				.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    	}
    	// ??? 以后再研究
    	ConfigurationPropertySources.attach(environment);
    	return environment;
    }
    

    加载后的数据源如下:

    0 = {SimpleCommandLinePropertySource@2697} "SimpleCommandLinePropertySource {name='commandLineArgs'}"
    1 = {PropertySource$StubPropertySource@2698} "StubPropertySource {name='servletConfigInitParams'}"
    2 = {PropertySource$StubPropertySource@2699} "StubPropertySource {name='servletContextInitParams'}"
    3 = {MapPropertySource@2700} "MapPropertySource {name='systemProperties'}"
    4 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor$OriginAwareSystemEnvironmentPropertySource@2701} "OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}"
    5 = {RandomValuePropertySource@2702} "RandomValuePropertySource {name='random'}"
    6 = {OriginTrackedMapPropertySource@2703} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}"
    

    执行加载的过程如下:

    1. servletConfigInitParams 和 servletContextInitParams 是 web 项目独有,systemEnvironment 和 systemEnvironment 这些都在初始化 environment 注入。
    2. commandLineArgs 是 main 方法命令行参数,在 configureEnvironment 注入。
    3. applicationConfig 是配置文件,在 environmentPrepared 时通过 ConfigFileApplicationListener#ApplicationEnvironmentPreparedEvent 事件注入。

    2.3 createApplicationContext - 实例化 ApplicationContext

    没什么可说的,略过

    2.4 prepareContext- 配置 ApplicationContext

    private void prepareContext(ConfigurableApplicationContext context,
    		ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
    		ApplicationArguments applicationArguments, Banner printedBanner) {
    	// 1. 为 context 注入基本的组件
    	context.setEnvironment(environment);
    	postProcessApplicationContext(context);
    	// 2. List<ApplicationContextInitializer<?>> initializers 在初始化的时候已经注入
    	applyInitializers(context);
    	// 3. 触发 contextPrepared 事件
    	listeners.contextPrepared(context);
    	// 4. 配置 beanFactory
    	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    	if (beanFactory instanceof DefaultListableBeanFactory) {
    		((DefaultListableBeanFactory) beanFactory)
    				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    	}
    	// 5. load 方法加载 BeanDefinition
    	Set<Object> sources = getAllSources();
    	Assert.notEmpty(sources, "Sources must not be empty");
    	load(context, sources.toArray(new Object[0]));
    	// 6. 触发 contextLoaded 事件,此时 context 准备工作已经完成
    	listeners.contextLoaded(context);
    }
    

    prepareContext 主要是配置 context 和 beanFactory。其中最重要的方法是 load(context, sources.toArray(new Object[0])) 方法,向容器中加载 BeanDefinition。sources 指的是 Spring 的配置类,默认为 this.primarySources 即 SpringApplication.run(Application.class, args) 中的配置类 Application。

    下面主要看一下 load 是如何加载 BeanDefinition 的。

    protected void load(ApplicationContext context, Object[] sources) {
    	BeanDefinitionLoader loader = createBeanDefinitionLoader(
    			getBeanDefinitionRegistry(context), sources);
    	if (this.beanNameGenerator != null) {
    		loader.setBeanNameGenerator(this.beanNameGenerator);
    	}
    	if (this.resourceLoader != null) {
    		loader.setResourceLoader(this.resourceLoader);
    	}
    	if (this.environment != null) {
    		loader.setEnvironment(this.environment);
    	}
    	loader.load();
    }
    
    protected BeanDefinitionLoader createBeanDefinitionLoader( 
    		BeanDefinitionRegistry registry, Object[] sources) {
    	return new BeanDefinitionLoader(registry, sources);
    }
    

    load 将加载 BeanDefinitionLoader 委托给了 BeanDefinitionLoader#load() 方法,其中 sources 即为配置类。

    2.5 refreshContext - 刷新 ApplicationContext

    没什么可说的,见 AbstractApplicationContext#refresh(https://www.cnblogs.com/binarylei/p/10423475.html)


    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    js动态添加CSS
    LINUX命令行回滚SVN版本
    LINUX文件名批量修改
    LINUX下文件编码转换 iconv
    ubuntu下eclipse的svn插件使用javahl
    HTML5学习笔记 本地数据库
    正则表达式
    我的第一篇博客
    【分布式锁的演化】分布式锁居然还能用MySQL?
    【分布式锁的演化】终章!手撸ZK分布式锁!
  • 原文地址:https://www.cnblogs.com/binarylei/p/10628596.html
Copyright © 2011-2022 走看看