文章主要对 Spring Boot 的源码进行分析,包括 SpringApplication 的创建和启动流程。介绍了 IOC 相关的 API 如 BeanFactory、ApplicationContext、Environment 等。在创建过程中,涉及判断应用类型、设置应用初始化器、事件监听器和确定主启动类。启动过程包括创建前准备、创建空容器、容器初始化、刷新容器、刷新后回调等步骤,并对每个步骤进行了详细阐述。
关联问题:IOC容器如何初始化SpringApplication如何创建Web应用类型如何判断
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方法:
java
代码解读
复制代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class[]{primarySource}, args); }
重载方法会创建SpringApplication对象,然后调用这个对象的run方法:
java
代码解读
复制代码
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return (new SpringApplication(primarySources)).run(args); }
2.1 创建
SpringAplication对应构造方法:
java
代码解读
复制代码
public SpringApplication(Class<?>... primarySources) { this((ResourceLoader)null, primarySources); }
SpringAplication还支持传入特定ResourceLoader以实现自定义资源加载:
java
代码解读
复制代码
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:
java
代码解读
复制代码
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:
java
代码解读
复制代码
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实现类。
java
代码解读
复制代码
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 确定主启动类
java
代码解读
复制代码
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应用。
java
代码解读
复制代码
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方法发布。
创建前准备环境:
java
代码解读
复制代码
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子类对象返回。
java
代码解读
复制代码
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(); } } }
再配置环境:
java
代码解读
复制代码
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绑定:
java
代码解读
复制代码
protected void bindToSpringApplication(ConfigurableEnvironment environment) { try { Binder.get(environment).bind("spring.main", Bindable.ofInstance(this)); } //catch ... }
Binder将environment中以“spring.main”开头的值映射到SpringApplication中
2.2.2 创建空容器
java
代码解读
复制代码
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 容器初始化
java
代码解读
复制代码
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); }
-
容器后置处理
java
代码解读
复制代码
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()); } }
-
应用初始化器比较简单,取出来遍历,分别调对应的初始化方法
java
代码解读
复制代码
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); } }
-
获取所有配置源。
java
代码解读
复制代码
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); }
-
加载配置源
java
代码解读
复制代码
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方法可以看出,具体的加载工作委托给了这几个类来做。
java
代码解读
复制代码
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 刷新容器
这里细节更多,提供了很多的扩展点给开发者,后面再分析。
java
代码解读
复制代码
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 刷新后回调
java
代码解读
复制代码
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的事件完成后,留出来的一个扩展点。
java
代码解读
复制代码
@FunctionalInterface public interface ApplicationRunner { void run(ApplicationArguments args) throws Exception; } //CommandLineRunner同样也只有一个run方法
在这个扩展点是位于应用启动完成后,所以可以利用它在应用启动后做一些工作。如应用启动后从数据库加载一些配置信息、从数据库加载热点数据到缓存等;应用启动后立即启动向数据同步、日志清理这样的后台任务;应用启动后设置一些默认的角色权限,保证系统正常的功能可用。
如下,在主启动类直接实现ApplicationRunner,并用@Order控制执行顺序(注解中数字越小越先执行):
java
代码解读
复制代码
@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成功启动,初始化系统常用角色和权限..."); } } }
执行结果:
项目启动后,紧接着回调逻辑也执行了。