Spring Boot启动过程源码分析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

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

  1. 入口类
    package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

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

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

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

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// …
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
很明显的,这个注解就是三个常用在一起的注解@SpringBootConfiguration,@EnableAutoConfiguration以及@ComponentScan的组合,并没有什么高深的地方。

1.1.1 @SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
1
2
3
4
5
6
这个注解实际上和@Configuration有相同的作用,配备了该注解的类就能够以JavaConfig的方式完成一些配置,可以不再使用XML配置。

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

1 可以使用basePackages属性指定要扫描的包,以及扫描的条件。如果不设置的话默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,所以对于一个Spring Boot项目,一般会把入口类放在顶层目录中,这样就能够保证源码目录下的所有类都能够被扫描到。 1.1.3 @EnableAutoConfiguration @Target(\{ElementType.TYPE\}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(\{EnableAutoConfigurationImportSelector.class\}) public @interface EnableAutoConfiguration \{ String ENABLED\_OVERRIDE\_PROPERTY = “spring.boot.enableautoconfiguration”; Class\[\] exclude() default \{\}; String\[\] excludeName() default \{\}; \} 1 2 3 4 5 6 7 8 9 10 11 12 13 这个注解是让Spring Boot的配置能够如此简化的关键性注解。目前知道这个注解的作用就可以了,关于自动配置不再本文讨论范围内,后面如果有机会另起文章专门分析这个自动配置的实现原理。 2. 入口方法 2.1 SpringApplication的实例化 介绍完了入口类,下面开始分析关键方法: SpringApplication.run(DemoApplication.class, args); 1 相应实现: // 参数对应的就是DemoApplication.class以及main方法中的args public static ConfigurableApplicationContext run(Class primarySource, String… args) \{ return run(new Class\[\] \{ primarySource \}, args); \} // 最终运行的这个重载方法 public static ConfigurableApplicationContext run(Class\[\] primarySources, String\[\] args) \{ return new SpringApplication(primarySources).run(args); \} 1 2 3 4 5 6 7 8 9 10 11 它实际上会构造一个SpringApplication的实例,然后运行它的run方法: // 构造实例 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 = deduceWebApplicationType(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); \} 1 2 3 4 5 6 7 8 9 10 11 在构造函数中,主要做了4件事情: 2.1.1 推断应用类型是Standard还是Web private WebApplicationType deduceWebApplicationType() \{ if (ClassUtils.isPresent(REACTIVE\_WEB\_ENVIRONMENT\_CLASS, null) && !ClassUtils.isPresent(MVC\_WEB\_ENVIRONMENT\_CLASS, null)) \{ return WebApplicationType.REACTIVE; \} for (String className : WEB\_ENVIRONMENT\_CLASSES) \{ if (!ClassUtils.isPresent(className, null)) \{ return WebApplicationType.NONE; \} \} return WebApplicationType.SERVLET; \} // 相关常量 private static final String REACTIVE\_WEB\_ENVIRONMENT\_CLASS = “org.springframework.” \+ “web.reactive.DispatcherHandler”; private static final String MVC\_WEB\_ENVIRONMENT\_CLASS = “org.springframework.” \+ “web.servlet.DispatcherServlet”; private static final String\[\] WEB\_ENVIRONMENT\_CLASSES = \{ “javax.servlet.Servlet”, “org.springframework.web.context.ConfigurableWebApplicationContext” \}; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 可能会出现三种结果: WebApplicationType.REACTIVE – 当类路径中存在REACTIVE\_WEB\_ENVIRONMENT\_CLASS并且不存在MVC\_WEB\_ENVIRONMENT\_CLASS时 WebApplicationType.NONE – 也就是非Web型应用(Standard型),此时类路径中不包含WEB\_ENVIRONMENT\_CLASSES中定义的任何一个类时 WebApplicationType.SERVLET – 类路径中包含了WEB\_ENVIRONMENT\_CLASSES中定义的所有类型时 2.1.2 设置初始化器(Initializer) setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); 1 2 这里出现了一个新的概念 – 初始化器。 先来看看代码,再来尝试解释一下它是干嘛的: private Collection getSpringFactoriesInstances(Class type) \{ return getSpringFactoriesInstances(type, new Class\[\] \{\}); \} // 这里的入参type就是ApplicationContextInitializer.class private Collection getSpringFactoriesInstances(Class type, Class\[\] parameterTypes, Object… args) \{ ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 使用Set保存names来避免重复元素 Set names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 根据names来进行实例化 List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 对实例进行排序 AnnotationAwareOrderComparator.sort(instances); return instances; \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 这里面首先会根据入参type读取所有的names(是一个String集合),然后根据这个集合来完成对应的实例化操作: // 入参就是ApplicationContextInitializer.class public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) \{ String factoryClassName = factoryClass.getName(); try \{ Enumeration urls = classLoader != null?classLoader.getResources(“META-INF/spring.factories”):ClassLoader.getSystemResources(“META-INF/spring.factories”); ArrayList result = new ArrayList(); while(urls.hasMoreElements()) \{ URL url = (URL)urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); \} return result; \} catch (IOException var8) \{ throw new IllegalArgumentException(“Unable to load \[” + factoryClass.getName() + “\] factories from location \[” + “META-INF/spring.factories” + “\]”, var8); \} \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 这个方法会尝试从类路径的META-INF/spring.factories处读取相应配置文件,然后进行遍历,读取配置文件中Key为:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure这个包为例,它的META-INF/spring.factories部分定义如下所示: \# Initializers org.springframework.context.ApplicationContextInitializer=\\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer 1 2 3 4 因此这两个类名会被读取出来,然后放入到集合中,准备开始下面的实例化操作: // 关键参数: // type: org.springframework.context.ApplicationContextInitializer.class // names: 上一步得到的names集合 private List createSpringFactoriesInstances(Class type, Class\[\] parameterTypes, ClassLoader classLoader, Object\[\] args, Set names) \{ List instances = new ArrayList(names.size()); for (String name : names) \{ try \{ Class instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor constructor = instanceClass .getDeclaredConstructor(parameterTypes); T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); \} catch (Throwable ex) \{ throw new IllegalArgumentException( “Cannot instantiate ” + type + ” : ” + name, ex); \} \} return instances; \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 初始化步骤很直观,没什么好说的,类加载,确认被加载的类确实是org.springframework.context.ApplicationContextInitializer的子类,然后就是得到构造器进行初始化,最后放入到实例列表中。 因此,所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,这个接口是这样定义的: public interface ApplicationContextInitializer \{ /\*\* \* Initialize the given application context. \* @param applicationContext the application to configure \*/ void initialize(C applicationContext); \} 1 2 3 4 5 6 7 8 9 根据类文档,这个接口的主要功能是: 在Spring上下文被刷新之前进行初始化的操作。典型地比如在Web应用中,注册Property Sources或者是激活Profiles。Property Sources比较好理解,就是配置文件。Profiles是Spring为了在不同环境下(如DEV,TEST,PRODUCTION等),加载不同的配置项而抽象出来的一个实体。 2.1.3. 设置监听器(Listener) 设置完了初始化器,下面开始设置监听器: setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 1 同样地,监听器也是一个新概念,还是从代码入手: // 这里的入参type是:org.springframework.context.ApplicationListener.class private Collection getSpringFactoriesInstances(Class type) \{ return getSpringFactoriesInstances(type, new Class\[\] \{\}); \} private Collection getSpringFactoriesInstances(Class type, Class\[\] parameterTypes, Object… args) \{ ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set names = new LinkedHashSet( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 可以发现,这个加载相应的类名,然后完成实例化的过程和上面在设置初始化器时如出一辙,同样,还是以spring-boot-autoconfigure这个包中的spring.factories为例,看看相应的Key-Value: \# Application Listeners org.springframework.context.ApplicationListener=\\ org.springframework.boot.autoconfigure.BackgroundPreinitializer 1 2 3 至于ApplicationListener接口,它是Spring框架中一个相当基础的接口了,代码如下: @FunctionalInterface public interface ApplicationListener extends EventListener \{ /\*\* \* Handle an application event. \* @param event the event to respond to \*/ void onApplicationEvent(E event); \} 1 2 3 4 5 6 7 8 9 10 这个接口基于JDK中的EventListener接口,实现了观察者模式。对于Spring框架的观察者模式实现,它限定感兴趣的事件类型需要是ApplicationEvent类型的子类,而这个类同样是继承自JDK中的EventObject类。 2.1.4. 推断应用入口类 this.mainApplicationClass = deduceMainApplicationClass(); 1 这个方法的实现有点意思: private Class deduceMainApplicationClass() \{ try \{ StackTraceElement\[\] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) \{ if (“main”.equals(stackTraceElement.getMethodName())) \{ return Class.forName(stackTraceElement.getClassName()); \} \} \} catch (ClassNotFoundException ex) \{ // Swallow and continue \} return null; \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 它通过构造一个运行时异常,通过异常栈中方法名为main的栈帧来得到入口类的名字。 至此,对于SpringApplication实例的初始化过程就结束了。 2.2 SpringApplication.run方法 完成了实例化,下面开始调用run方法: // 运行run方法 public ConfigurableApplicationContext run(String… args) \{ // 计时工具 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection exceptionReporters = new ArrayList<>(); // 设置java.awt.headless系统属性为true – 没有图形化界面 configureHeadlessProperty(); // KEY 1 – 获取SpringApplicationRunListeners SpringApplicationRunListeners listeners = getRunListeners(args); // 发出开始执行的事件 listeners.starting(); try \{ ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // KEY 2 – 根据SpringApplicationRunListeners以及参数来准备环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); // 准备Banner打印器 – 就是启动Spring Boot的时候打印在console上的ASCII艺术字体 Banner printedBanner = printBanner(environment); // KEY 3 – 创建Spring上下文 context = createApplicationContext(); // 准备异常报告器 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class\[\] \{ ConfigurableApplicationContext.class \}, context); // KEY 4 – Spring上下文前置处理 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // KEY 5 – Spring上下文刷新 refreshContext(context); // KEY 6 – Spring上下文后置处理 afterRefresh(context, applicationArguments); // 发出结束执行的事件 listeners.finished(context, null); // 停止计时器 stopWatch.stop(); if (this.logStartupInfo) \{ new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); \} return context; \} catch (Throwable ex) \{ handleRunFailure(context, listeners, exceptionReporters, ex); throw new IllegalStateException(ex); \} \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 这个run方法包含的内容也是有点多的,根据上面列举出的关键步骤逐个进行分析: 2.2.1 第一步 – 获取所谓的run listeners: private SpringApplicationRunListeners getRunListeners(String\[\] args) \{ Class\[\] types = new Class\[\] \{ SpringApplication.class, String\[\].class \}; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); \} 1 2 3 4 5 这里仍然利用了getSpringFactoriesInstances方法来获取实例: // 这里的入参: // type: SpringApplicationRunListener.class // parameterTypes: new Class\[\] \{ SpringApplication.class, String\[\].class \}; // args: SpringApplication实例本身 + main方法传入的args private Collection getSpringFactoriesInstances(Class type, Class\[\] parameterTypes, Object… args) \{ ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set names = new LinkedHashSet( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 所以这里还是故技重施,从META-INF/spring.factories中读取Key为org.springframework.boot.SpringApplicationRunListener的Values: 比如在spring-boot包中的定义的spring.factories: \# Run Listeners org.springframework.boot.SpringApplicationRunListener=\\ org.springframework.boot.context.event.EventPublishingRunListener 1 2 3 我们来看看这个EventPublishingRunListener是干嘛的: /\*\* \* \{@link SpringApplicationRunListener\} to publish \{@link SpringApplicationEvent\}s. \*

\* Uses an internal \{@link ApplicationEventMulticaster\} for the events that are fired \* before the context is actually refreshed. \* \* @author Phillip Webb \* @author Stephane Nicoll \*/ public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered \{ // … \} 1 2 3 4 5 6 7 8 9 10 11 12 从类文档可以看出,它主要是负责发布SpringApplicationEvent事件的,它会利用一个内部的ApplicationEventMulticaster在上下文实际被刷新之前对事件进行处理。至于具体的应用场景,后面用到的时候再来分析。 2.2.2 第二步 – 根据SpringApplicationRunListeners以及参数来准备环境 private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) \{ // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); listeners.environmentPrepared(environment); if (!this.webEnvironment) \{ environment = new EnvironmentConverter(getClassLoader()) .convertToStandardEnvironmentIfNecessary(environment); \} return environment; \} 1 2 3 4 5 6 7 8 9 10 11 12 13 配置环境的方法: protected void configureEnvironment(ConfigurableEnvironment environment, String\[\] args) \{ configurePropertySources(environment, args); configureProfiles(environment, args); \} 1 2 3 4 5 所以这里实际上也包含了两个步骤: 配置Property Sources 配置Profiles 具体实现这里就不展开了,代码也比较直观。 对于Web应用而言,得到的environment变量是一个StandardServletEnvironment的实例。得到实例后,会调用前面RunListeners中的environmentPrepared方法: @Override public void environmentPrepared(ConfigurableEnvironment environment) \{ this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( this.application, this.args, environment)); \} 1 2 3 4 5 在这里,定义的广播器就派上用场了,它会发布一个ApplicationEnvironmentPreparedEvent事件。 那么有发布就有监听,在构建SpringApplication实例的时候不是初始化过一些ApplicationListeners嘛,其中的Listener就可能会监听ApplicationEnvironmentPreparedEvent事件,然后进行相应处理。 所以这里SpringApplicationRunListeners的用途和目的也比较明显了,它实际上是一个事件中转器,它能够感知到Spring Boot启动过程中产生的事件,然后有选择性的将事件进行中转。为何是有选择性的,看看它的实现就知道了: @Override public void contextPrepared(ConfigurableApplicationContext context) \{ \} 1 2 3 4 它的contextPrepared方法实现为空,没有利用内部的initialMulticaster进行事件的派发。因此即便是外部有ApplicationListener对这个事件有兴趣,也是没有办法监听到的。 那么既然有事件的转发,是谁在监听这些事件呢,在这个类的构造器中交待了: public EventPublishingRunListener(SpringApplication application, String\[\] args) \{ this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener listener : application.getListeners()) \{ this.initialMulticaster.addApplicationListener(listener); \} \} 1 2 3 4 5 6 7 8 前面在构建SpringApplication实例过程中设置的监听器在这里被逐个添加到了initialMulticaster对应的ApplicationListener列表中。所以当initialMulticaster调用multicastEvent方法时,这些Listeners中定义的相应方法就会被触发了。 2.2.3 第三步 – 创建Spring上下文 protected ConfigurableApplicationContext createApplicationContext() \{ Class contextClass = this.applicationContextClass; if (contextClass == null) \{ try \{ contextClass = Class.forName(this.webEnvironment ? DEFAULT\_WEB\_CONTEXT\_CLASS : DEFAULT\_CONTEXT\_CLASS); \} catch (ClassNotFoundException ex) \{ throw new IllegalStateException( “Unable create a default ApplicationContext, “ \+ “please specify an ApplicationContextClass”, ex); \} \} return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); \} // WEB应用的上下文类型 public static final String DEFAULT\_WEB\_CONTEXT\_CLASS = “org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext”; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 这个上下文类型的类图如下所示: 这也是相当复杂的一个类图了,如果能把这张图中的各个类型的作用弄清楚,估计也是一个Spring大神了 🙂 对于我们的Web应用,上下文类型就是DEFAULT\_WEB\_CONTEXT\_CLASS。 2.2.4 第四步 – Spring上下文前置处理 private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) \{ // 将环境和上下文关联起来 context.setEnvironment(environment); // 为上下文配置Bean生成器以及资源加载器(如果它们非空) postProcessApplicationContext(context); // 调用初始化器 applyInitializers(context); // 触发Spring Boot启动过程的contextPrepared事件 listeners.contextPrepared(context); if (this.logStartupInfo) \{ logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); \} // 添加两个Spring Boot中的特殊单例Beans – springApplicationArguments以及springBootBanner context.getBeanFactory().registerSingleton(“springApplicationArguments”, applicationArguments); if (printedBanner != null) \{ context.getBeanFactory().registerSingleton(“springBootBanner”, printedBanner); \} // 加载sources – 对于DemoApplication而言,这里的sources集合只包含了它一个class对象 Set sources = getSources(); Assert.notEmpty(sources, “Sources must not be empty”); // 加载动作 – 构造BeanDefinitionLoader并完成Bean定义的加载 load(context, sources.toArray(new Object\[sources.size()\])); // 触发Spring Boot启动过程的contextLoaded事件 listeners.contextLoaded(context); \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 关键步骤: 配置Bean生成器以及资源加载器(如果它们非空): protected void postProcessApplicationContext(ConfigurableApplicationContext context) \{ if (this.beanNameGenerator != null) \{ context.getBeanFactory().registerSingleton( AnnotationConfigUtils.CONFIGURATION\_BEAN\_NAME\_GENERATOR, this.beanNameGenerator); \} if (this.resourceLoader != null) \{ if (context instanceof GenericApplicationContext) \{ ((GenericApplicationContext) context) .setResourceLoader(this.resourceLoader); \} if (context instanceof DefaultResourceLoader) \{ ((DefaultResourceLoader) context) .setClassLoader(this.resourceLoader.getClassLoader()); \} \} \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 调用初始化器 protected void applyInitializers(ConfigurableApplicationContext context) \{ for (ApplicationContextInitializer initializer : getInitializers()) \{ Class requiredType = GenericTypeResolver.resolveTypeArgument( initializer.getClass(), ApplicationContextInitializer.class); Assert.isInstanceOf(requiredType, context, “Unable to call initializer.”); initializer.initialize(context); \} \} 1 2 3 4 5 6 7 8 这里终于用到了在创建SpringApplication实例时设置的初始化器了,依次对它们进行遍历,并调用initialize方法。 2.2.5 第五步 – Spring上下文刷新 private void refreshContext(ConfigurableApplicationContext context) \{ // 由于这里需要调用父类一系列的refresh操作,涉及到了很多核心操作,因此耗时会比较长,本文不做具体展开 refresh(context); // 注册一个关闭容器时的钩子函数 if (this.registerShutdownHook) \{ try \{ context.registerShutdownHook(); \} catch (AccessControlException ex) \{ // Not allowed in some environments. \} \} \} // 调用父类的refresh方法完成容器刷新的基础操作 protected void refresh(ApplicationContext applicationContext) \{ Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext)applicationContext).refresh(); \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 注册关闭容器时的钩子函数的默认实现是在AbstractApplicationContext类中: public void registerShutdownHook() \{ if(this.shutdownHook == null) \{ this.shutdownHook = new Thread() \{ public void run() \{ synchronized(AbstractApplicationContext.this.startupShutdownMonitor) \{ AbstractApplicationContext.this.doClose(); \} \} \}; Runtime.getRuntime().addShutdownHook(this.shutdownHook); \} \} 1 2 3 4 5 6 7 8 9 10 11 12 如果没有提供自定义的shutdownHook,那么会生成一个默认的,并添加到Runtime中。默认行为就是调用它的doClose方法,完成一些容器销毁时的清理工作。 2.2.6 第六步 – Spring上下文后置处理 protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) \{ callRunners(context, args); \} private void callRunners(ApplicationContext context, ApplicationArguments args) \{ List 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); \} \} \} private void callRunner(ApplicationRunner runner, ApplicationArguments args) \{ try \{ (runner).run(args); \} catch (Exception ex) \{ throw new IllegalStateException(“Failed to execute ApplicationRunner”, ex); \} \} private void callRunner(CommandLineRunner runner, ApplicationArguments args) \{ try \{ (runner).run(args.getSourceArgs()); \} catch (Exception ex) \{ throw new IllegalStateException(“Failed to execute CommandLineRunner”, ex); \} \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 所谓的后置操作,就是在容器完成刷新后,依次调用注册的Runners。Runners可以是两个接口的实现类: org.springframework.boot.ApplicationRunner org.springframework.boot.CommandLineRunner 这两个接口有什么区别呢: /\*\* \* Interface used to indicate that a bean should run when it is contained within \* a \{@link SpringApplication\}. Multiple \{@link ApplicationRunner\} beans can be defined \* within the same application context and can be ordered using the \{@link Ordered\} \* interface or \{@link Order @Order\} annotation. \* \* @author Phillip Webb \* @since 1.3.0 \* @see CommandLineRunner \*/ public interface ApplicationRunner \{ /\*\* \* Callback used to run the bean. \* @param args incoming application arguments \* @throws Exception on error \*/ void run(ApplicationArguments args) throws Exception; \} /\*\* \* Interface used to indicate that a bean should run when it is contained within \* a \{@link SpringApplication\}. Multiple \{@link CommandLineRunner\} beans can be defined \* within the same application context and can be ordered using the \{@link Ordered\} \* interface or \{@link Order @Order\} annotation. \*

\* If you need access to \{@link ApplicationArguments\} instead of the raw String array \* consider using \{@link ApplicationRunner\}. \* \* @author Dave Syer \* @see ApplicationRunner \*/ public interface CommandLineRunner \{ /\*\* \* Callback used to run the bean. \* @param args incoming main method arguments \* @throws Exception on error \*/ void run(String… args) throws Exception; \} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 其实没有什么不同之处,除了接口中的run方法接受的参数类型是不一样的以外。一个是封装好的ApplicationArguments类型,另一个是直接的String不定长数组类型。因此根据需要选择相应的接口实现即可。 至此,SpringApplication的run方法就分析完毕了。 3. 总结 本文分析了Spring Boot启动时的关键步骤,主要包含以下两个方面: SpringApplication实例的构建过程 其中主要涉及到了初始化器(Initializer)以及监听器(Listener)这两大概念,它们都通过META-INF/spring.factories完成定义。 SpringApplication实例run方法的执行过程 其中主要有一个SpringApplicationRunListeners的概念,它作为Spring Boot容器初始化时各阶段事件的中转器,将事件派发给感兴趣的Listeners(在SpringApplication实例的构建过程中得到的)。这些阶段性事件将容器的初始化过程给构造起来,提供了比较强大的可扩展性。 如果从可扩展性的角度出发,应用开发者可以在Spring Boot容器的启动阶段,扩展哪些内容呢: 初始化器(Initializer) 监听器(Listener) 容器刷新后置Runners(ApplicationRunner或者CommandLineRunner接口的实现类) 启动期间在Console打印Banner的具体实现类 因此在下一篇文章中,将介绍如何对Spring Boot容器的启动过程进行扩展,实现对该过程的定制化。 ——————— 作者:dm\_vincent 来源:CSDN 原文:https://blog.csdn.net/dm\_vincent/article/details/76735888 版权声明:本文为博主原创文章,转载请附上博文链接! ------- 来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Boot启动过程源码分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

© 2014 - 2019 Java 技术驿站   网站地图  | 

icp 湘ICP备14000180

>>> 网站已平稳运行:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏