https://blog.csdn.net/dm_vincent/article/details/76735888

关于Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码(基于Spring-boot 1.5.6)的角度来看看Spring Boot的启动过程到底是怎么样的,为何以往纷繁复杂的配置到如今可以这么简便。

1. 入口类

  1. package com.example.demo;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class DemoApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(DemoApplication.class, args);
  8. }
  9. }

以上的代码就是通过Spring Initializr配置生成的一个最简单的Web项目(只引入了Web功能)的入口方法。这个想必只要是接触过Spring Boot都会很熟悉。简单的方法背后掩藏的是Spring Boot在启动过程中的复杂性,本文的目的就是一探这里面的究竟。

1.1 注解@SpringBootApplication

而在看这个方法的实现之前,需要看看@SpringBootApplication这个注解的功能:

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(
  8. excludeFilters = {@Filter(
  9. type = FilterType.CUSTOM,
  10. classes = {TypeExcludeFilter.class}
  11. ), @Filter(
  12. type = FilterType.CUSTOM,
  13. classes = {AutoConfigurationExcludeFilter.class}
  14. )}
  15. )
  16. public @interface SpringBootApplication {
  17. // ...
  18. }

很明显的,这个注解就是三个常用在一起的注解@SpringBootConfiguration,@EnableAutoConfiguration以及@ComponentScan的组合,并没有什么高深的地方。

1.1.1 @SpringBootConfiguration

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Configuration
  5. public @interface SpringBootConfiguration {
  6. }

这个注解实际上和@Configuration有相同的作用,配备了该注解的类就能够以JavaConfig的方式完成一些配置,可以不再使用XML配置。

1.1.2 @ComponentScan

顾名思义,这个注解完成的是自动扫描的功能,相当于Spring XML配置文件中的:

  1. <context:component-scan>

可以使用basePackages属性指定要扫描的包,以及扫描的条件。如果不设置的话默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,所以对于一个Spring Boot项目,一般会把入口类放在顶层目录中,这样就能够保证源码目录下的所有类都能够被扫描到。

1.1.3 @EnableAutoConfiguration

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import({EnableAutoConfigurationImportSelector.class})
  7. public @interface EnableAutoConfiguration {
  8. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  9. Class<?>[] exclude() default {};
  10. String[] excludeName() default {};
  11. }

这个注解是让Spring Boot的配置能够如此简化的关键性注解。目前知道这个注解的作用就可以了,关于自动配置不再本文讨论范围内,后面如果有机会另起文章专门分析这个自动配置的实现原理。

2. 入口方法

2.1 SpringApplication的实例化

介绍完了入口类,下面开始分析关键方法:

  1. SpringApplication.run(DemoApplication.class, args);

相应实现:

  1. // 参数对应的就是DemoApplication.class以及main方法中的args
  2. public static ConfigurableApplicationContext run(Class<?> primarySource,
  3. String... args) {
  4. return run(new Class<?>[] { primarySource }, args);
  5. }
  6. // 最终运行的这个重载方法
  7. public static ConfigurableApplicationContext run(Class<?>[] primarySources,
  8. String[] args) {
  9. return new SpringApplication(primarySources).run(args);
  10. }

它实际上会构造一个SpringApplication的实例,然后运行它的run方法:

  1. // 构造实例
  2. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  3. this.resourceLoader = resourceLoader;
  4. Assert.notNull(primarySources, "PrimarySources must not be null");
  5. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  6. this.webApplicationType = deduceWebApplicationType();
  7. setInitializers((Collection) getSpringFactoriesInstances(
  8. ApplicationContextInitializer.class));
  9. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  10. this.mainApplicationClass = deduceMainApplicationClass();
  11. }

在构造函数中,主要做了4件事情:

2.1.1 推断应用类型是Standard还是Web

  1. private WebApplicationType deduceWebApplicationType() {
  2. if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
  3. && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
  4. return WebApplicationType.REACTIVE;
  5. }
  6. for (String className : WEB_ENVIRONMENT_CLASSES) {
  7. if (!ClassUtils.isPresent(className, null)) {
  8. return WebApplicationType.NONE;
  9. }
  10. }
  11. return WebApplicationType.SERVLET;
  12. }
  13. // 相关常量
  14. private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
  15. + "web.reactive.DispatcherHandler";
  16. private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
  17. + "web.servlet.DispatcherServlet";
  18. private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
  19. "org.springframework.web.context.ConfigurableWebApplicationContext" };

可能会出现三种结果:

  1. WebApplicationType.REACTIVE - 当类路径中存在REACTIVE_WEB_ENVIRONMENT_CLASS并且不存在MVC_WEB_ENVIRONMENT_CLASS时
  2. WebApplicationType.NONE - 也就是非Web型应用(Standard型),此时类路径中不包含WEB_ENVIRONMENT_CLASSES中定义的任何一个类时
  3. WebApplicationType.SERVLET - 类路径中包含了WEB_ENVIRONMENT_CLASSES中定义的所有类型时

2.1.2 设置初始化器(Initializer)

  1. setInitializers((Collection) getSpringFactoriesInstances(
  2. ApplicationContextInitializer.class));

这里出现了一个新的概念 - 初始化器。

先来看看代码,再来尝试解释一下它是干嘛的:

  1. private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
  2. return getSpringFactoriesInstances(type, new Class<?>[] {});
  3. }
  4. // 这里的入参type就是ApplicationContextInitializer.class
  5. private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
  6. Class<?>[] parameterTypes, Object... args) {
  7. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  8. // 使用Set保存names来避免重复元素
  9. Set<String> names = new LinkedHashSet<>(
  10. SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  11. // 根据names来进行实例化
  12. List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
  13. classLoader, args, names);
  14. // 对实例进行排序
  15. AnnotationAwareOrderComparator.sort(instances);
  16. return instances;
  17. }

这里面首先会根据入参type读取所有的names(是一个String集合),然后根据这个集合来完成对应的实例化操作:

  1. // 入参就是ApplicationContextInitializer.class
  2. public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  3. String factoryClassName = factoryClass.getName();
  4. try {
  5. Enumeration<URL> urls = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
  6. ArrayList result = new ArrayList();
  7. while(urls.hasMoreElements()) {
  8. URL url = (URL)urls.nextElement();
  9. Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
  10. String factoryClassNames = properties.getProperty(factoryClassName);
  11. result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
  12. }
  13. return result;
  14. } catch (IOException var8) {
  15. throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
  16. }
  17. }

这个方法会尝试从类路径的META-INF/spring.factories处读取相应配置文件,然后进行遍历,读取配置文件中Key为:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure这个包为例,它的META-INF/spring.factories部分定义如下所示:

  1. # Initializers
  2. org.springframework.context.ApplicationContextInitializer=\
  3. org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
  4. org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

因此这两个类名会被读取出来,然后放入到集合中,准备开始下面的实例化操作:

  1. // 关键参数:
  2. // type: org.springframework.context.ApplicationContextInitializer.class
  3. // names: 上一步得到的names集合
  4. private <T> List<T> createSpringFactoriesInstances(Class<T> type,
  5. Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
  6. Set<String> names) {
  7. List<T> instances = new ArrayList<T>(names.size());
  8. for (String name : names) {
  9. try {
  10. Class<?> instanceClass = ClassUtils.forName(name, classLoader);
  11. Assert.isAssignable(type, instanceClass);
  12. Constructor<?> constructor = instanceClass
  13. .getDeclaredConstructor(parameterTypes);
  14. T instance = (T) BeanUtils.instantiateClass(constructor, args);
  15. instances.add(instance);
  16. }
  17. catch (Throwable ex) {
  18. throw new IllegalArgumentException(
  19. "Cannot instantiate " + type + " : " + name, ex);
  20. }
  21. }
  22. return instances;
  23. }

初始化步骤很直观,没什么好说的,类加载,确认被加载的类确实是org.springframework.context.ApplicationContextInitializer的子类,然后就是得到构造器进行初始化,最后放入到实例列表中。

因此,所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,这个接口是这样定义的:

  1. public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
  2. /**
  3. * Initialize the given application context.
  4. * @param applicationContext the application to configure
  5. */
  6. void initialize(C applicationContext);
  7. }

根据类文档,这个接口的主要功能是:

在Spring上下文被刷新之前进行初始化的操作。典型地比如在Web应用中,注册Property Sources或者是激活Profiles。Property Sources比较好理解,就是配置文件。Profiles是Spring为了在不同环境下(如DEV,TEST,PRODUCTION等),加载不同的配置项而抽象出来的一个实体。

2.1.3. 设置监听器(Listener)

设置完了初始化器,下面开始设置监听器:

  1. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

同样地,监听器也是一个新概念,还是从代码入手:

  1. // 这里的入参type是:org.springframework.context.ApplicationListener.class
  2. private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
  3. return getSpringFactoriesInstances(type, new Class<?>[] {});
  4. }
  5. private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
  6. Class<?>[] parameterTypes, Object... args) {
  7. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  8. // Use names and ensure unique to protect against duplicates
  9. Set<String> names = new LinkedHashSet<String>(
  10. SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  11. List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
  12. classLoader, args, names);
  13. AnnotationAwareOrderComparator.sort(instances);
  14. return instances;
  15. }

可以发现,这个加载相应的类名,然后完成实例化的过程和上面在设置初始化器时如出一辙,同样,还是以spring-boot-autoconfigure这个包中的spring.factories为例,看看相应的Key-Value:

  1. # Application Listeners
  2. org.springframework.context.ApplicationListener=\
  3. org.springframework.boot.autoconfigure.BackgroundPreinitializer

至于ApplicationListener接口,它是Spring框架中一个相当基础的接口了,代码如下:

  1. @FunctionalInterface
  2. public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
  3. /**
  4. * Handle an application event.
  5. * @param event the event to respond to
  6. */
  7. void onApplicationEvent(E event);
  8. }

这个接口基于JDK中的EventListener接口,实现了观察者模式。对于Spring框架的观察者模式实现,它限定感兴趣的事件类型需要是ApplicationEvent类型的子类,而这个类同样是继承自JDK中的EventObject类。

2.1.4. 推断应用入口类

  1. this.mainApplicationClass = deduceMainApplicationClass();
  • 1

这个方法的实现有点意思:

  1. private Class<?> deduceMainApplicationClass() {
  2. try {
  3. StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
  4. for (StackTraceElement stackTraceElement : stackTrace) {
  5. if ("main".equals(stackTraceElement.getMethodName())) {
  6. return Class.forName(stackTraceElement.getClassName());
  7. }
  8. }
  9. }
  10. catch (ClassNotFoundException ex) {
  11. // Swallow and continue
  12. }
  13. return null;
  14. }

它通过构造一个运行时异常,通过异常栈中方法名为main的栈帧来得到入口类的名字。


至此,对于SpringApplication实例的初始化过程就结束了。

2.2 SpringApplication.run方法

完成了实例化,下面开始调用run方法:

  1. // 运行run方法
  2. public ConfigurableApplicationContext run(String... args) {
  3. // 计时工具
  4. StopWatch stopWatch = new StopWatch();
  5. stopWatch.start();
  6. ConfigurableApplicationContext context = null;
  7. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  8. // 设置java.awt.headless系统属性为true - 没有图形化界面
  9. configureHeadlessProperty();
  10. // KEY 1 - 获取SpringApplicationRunListeners
  11. SpringApplicationRunListeners listeners = getRunListeners(args);
  12. // 发出开始执行的事件
  13. listeners.starting();
  14. try {
  15. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  16. args);
  17. // KEY 2 - 根据SpringApplicationRunListeners以及参数来准备环境
  18. ConfigurableEnvironment environment = prepareEnvironment(listeners,
  19. applicationArguments);
  20. configureIgnoreBeanInfo(environment);
  21. // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
  22. Banner printedBanner = printBanner(environment);
  23. // KEY 3 - 创建Spring上下文
  24. context = createApplicationContext();
  25. // 准备异常报告器
  26. exceptionReporters = getSpringFactoriesInstances(
  27. SpringBootExceptionReporter.class,
  28. new Class[] { ConfigurableApplicationContext.class }, context);
  29. // KEY 4 - Spring上下文前置处理
  30. prepareContext(context, environment, listeners, applicationArguments,
  31. printedBanner);
  32. // KEY 5 - Spring上下文刷新
  33. refreshContext(context);
  34. // KEY 6 - Spring上下文后置处理
  35. afterRefresh(context, applicationArguments);
  36. // 发出结束执行的事件
  37. listeners.finished(context, null);
  38. // 停止计时器
  39. stopWatch.stop();
  40. if (this.logStartupInfo) {
  41. new StartupInfoLogger(this.mainApplicationClass)
  42. .logStarted(getApplicationLog(), stopWatch);
  43. }
  44. return context;
  45. }
  46. catch (Throwable ex) {
  47. handleRunFailure(context, listeners, exceptionReporters, ex);
  48. throw new IllegalStateException(ex);
  49. }
  50. }

这个run方法包含的内容也是有点多的,根据上面列举出的关键步骤逐个进行分析:

2.2.1 第一步 - 获取所谓的run listeners:

  1. private SpringApplicationRunListeners getRunListeners(String[] args) {
  2. Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  3. return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
  4. SpringApplicationRunListener.class, types, this, args));
  5. }

这里仍然利用了getSpringFactoriesInstances方法来获取实例:

  1. // 这里的入参:
  2. // type: SpringApplicationRunListener.class
  3. // parameterTypes: new Class<?>[] { SpringApplication.class, String[].class };
  4. // args: SpringApplication实例本身 + main方法传入的args
  5. private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
  6. Class<?>[] parameterTypes, Object... args) {
  7. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  8. // Use names and ensure unique to protect against duplicates
  9. Set<String> names = new LinkedHashSet<String>(
  10. SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  11. List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
  12. classLoader, args, names);
  13. AnnotationAwareOrderComparator.sort(instances);
  14. return instances;
  15. }

所以这里还是故技重施,从META-INF/spring.factories中读取Key为org.springframework.boot.SpringApplicationRunListener的Values:

比如在spring-boot包中的定义的spring.factories:

  1. # Run Listeners
  2. org.springframework.boot.SpringApplicationRunListener=\
  3. org.springframework.boot.context.event.EventPublishingRunListener

我们来看看这个EventPublishingRunListener是干嘛的:

  1. /**
  2. * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
  3. * <p>
  4. * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
  5. * before the context is actually refreshed.
  6. *
  7. * @author Phillip Webb
  8. * @author Stephane Nicoll
  9. */
  10. public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
  11. // ...
  12. }

从类文档可以看出,它主要是负责发布SpringApplicationEvent事件的,它会利用一个内部的ApplicationEventMulticaster在上下文实际被刷新之前对事件进行处理。至于具体的应用场景,后面用到的时候再来分析。

2.2.2 第二步 - 根据SpringApplicationRunListeners以及参数来准备环境

  1. private ConfigurableEnvironment prepareEnvironment(
  2. SpringApplicationRunListeners listeners,
  3. ApplicationArguments applicationArguments) {
  4. // Create and configure the environment
  5. ConfigurableEnvironment environment = getOrCreateEnvironment();
  6. configureEnvironment(environment, applicationArguments.getSourceArgs());
  7. listeners.environmentPrepared(environment);
  8. if (!this.webEnvironment) {
  9. environment = new EnvironmentConverter(getClassLoader())
  10. .convertToStandardEnvironmentIfNecessary(environment);
  11. }
  12. return environment;
  13. }

配置环境的方法:

  1. protected void configureEnvironment(ConfigurableEnvironment environment,
  2. String[] args) {
  3. configurePropertySources(environment, args);
  4. configureProfiles(environment, args);
  5. }

所以这里实际上也包含了两个步骤:

  1. 配置Property Sources
  2. 配置Profiles

具体实现这里就不展开了,代码也比较直观。

对于Web应用而言,得到的environment变量是一个StandardServletEnvironment的实例。得到实例后,会调用前面RunListeners中的environmentPrepared方法:

  1. @Override
  2. public void environmentPrepared(ConfigurableEnvironment environment) {
  3. this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
  4. this.application, this.args, environment));
  5. }

在这里,定义的广播器就派上用场了,它会发布一个ApplicationEnvironmentPreparedEvent事件。

那么有发布就有监听,在构建SpringApplication实例的时候不是初始化过一些ApplicationListeners嘛,其中的Listener就可能会监听ApplicationEnvironmentPreparedEvent事件,然后进行相应处理。

所以这里SpringApplicationRunListeners的用途和目的也比较明显了,它实际上是一个事件中转器,它能够感知到Spring Boot启动过程中产生的事件,然后有选择性的将事件进行中转。为何是有选择性的,看看它的实现就知道了:

  1. @Override
  2. public void contextPrepared(ConfigurableApplicationContext context) {
  3. }

它的contextPrepared方法实现为空,没有利用内部的initialMulticaster进行事件的派发。因此即便是外部有ApplicationListener对这个事件有兴趣,也是没有办法监听到的。

那么既然有事件的转发,是谁在监听这些事件呢,在这个类的构造器中交待了:

  1. public EventPublishingRunListener(SpringApplication application, String[] args) {
  2. this.application = application;
  3. this.args = args;
  4. this.initialMulticaster = new SimpleApplicationEventMulticaster();
  5. for (ApplicationListener<?> listener : application.getListeners()) {
  6. this.initialMulticaster.addApplicationListener(listener);
  7. }
  8. }

前面在构建SpringApplication实例过程中设置的监听器在这里被逐个添加到了initialMulticaster对应的ApplicationListener列表中。所以当initialMulticaster调用multicastEvent方法时,这些Listeners中定义的相应方法就会被触发了。

2.2.3 第三步 - 创建Spring上下文

  1. protected ConfigurableApplicationContext createApplicationContext() {
  2. Class<?> contextClass = this.applicationContextClass;
  3. if (contextClass == null) {
  4. try {
  5. contextClass = Class.forName(this.webEnvironment
  6. ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
  7. }
  8. catch (ClassNotFoundException ex) {
  9. throw new IllegalStateException(
  10. "Unable create a default ApplicationContext, "
  11. + "please specify an ApplicationContextClass",
  12. ex);
  13. }
  14. }
  15. return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
  16. }
  17. // WEB应用的上下文类型
  18. public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

这个上下文类型的类图如下所示:

这也是相当复杂的一个类图了,如果能把这张图中的各个类型的作用弄清楚,估计也是一个Spring大神了 :)

对于我们的Web应用,上下文类型就是DEFAULT_WEB_CONTEXT_CLASS。

2.2.4 第四步 - Spring上下文前置处理

  1. private void prepareContext(ConfigurableApplicationContext context,
  2. ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
  3. ApplicationArguments applicationArguments, Banner printedBanner) {
  4. // 将环境和上下文关联起来
  5. context.setEnvironment(environment);
  6. // 为上下文配置Bean生成器以及资源加载器(如果它们非空)
  7. postProcessApplicationContext(context);
  8. // 调用初始化器
  9. applyInitializers(context);
  10. // 触发Spring Boot启动过程的contextPrepared事件
  11. listeners.contextPrepared(context);
  12. if (this.logStartupInfo) {
  13. logStartupInfo(context.getParent() == null);
  14. logStartupProfileInfo(context);
  15. }
  16. // 添加两个Spring Boot中的特殊单例Beans - springApplicationArguments以及springBootBanner
  17. context.getBeanFactory().registerSingleton("springApplicationArguments",
  18. applicationArguments);
  19. if (printedBanner != null) {
  20. context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
  21. }
  22. // 加载sources - 对于DemoApplication而言,这里的sources集合只包含了它一个class对象
  23. Set<Object> sources = getSources();
  24. Assert.notEmpty(sources, "Sources must not be empty");
  25. // 加载动作 - 构造BeanDefinitionLoader并完成Bean定义的加载
  26. load(context, sources.toArray(new Object[sources.size()]));
  27. // 触发Spring Boot启动过程的contextLoaded事件
  28. listeners.contextLoaded(context);
  29. }

关键步骤:

配置Bean生成器以及资源加载器(如果它们非空):

  1. protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
  2. if (this.beanNameGenerator != null) {
  3. context.getBeanFactory().registerSingleton(
  4. AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
  5. this.beanNameGenerator);
  6. }
  7. if (this.resourceLoader != null) {
  8. if (context instanceof GenericApplicationContext) {
  9. ((GenericApplicationContext) context)
  10. .setResourceLoader(this.resourceLoader);
  11. }
  12. if (context instanceof DefaultResourceLoader) {
  13. ((DefaultResourceLoader) context)
  14. .setClassLoader(this.resourceLoader.getClassLoader());
  15. }
  16. }
  17. }

调用初始化器

  1. protected void applyInitializers(ConfigurableApplicationContext context) {
  2. for (ApplicationContextInitializer initializer : getInitializers()) {
  3. Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
  4. initializer.getClass(), ApplicationContextInitializer.class);
  5. Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
  6. initializer.initialize(context);
  7. }
  8. }

这里终于用到了在创建SpringApplication实例时设置的初始化器了,依次对它们进行遍历,并调用initialize方法。

2.2.5 第五步 - Spring上下文刷新

  1. private void refreshContext(ConfigurableApplicationContext context) {
  2. // 由于这里需要调用父类一系列的refresh操作,涉及到了很多核心操作,因此耗时会比较长,本文不做具体展开
  3. refresh(context);
  4. // 注册一个关闭容器时的钩子函数
  5. if (this.registerShutdownHook) {
  6. try {
  7. context.registerShutdownHook();
  8. }
  9. catch (AccessControlException ex) {
  10. // Not allowed in some environments.
  11. }
  12. }
  13. }
  14. // 调用父类的refresh方法完成容器刷新的基础操作
  15. protected void refresh(ApplicationContext applicationContext) {
  16. Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
  17. ((AbstractApplicationContext)applicationContext).refresh();
  18. }

注册关闭容器时的钩子函数的默认实现是在AbstractApplicationContext类中:

  1. public void registerShutdownHook() {
  2. if(this.shutdownHook == null) {
  3. this.shutdownHook = new Thread() {
  4. public void run() {
  5. synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
  6. AbstractApplicationContext.this.doClose();
  7. }
  8. }
  9. };
  10. Runtime.getRuntime().addShutdownHook(this.shutdownHook);
  11. }
  12. }

如果没有提供自定义的shutdownHook,那么会生成一个默认的,并添加到Runtime中。默认行为就是调用它的doClose方法,完成一些容器销毁时的清理工作。

2.2.6 第六步 - Spring上下文后置处理

  1. protected void afterRefresh(ConfigurableApplicationContext context,
  2. ApplicationArguments args) {
  3. callRunners(context, args);
  4. }
  5. private void callRunners(ApplicationContext context, ApplicationArguments args) {
  6. List<Object> runners = new ArrayList<Object>();
  7. runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
  8. runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
  9. AnnotationAwareOrderComparator.sort(runners);
  10. for (Object runner : new LinkedHashSet<Object>(runners)) {
  11. if (runner instanceof ApplicationRunner) {
  12. callRunner((ApplicationRunner) runner, args);
  13. }
  14. if (runner instanceof CommandLineRunner) {
  15. callRunner((CommandLineRunner) runner, args);
  16. }
  17. }
  18. }
  19. private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
  20. try {
  21. (runner).run(args);
  22. }
  23. catch (Exception ex) {
  24. throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
  25. }
  26. }
  27. private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
  28. try {
  29. (runner).run(args.getSourceArgs());
  30. }
  31. catch (Exception ex) {
  32. throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
  33. }
  34. }

所谓的后置操作,就是在容器完成刷新后,依次调用注册的Runners。Runners可以是两个接口的实现类:

  1. org.springframework.boot.ApplicationRunner
  2. org.springframework.boot.CommandLineRunner

这两个接口有什么区别呢:

  1. /**
  2. * Interface used to indicate that a bean should <em>run</em> when it is contained within
  3. * a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
  4. * within the same application context and can be ordered using the {@link Ordered}
  5. * interface or {@link Order @Order} annotation.
  6. *
  7. * @author Phillip Webb
  8. * @since 1.3.0
  9. * @see CommandLineRunner
  10. */
  11. public interface ApplicationRunner {
  12. /**
  13. * Callback used to run the bean.
  14. * @param args incoming application arguments
  15. * @throws Exception on error
  16. */
  17. void run(ApplicationArguments args) throws Exception;
  18. }
  19. /**
  20. * Interface used to indicate that a bean should <em>run</em> when it is contained within
  21. * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
  22. * within the same application context and can be ordered using the {@link Ordered}
  23. * interface or {@link Order @Order} annotation.
  24. * <p>
  25. * If you need access to {@link ApplicationArguments} instead of the raw String array
  26. * consider using {@link ApplicationRunner}.
  27. *
  28. * @author Dave Syer
  29. * @see ApplicationRunner
  30. */
  31. public interface CommandLineRunner {
  32. /**
  33. * Callback used to run the bean.
  34. * @param args incoming main method arguments
  35. * @throws Exception on error
  36. */
  37. void run(String... args) throws Exception;
  38. }

其实没有什么不同之处,除了接口中的run方法接受的参数类型是不一样的以外。一个是封装好的ApplicationArguments类型,另一个是直接的String不定长数组类型。因此根据需要选择相应的接口实现即可。


至此,SpringApplication的run方法就分析完毕了。

3. 总结

本文分析了Spring Boot启动时的关键步骤,主要包含以下两个方面:

  1. SpringApplication实例的构建过程

    其中主要涉及到了初始化器(Initializer)以及监听器(Listener)这两大概念,它们都通过META-INF/spring.factories完成定义。

  2. SpringApplication实例run方法的执行过程

    其中主要有一个SpringApplicationRunListeners的概念,它作为Spring Boot容器初始化时各阶段事件的中转器,将事件派发给感兴趣的Listeners(在SpringApplication实例的构建过程中得到的)。这些阶段性事件将容器的初始化过程给构造起来,提供了比较强大的可扩展性。

如果从可扩展性的角度出发,应用开发者可以在Spring Boot容器的启动阶段,扩展哪些内容呢:

  1. 初始化器(Initializer)
  2. 监听器(Listener)
  3. 容器刷新后置Runners(ApplicationRunner或者CommandLineRunner接口的实现类)
  4. 启动期间在Console打印Banner的具体实现类

Spring Boot启动过程源码分析--转的更多相关文章

  1. [Spring Boot] Spring Boot启动过程源码分析

    关于Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码(基于Spring-boot 1.5.6)的角度来看看Spring Boot的启动过程到底是怎么样的,为何以往纷繁复杂的配置到 ...

  2. Spring启动过程源码分析基本概念

    Spring启动过程源码分析基本概念 本文是通过AnnotationConfigApplicationContext读取配置类来一步一步去了解Spring的启动过程. 在看源码之前,我们要知道某些类的 ...

  3. Spring加载流程源码分析03【refresh】

      前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...

  4. spring boot 2.0 源码分析(一)

    在学习spring boot 2.0源码之前,我们先利用spring initializr快速地创建一个基本的简单的示例: 1.先从创建示例中的main函数开始读起: package com.exam ...

  5. Spring Boot 自动配置 源码分析

    Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...

  6. Android系统默认Home应用程序(Launcher)的启动过程源码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

  7. Android Content Provider的启动过程源码分析

    本文參考Android应用程序组件Content Provider的启动过程源码分析http://blog.csdn.net/luoshengyang/article/details/6963418和 ...

  8. spring boot 2.0 源码分析(四)

    在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...

  9. Flume-NG启动过程源码分析(二)(原创)

    在上一节中讲解了——Flume-NG启动过程源码分析(一)(原创)  本节分析配置文件的解析,即PollingPropertiesFileConfigurationProvider.FileWatch ...

随机推荐

  1. 未知USB设备 端口重置失败

    1.开启手机中USB调试 进入“设置”->“应用程序”->“开发”勾选“USB调试程序”.这样设备才可以通过USB连线时被PC识别到. 2.安装驱动 要将Android手机连接到PC需要安 ...

  2. webstorm怎样查找历史记录

    在webstorm中 文件界面右键,local History --> show History 上面能看到具体的日期和编写的代码. 如果想回到某一次的代码.把中间的代码按>>移入到 ...

  3. Ubuntu16.04 + cuda9.0 + cudnn7.1.4 + tensorflow安装

    安装前的准备 UEFI 启动GPT分区 Win10和Ubuntu16.04双系统安装 ubuntu16.04 NVIDIA 驱动安装 ubuntu16.04 NVIDIA CUDA8.0 以及cuDN ...

  4. 20.java的7种控制语句

    1.java控制语句可以划分为七种 1)控制选择结构语句: if ,if else switch 2)控制循环结构语句: for ,while,do while 3)改变控制语句的顺序 break, ...

  5. bzoj2117

    动态电分治+二分 肯定要枚举所有点对,那么我们建出点分树降低树高,然后每个点存下点分树中所有子树到这个点的距离,然后二分+lower_bound就行了. #include<bits/stdc++ ...

  6. 机器学习之逻辑回归(logistic回归)

    前言            以下内容是个人学习之后的感悟,转载请注明出处~ 逻辑回归 一.为什么使用logistic回归   一般来说,回归不用在分类问题上,因为回归是连续型模型,而且受噪声影响比较大 ...

  7. 创建cube 维度层次

    http://blog.programmingsolution.net/ssas-2008/period-dimension-time-dimension-creation-with-year-mon ...

  8. 超级简单的跨平台高性能音视频播放框架QtAv编译指南

    目录 一.了解QtAv 二.相关文章 三.下载QtAv源码 四.下载QtAv依赖库 五.设置环境变量 1.gcc设置方式 2.msvc(cl)设置方式 六.编译 七.测试 一.了解QtAv 这几天抱着 ...

  9. 【Linux学习】Linux用户管理1—用户查询指令、用户切换

    Linux用户管理1-用户查询指令.用户切换 一.用户查询指令 who: 查看当前在线用户情况 -a:显示所有用户的所有信息 -m:显示运行该程序的用户名,和"who am I"的 ...

  10. Flutter实战视频-移动电商-48.详细页_详情和评论的切换

    48.详细页_详情和评论的切换 增加切换的效果,我们主要是修改这个地方 这样我们的评论的内容就显示出来了 最终代码 details_web.dart import 'package:flutter/m ...