SpringBoot学习笔记(三)-自动配置的实现机制与启动原理(源码分析)

Scroll Down

SpringBoot学习笔记(三)-自动配置的实现机制与启动原理(源码分析)

SpringBoot的依赖管理机制

为什么SpringBoot中导入的部分Dependency无需指定版本?

首先查看pom.xml文件,找到其中的parent依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

进入spring-boot-starter-parent查看,会发现还有一个parent已经一些属性配置,例如maven的默认编译配置

  <properties>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <resource.delimiter>@</resource.delimiter>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.target>${java.version}</maven.compiler.target>
  </properties>

加载配置文件的顺序

<resource>
        <filtering>true</filtering>
        <directory>${basedir}/src/main/resources</directory>
        <includes>
          <include>**/application*.yml</include>
          <include>**/application*.yaml</include>
          <include>**/application*.properties</include>
        </includes>
      </resource>

但是还是没有看到版本的信息,进入此文件的parent,spring-boot-dependencies,可以查看到常用的版本配置,springBoot都帮忙配置好了,避免我们自己配置的版本产生依赖冲突
image-20200602165852397

SpringBoot的自动配置如何实现?把哪些组件进行了自动配置?

springBoot会自动的为我们的一些组件配置相应的组件,我们无需配置或者少量配置就可以运行项目

从SpringBoot的启动入口(@SpringBootApplication注解标注类中的main()方法),@SpringBootApplication注解能够自动扫描Spring的组件并自动配置SpringBoot.

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

@SpringBootApplication注解源码:

@Target(ElementType.TYPE)    //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) ///表示注解的生命周期,Runtime运行时
@Documented ////表示注解可以记录在javadoc中
@Inherited   //表示可以被子类继承该注解

@SpringBootConfiguration //// 标明该类为配置类
@EnableAutoConfiguration  // 启动自动配置功能
@ComponentScan(excludeFilters = {   // 包扫描器 <context:component-scan base-package="com.xxx.xxx"/>
		@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 {};

}

@SpringBootApplication是一个组合注解,包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个核心注解

SpringBootConfiguration注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Configuration //配置类
public @interface SpringBootConfiguration {

}

@SpringBootConfiguration注解内部只有一个核心注解@Configuration,该注解是Spring框架原本就提供的,表示当前类为一个配置类,并可以被组件扫描器扫描,@SpringBootConfiguration功能与@Configuration完全一致,只是SpringBoot为了标识重新封装了一份

@EnableAutoConfiguration注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@AutoConfigurationPackage		//自动配置包 : 会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中
@Import(AutoConfigurationImportSelector.class)  //可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
	String[] excludeName() default {};
}

@EnableAutoConfiguration也是一个组合注解,SpringBoot中有很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的bean,并加载到IOC容器,@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到容器中

@AutoConfigurationPackage注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

//spring框架的底层注解,它的作用就是给容器中导入某个组件类,
//例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中
@Import(AutoConfigurationPackages.Registrar.class)  //  默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中
public @interface AutoConfigurationPackage {

}

这个注解的功能就是将Registrar类导入到容器中

 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }

registerBeanDefinitions方法中显示会注入一个该Application启动类所在的packgeName,这里会将packgeName下所有的Bean都扫描注入到容器当中,因此我们使用SpringBoot的时候,需要将Application类放在根目录下

image-20200602175712056

AutoConfigurationImportSelector.class 详解

进入AutoConfigurationImportSelector这个类,发现他是以selectImports方法告知SpringBoot需要哪些组件

	// 这个方法告诉springboot都需要导入那些组件
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		//判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		//1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
		//作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
		// SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
		// 自动配置的类全名.条件=值
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

进入AutoConfigurationMetadataLoader.loadMetadata方法

protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";//文件中为需要加载的配置类的类路径	
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		//重载方法
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			//1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
            // 获得 PATH 对应的 URL 们
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
            // 遍历 URL 数组,读取到 properties 中
            Properties properties = new Properties();

			//2.解析urls枚举对象中的信息封装成properties对象并加载
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			// 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象

			//根据封装好的properties对象生成AutoConfigurationMetadata对象返回
			return loadMetadata(properties);
		} catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}

将这些properties内容返回

image-20200602184001318

进入AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
	    // 1. 判断是否开启注解。如未开启,返回空串
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// 2. 获得注解的属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);

		// 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
		// spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
		// 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
		// 将这些值作为自动配置类导入到容器中,自动配置类就生效了
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);


		// 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
		configurations = removeDuplicates(configurations);
		// 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
		// 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
		//找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
		checkExcludedClasses(configurations, exclusions);
		// 4.2 从 configurations 中,移除所有不希望自动配置的配置类
		configurations.removeAll(exclusions);

		// 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类

		//@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
		//@ConditionalOnMissingClass : classpath中不存在该类时起效
		//@ConditionalOnBean : DI容器中存在该类型Bean时起效
		//@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
		//@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
		//@ConditionalOnExpression : SpEL表达式结果为true时
		//@ConditionalOnProperty : 参数设置或者值一致时起效
		//@ConditionalOnResource : 指定的文件存在时起效
		//@ConditionalOnJndi : 指定的JNDI存在时起效
		//@ConditionalOnJava : 指定的Java版本存在时起效
		//@ConditionalOnWebApplication : Web应用环境下起效
		//@ConditionalOnNotWebApplication : 非Web应用环境下起效

		//总结一下判断是否要加载某个类的两种方式:
		//根据spring-autoconfigure-metadata.properties进行判断。
		//要判断@Conditional是否满足
		// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
		configurations = filter(configurations, autoConfigurationMetadata);


		// 6. 将自动配置导入事件通知监听器
		//当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
		// 并触发fireAutoConfigurationImportEvents事件。
		fireAutoConfigurationImportEvents(configurations, exclusions);
		// 7. 创建 AutoConfigurationEntry 对象
		return new AutoConfigurationEntry(configurations, exclusions);
	}

进入getCandidateConfigurations()方法

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	    // 让SpringFactoryLoader去加载一些组件的名字
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		// 断言,非空
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

image-20200602191101240

最终会去加载一个外部文件,在META-INF/spring.factories的配置文件
image-20200602191221030

SpringBoot的自动配置流程:

  1. SpringBoot应用启动
  2. @SpringBootApplication起作用
  3. @EnableAutoConfiguration注解声明为配置类
  4. @AutoConfigurationPackage:组合注解主要是用于@Import(AutoConfigurationPackages.Registrar.class)扫描Application类所在目录下的文件,并将相应组件导入到SpringBoot容器中
  5. @Import(AutoConfigurationImportSelector.class):通过将AutoConfigurationImportSelector.class类导入到容器中,其中AutoConfigurationImportSelector.class类作用是通过selectImports方法执行过程中,使用内部工具类SpringFactoriesLoader,查找classpath上所有的jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程

@ComponentScan 注解

@ComponentScan(excludeFilters = {   // 包扫描器 <context:component-scan base-package="com.xxx.xxx"/>
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

用于扫描对应包下面相应的SpringBean,这里可用于过滤一些包

总结

|- @SpringBootConfiguration
	|- @Configuration //通过javaConfig的方式来添加组件到IOC容器中
|- @EnableAutoConfiguration
	|- @AutoConfigurationPackage //自动配置包,与@ComponentScan注解配合将对应的包添加到IOC容器中
	|- @Import(AutoConfigurationImportSelector.class) // 到META-INF/spring.factories中定义的bean添加到IOC容器中
|- @ComponentScan //包扫描

SpringBoot如何通过main()方法进行启动流程?

每个SpringBoot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中通过

SpringApplication.run(Class<?> primarySource, String... args)

进行启动,查看源码

	//调用静态类,参数对应的就是SpringbootDemoApplication.class以及main方法中的args
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

	/**
     * 运行 Spring 应用
     *
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified sources using default settings and user supplied arguments.
	 * @param primarySources the primary sources to load 加载的主类的数组
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	   //SpringApplication的启动由两部分组成:
		//1. 实例化SpringApplication对象
		//2. run(args):调用run方法
		return new SpringApplication(primarySources).run(args);
	}

如何实例化SpringApplication对象?

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

		this.sources = new LinkedHashSet();
		this.bannerMode = Mode.CONSOLE;
		this.logStartupInfo = true;
		this.addCommandLineProperties = true;
		this.addConversionService = true;
		this.headless = true;
		this.registerShutdownHook = true;
		this.additionalProfiles = new HashSet();
		this.isCustomEnvironment = false;
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");

		//项目启动类 SpringbootDemoApplication.class设置为属性存储起来
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

		//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
		this.webApplicationType = WebApplicationType.deduceFromClasspath();

		// 设置初始化器(Initializer),最后会调用这些初始化器
		//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

		// 设置监听器(Listener)
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

		// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

实例化SpringApplication对象总共大致分为四步

  • this.webApplicationType = WebApplicationType.deduceFromClasspath();判断当前的web应用类型,是Servlet应用还是REACTIVE应用
  • setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));设置初始化器,在初始化过程中,会使Spring类加载器SpringFacotriesLoader从META-INF/spring.factories文件中获取所有可用的应用初始化器类ApplicationContextInitializer,这里默认是有两个
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
  • setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));用于SpringApplication应用的监听设置,监听器也是从META-INF/spring.factories文件中获取所有可用的ApplicationListener类,这里默认有一个

    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
    
  • this.mainApplicationClass = this.deduceMainApplicationClass(),用于推断哪个类执行了main方法,并将对应的Class返回

    private Class<?> deduceMainApplicationClass() {
       try {
           // 获得当前 StackTraceElement 数组
          StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
          // 判断哪个执行了 main 方法
          for (StackTraceElement stackTraceElement : stackTrace) {
             if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
             }
          }
       } catch (ClassNotFoundException ex) {
          // Swallow and continue
       }
       return null;
    }
    

项目使用run方法启动

	public ConfigurableApplicationContext run(String... args) {
	    // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		// 初始化应用上下文和异常报告集合
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 配置 headless 属性
		configureHeadlessProperty();


		//   (1)获取并启动监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
		    // 创建  ApplicationArguments 对象 初始化默认应用参数类
			// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

			//(2)项目运行环境Environment的预配置
			// 创建并配置当前SpringBoot应用将要使用的Environment
			// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

			configureIgnoreBeanInfo(environment);
			// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
			Banner printedBanner = printBanner(environment);

			// (3)创建Spring容器
			context = createApplicationContext();
			// 获得异常报告器 SpringBootExceptionReporter 数组
			//这一步的逻辑和实例化初始化器和监听器的一样,
			// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);


			// (4)Spring容器前置处理
			//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);

			// (5):刷新容器
			refreshContext(context);

			// (6):Spring容器后置处理
			//扩展接口,设计模式中的模板方法,默认为空实现。
			// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
			afterRefresh(context, applicationArguments);
			// 停止 StopWatch 统计时长
			stopWatch.stop();
			// 打印 Spring Boot 启动的时长日志。
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			// (7)发出结束执行的事件通知
			listeners.started(context);

			// (8):执行Runners
			//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
			//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
			//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
			callRunners(context, applicationArguments);
		} catch (Throwable ex) {
		    // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

        //   (9)发布应用上下文就绪事件
		//表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
		// 这样整个Spring Boot项目就正式启动完成了。
		try {
			listeners.running(context);
		} catch (Throwable ex) {
            // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
            handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		 //返回容器
		return context;
	}

从上述源码中查看,大概启动步骤一共分为9步

  • SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting();第1步,获取获取并启动监听器,获取也是一样使用SpringFactoriesLoader.loadFactoryNames(type, classLoader)方法到META-INF/spring.factories 文件中获取SpringApplicationRunListener的实现类,这里默认只有一个
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
  • 第2步,根据SpringApplicationRunListeners以及参数来准备环境
/*
	加载外部化配置资源到environment,包括命令行参数、servletConfigInitParams、
	servletContextInitParams、systemProperties、sytemEnvironment、random、
	application.yml(.yaml/.xml/.properties)等;初始化日志系统。
	 */
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {

		//获取或创建环境(存在就直接返回,不存在创建一个再返回)
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//配置环境:配置PropertySources和active Profiles
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		//listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。
		listeners.environmentPrepared(environment);
		//将环境绑定到SpringApplication
		bindToSpringApplication(environment);
		//如果非web环境,将环境转换成StandardEnvironment
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		// 配置PropertySources对它自己的递归依赖
		// 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中。
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

 /**
     * @return 获得或创建 ConfigurableEnvironment 对象
     */
	private ConfigurableEnvironment getOrCreateEnvironment() {
	    // 已经存在,则进行返回
		if (this.environment != null) {
			return this.environment;
		}
		// 不存在,则根据 webApplicationType 类型,进行创建。
		switch (this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
		}
	}

	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
	    // 设置 environment 的 conversionService 属性
		if (this.addConversionService) {
			ConversionService conversionService = ApplicationConversionService.getSharedInstance();
			environment.setConversionService((ConfigurableConversionService) conversionService);
		}
		// 增加 environment 的 PropertySource 属性源
		configurePropertySources(environment, args);
		// 配置 environment 的 activeProfiles 属性
		configureProfiles(environment, args);
	}
  • context = createApplicationContext();第3步,创建Spring容器,根据上面环境准备获取到的webApplicationType进行判断,确定容器的类型,如果类型为SERVLET类型,会使用反射装在对应的类,也就是AnnotationConfigServletWebServerApplicationContext,接着使用初始化设置的context(上下文),environment(环境),listener(监听器),applicationArguments(项目参数)和printedBanner(项目图标信息打印)进行应用上下文的组装配置

image-20200602201320297

exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

还是一样的方法,通过从META-INF/spring.factories文件中获取SpringBootExceptionReporter.class类,这里默认只有一个

org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
  • 第4步,Spring容器前置处理
private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		//设置容器环境,包括各种变量
	    context.setEnvironment(environment);

		//设置上下文的 bean 生成器和资源加载器
		postProcessApplicationContext(context);

		//执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
		applyInitializers(context);

		//触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
		listeners.contextPrepared(context);

		//记录启动日志
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		//注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
		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");
		//加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础
		load(context, sources.toArray(new Object[0]));

		//触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
        listeners.contextLoaded(context);

		//这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等
	}

load(context, sources.toArray(new Object[0]));这里将我们的启动类也注入到了容器当中

  • 第5步,刷新容器,refreshContext(context);,这里最终会去调用spring中底层的刷新容器方法,org.springframework.context.support.AbstractApplicationContext#refresh,会将前面自动配置的那些bean都注入到容器当中
private void refreshContext(ConfigurableApplicationContext context) {
	    // 开启(刷新)Spring 容器,通过refresh方法对整个IoC容器的初始化(包括Bean资源的定位、解析、注册等等)
		refresh(context);
		// 注册 ShutdownHook 钩子
		if (this.registerShutdownHook) {
			try {
				//向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,除非它当时已经关闭
				context.registerShutdownHook();
			} catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}
  • afterRefresh(context, applicationArguments);第6步,Spring容器的后置处理,该方法默认没有实现,需要根据实际去定制化操作

    	protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
    		//该方法没有实现,可以根据需要做一些定制化的操作。
    	}
    
  • listeners.started(context);第7步,发出容器启动结束的通知,这里其实就是获取EventPublishingRunListener监听器,并执行其stated方法,并将创建的Spring容器传进去,创建了一个ApplicationStartedEvent事件,这里并执行context.publishEvent方法

	public void started(ConfigurableApplicationContext context) {
		//执行所有SpringApplicationRunListener实现的started方法。
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.started(context);
		}
	}
	@Override // ApplicationStartedEvent
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
	}
  • callRunners(context, applicationArguments);第8步,执行Runners,用于调用项目中自定义的XxxRunner类,使得项目启动完成后立即执行一些特定程序,SpringBoot提供的执行器接口有ApplicationRunner.classCommandLineRunner.class两种,只需要实现相应接口,并在接口的默认实现方法run()中写相应代码.SpringBoot启动后会自动执行该方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {
	    // 获得所有 Runner 们
		List<Object> runners = new ArrayList<>();
		// 获得所有 ApplicationRunner 实现类
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		// 获得所有 CommandLineRunner 实现类
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		// 排序 runners
		AnnotationAwareOrderComparator.sort(runners);
		// 遍历 Runner 数组,执行逻辑
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}
  • 第九步,listeners.running(context);在前面一切初始化启动都没有问题的情况下,执行所有监听器的running事件方法

	public void running(ConfigurableApplicationContext context) {
		//触发所有 SpringApplicationRunListener 监听器的 running 事件方法。
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.running(context);
		}
	}

总结

image-20200602204204440