Spring Boot主启动类的main方法中调用SpringApplication的静态方法run后,项目直接启动了,这一篇我们就来分析下具体的启动流程。IOC是Spring的两个核心特性之一,Spring Boot项目启动过程中必然会围绕IOC容器的初始化做一些动作。另外,Spring Boot 2.x基于Spring 5.x,Spring Boot 1.x基于Spring 4.x,下面的分析都是基于很多项目常用的Spring Boot 2.x。先来看一下Spring IOC的一些基础API。
1. IOC相关API
1.1 BeanFactory
用IDEA生成BeanFactory接口和相关重要API的类图:
BeanFactory是最基本的接口,它的重要子接口有ListableBeanFactory、HierarchicalBeanFactory、AutowireCapableBeanFactory等。ListableBeanFactory具有可列举性,可以把容器里的bean列举出来;HierarchicalBeanFactory使其派生接口具有父子结构。ConfigurableBeanFactory继承了它,因而具有获得了层次性;AutowireCapableBeanFactory支持自动装配,用来注入Spring无法控制其生命周期的bean。
AbstractBeanFactory是BeanFactory最基础的抽象类,通过继承HierarchicalBeanFactory管理BeanFactory的层次性,通过继承AliasRegistry等具有管理bean对象名称的能力,createBean抽象方法, getBean和doGetBean等重要方法都在这里。
AbstractAutowireCapableBeanFactory抽象类,实现了AutowireCapableBeanFactory,具有组件自动装配能力;继承了AbstractBeanFactory,实现了createBean方法,bean对象的真正创建动作、属性赋值和依赖注入,Bean的初始化都在这里。
DefaultListableBeanFactory是BeanFactory的成熟的落地实现类,通过继承ConfigurableListableBeanFactory获得可列举性,通过继承AbstractAutowireCapableBeanFactory获取自动装配和bean对象生命周期管理能力, 通过实现BeanDefinitionRegistry接口获得注册bean定义的能力。
1.2 ApplicationContext
用IDEA生成ApplicationContext接口的父接口类图:
类图显示ApplicationContext接口具有加载文件资源、事件发布、国际化支持、容器层级关系支持和可列举性的能力。
用IDEA生成ApplicationContext接口的父接口类图:
ConfigurableApplicationContext接口继承ApplicationContext,同时加了一些配置上下文的方法。AbstractApplicationContext抽象类,定义和实现了绝大部分应用上下文的功能。AbstractApplicationContext有2个重要子类GenericApplicationContext和AbstractRefreshableApplicationContext。Spring IOC基于XML配置和注解驱动2种方式,Spring Boot已放弃基于XML的方式。基于XML的基于XML的IOC容器可重复刷新,基于注解的不可以。AbstractRefreshableApplicationContext代表XML方式,GenericApplicationContext代表注解方式。所以我们主要分析GenericApplicationContext及其子类。它的子类AnnotationConfigApplicationContext是Spring中最常用的注解驱动IOC容器。
1.3 Environment
包含Profile和Property配置的信息,可实现统一的配置存储、配置解析等。
用IDEA生成Environment接口的父接口类图:
Environment继承PropertyResolver,PropertyResolver具有获取属性和解析占位符的能力。ConfigurableEnvironment具有指定激活的Profile和获取MutablePropertySources的能力。AbstractEnvironment是所有Environment的落地实现类。
下面的源码分析会发现ApplicationContext创建后Environment才创建,所以Environment伴随ApplicationContext的存在。ApplicationContext同时包含普通bean和Environment,同时Environment还要辅助处理配置属性的解析和注入,配合ApplicationContext工作,所有bean都可能要Environment参与处理,所以Environment覆盖范围比普通bean更大,但在ApplicationContext范围内。
2. springApplication的创建和启动
Spring Boot项目主启动类的main方法会调用springApplication的静态run方法,这个run方法会调用重载的静态run方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
重载方法会创建SpringApplication对象,然后调用这个对象的run方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
2.1 创建
SpringAplication对应构造方法:
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
SpringAplication还支持传入特定ResourceLoader以实现自定义资源加载:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// ...
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//传入的主启动类放入primarySources
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//判断应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置应用初始化器
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置事件监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//确定主启动类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
2.1 判断应用类型
直接打开枚举WebApplicationType:
public enum WebApplicationType {
NONE,
SERVLET,
REACTIVE;
private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
//...
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, (ClassLoader)null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, (ClassLoader)null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, (ClassLoader)null)) {
return REACTIVE;
} else {
String[] var0 = SERVLET_INDICATOR_CLASSES;
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
String className = var0[var2];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return NONE;
}
}
return SERVLET;
}
}
}
Spring Boot 2.x解决Web场景有WebMvc和WebFlux两种方案。从源码看出:
- 若WebFlux的核心控制器DispatcherHandler存在,且WebMvc的核心控制器DispatcherServlet不存在,则启用WebFlux环境;
- 若Servelet类和ConfigurableWebApplicationContext中有任何一个不存在,则启用None环境;
- 否则启用Servlet环境。
2.2 设置应用初始化器
先看看ApplicationContextInitializer接口,顾名思义它和初始化ApplicationContext有关。我们的分析是基于WebMvc环境,这个环境中对应的实现是ServletContextApplicationContextInitializer:
public class ServletContextApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext>, Ordered {
private final ServletContext servletContext;
public ServletContextApplicationContextInitializer(ServletContext servletContext) {
this(servletContext, false);
}
//...
public void initialize(ConfigurableWebApplicationContext applicationContext) {
applicationContext.setServletContext(this.servletContext);
if (this.addApplicationContextAttribute) {
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, applicationContext);
}
}
}
重写的initialize方法将ApplicationContext和ServletContext互相放到对方的容器中。
回头看this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
明显用了SPI机制加载配进去的所有ApplicationContextInitializer实现类。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return this.getSpringFactoriesInstances(type, new Class[0]);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
//Spring SPI
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
2.3 设置事件监听器
Spring本身有完善的事件监听机制,开发时可以编写事件并发布,然后设置监听方法处理。Spring Boot将Spring事件监听机制做了扩展,首先看下事件监听的抽象SpringApplicationRunListener接口。它有下面方法:
-
starting,调用SpringApplication的run方法时立即回调(下面的run方法源码可以看到)。
-
environmentPrepared,Environment构建完成但ApplicationContext创建前回到。
-
contextPrepared,创建ApplicationContext后调用。
-
contextLoaded,ApplicationContext已加载到尚未刷新容器。
-
started,容器已刷新但未调用CommandLineRunners和ApplicationRunners。
-
running,run方法彻底完成前。
-
failed,run方法执行过程抛异常时调用。
它唯一的实现类EventPublishingRunListener看,每个方法对应一个Event对象:
- starting:ApplicationStartingEvent
- environmentPrepared:ApplicationEnvironmentPreparedEvent
- contextPrepared:ApplicationContextInitializedEvent
- contextLoaded:ApplicationPreparedEvent
- started:ApplicationStartedEvent
- running:ApplicationReadyEvent
- failed:ApplicationFailedEvent
了解到这个程度可以更好分析下面的源码了。SpringApplication的构造方法中设置事件监听器也是用了SPI机制。
Spring Boot加载解析yaml或properties等配置文件就是依靠一个META-INF/spring.factories中配的一个事件监听器来完成相关动作的,后面接着分析这一点的源码。
2.4 确定主启动类
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
//遍历方法调用栈,找到有main方法那一层,应类就是主启动类
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
// ...
return null;
}
2.2 启动
SpringApplication对象创建完成后就执行run方法启动Spring Boot应用。
public ConfigurableApplicationContext run(String... args) {
//...
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
//获取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//调用所有SpringApplicationRunListener实现类的starting
listeners.starting();
try {
//封装main方法的args参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
//创建空的容器
context = this.createApplicationContext();
//容器初始化
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器
this.refreshContext(context);
//容器刷新后的回调,模板方法供子类重写
this.afterRefresh(context, applicationArguments);
//调用SpringApplicationRunListener实现类的started(容器已刷新但未调用CommandLineRunners和ApplicationRunners)
listeners.started(context);
//调用CommandLineRunners和ApplicationRunners
this.callRunners(context, applicationArguments);
} catch (Throwable var9) {
//执行run抛出异常,调用listeners.failed(context, exception);
this.handleRunFailure(context, var9, listeners);
throw new IllegalStateException(var9);
}
try {
//run方法彻底完成时回调
listeners.running(context);
return context;
} catch (Throwable var8) {
this.handleRunFailure(context, var8, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var8);
}
}
run方法是按照IOC容器的创建前准备、创建、刷新、刷新后回调的流程来处理的,上面介绍的SpringApplicationRunListener接口方法的回调会贯穿其中。
2.2.1 创建前准备
先获取SpringApplicationRunListeners并执行starting方法。SpringApplicationRunListeners是个工具类,它组合了SpringApplicationRunListener对象集合,实现了SpringApplicationRunListener的所有接口方法,具体的实现逻辑在SpringApplicationRunListener的唯一实现类EventPublishingRunListener中。进一步看,EventPublishingRunListener中重写方法中会先构造事件对象,然后用它组合的事件广播器SimpleApplicationEventMulticaster广播或用ConfigurableApplicationContext的publishEvent方法发布。
创建前准备环境:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
//创建环境
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
//配置环境
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach((Environment)environment);
//SpringApplicationRunListener的回调environmentPrepared
listeners.environmentPrepared((ConfigurableEnvironment)environment);
//Environment与SpringApplication绑定
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
先创建环境,根据SpringApplication构造方法中确定的Web类型,创建对应Environment子类对象返回。
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
} else {
switch(this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
}
再配置环境:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
//获取ApplicationConversionService实例配置到environment
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService)conversionService);
}
//main方法参数封装成PropertySource,配置到environment
this.configurePropertySources(environment, args);
//手动编写的激活的Profile配置到environment
this.configureProfiles(environment, args);
}
最后Environment与SpringApplication绑定:
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
} //catch ...
}
Binder将environment中以“spring.main”开头的值映射到SpringApplication中
2.2.2 创建空容器
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
//根据Web类型找到对应ApplicationContext的实现类
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
//用反射创建容器对象
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
2.2.3 容器初始化
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
//创建容器后的后置处理
this.postProcessApplicationContext(context);
//应用SpringApplication构造方法中设置的初始化器
this.applyInitializers(context);
listeners.contextPrepared(context); //调用SpringApplicationRunListener实现类的started
//从ConfigurableApplicationContext中获取beanFactory
//然后用beanFactory注册main方法参数对应的封装对象,设置是否运行Bean定义覆盖,添加懒加载BeanFactoryPostProcessor
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
//获取所有配置源
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载配置源
this.load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
-
容器后置处理
protected void postProcessApplicationContext(ConfigurableApplicationContext context) { //注册Bean的名称生成器 if (this.beanNameGenerator != null) { context.getBeanFactory().registerSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator", 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()); } } //注册类型转换器 if (this.addConversionService) { context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); } } -
应用初始化器比较简单,取出来遍历,分别调对应的初始化方法
protected void applyInitializers(ConfigurableApplicationContext context) { Iterator var2 = this.getInitializers().iterator(); while(var2.hasNext()) { ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next(); Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class); Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); initializer.initialize(context); } } -
获取所有配置源。
public Set<Object> getAllSources() { Set<Object> allSources = new LinkedHashSet(); //main方法传入的主类Class对象放在了primarySources if (!CollectionUtils.isEmpty(this.primarySources)) { allSources.addAll(this.primarySources); } if (!CollectionUtils.isEmpty(this.sources)) { allSources.addAll(this.sources); } return Collections.unmodifiableSet(allSources); } -
加载配置源
protected void load(ApplicationContext context, Object[] sources) { BeanDefinitionLoader loader = this.createBeanDefinitionLoader(this.getBeanDefinitionRegistry(context), sources); //... loader.load(); }创建的Bean定义加载器,可以加载XML配置中定义的Bean和注解驱动的Bean,也可以获取包路径下被@Component标注的Bean。它组合了XmlBeanDefinitionReader、AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner,从重载的load方法可以看出,具体的加载工作委托给了这几个类来做。
class BeanDefinitionLoader { private final Object[] sources; private final AnnotatedBeanDefinitionReader annotatedReader; private final XmlBeanDefinitionReader xmlReader; private BeanDefinitionReader groovyReader; private final ClassPathBeanDefinitionScanner scanner; int load() { int count = 0; Object[] var2 = this.sources; int var3 = var2.length; for(int var4 = 0; var4 < var3; ++var4) { Object source = var2[var4]; count += this.load(source); } return count; } //重载的load,根据配置源类型委托不同加载器来做 private int load(Object source) { Assert.notNull(source, "Source must not be null"); if (source instanceof Class) { return this.load((Class)source); } else if (source instanceof Resource) { return this.load((Resource)source); } else if (source instanceof Package) { return this.load((Package)source); } else if (source instanceof CharSequence) { return this.load((CharSequence)source); } else { throw new IllegalArgumentException("Invalid source type " + source.getClass()); } } }
2.2.4 刷新容器
这里细节更多,提供了很多的扩展点给开发者,后面再分析。
private void refreshContext(ConfigurableApplicationContext context) {
//...
this.refresh((ApplicationContext)context);
}
//refreshContext调refresh,实际是调ConfigurableApplicationContext的refresh方法
//ConfigurableApplicationContext接口的refresh,最终都会调AbstractApplicationContext的refresh
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
2.2.5 刷新后回调
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
Iterator var4 = (new LinkedHashSet(runners)).iterator();
while(var4.hasNext()) {
Object runner = var4.next();
if (runner instanceof ApplicationRunner) {
this.callRunner((ApplicationRunner)runner, args);
}
if (runner instanceof CommandLineRunner) {
this.callRunner((CommandLineRunner)runner, args);
}
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
runner.run(args.getSourceArgs());
} // catch ...
}
收集容器中所有ApplicationRunner和CommandLineRunner对象,然后分别调用run方法。这里用于在IOC容器完全启动后,SpringApplicationRunListener的事件完成后,留出来的一个扩展点。
@FunctionalInterface
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
//CommandLineRunner同样也只有一个run方法
在这个扩展点是位于应用启动完成后,所以可以利用它在应用启动后做一些工作。如应用启动后从数据库加载一些配置信息、从数据库加载热点数据到缓存等;应用启动后立即启动向数据同步、日志清理这样的后台任务;应用启动后设置一些默认的角色权限,保证系统正常的功能可用。
如下,在主启动类直接实现ApplicationRunner,并用@Order控制执行顺序(注解中数字越小越先执行):
@SpringBootApplication
public class SpringBootInductionApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootInductionApplication.class, args);
}
@Component
@Order(3)
class ApplicationRunnerBean implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunnerBean...");
System.out.println("Spring Boot成功启动,读库写缓存...");
}
}
@Component
@Order(2)
class InductApplicationRunnerBean2 implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunnerBean2...");
System.out.println("Spring Boot成功启动,初始化系统常用角色和权限...");
}
}
}
执行结果:
项目启动后,紧接着回调逻辑也执行了。