在Spring Framework时代,Spring应用上下文通常由容器启动,如ContextLoaderListener或WebApplicationInitializer的实现类由Servlet容器装载并驱动。到了Spring Boot时代,Spring应用上下文的启动则通过调用SpringApplication.run(Object,String …)或SpringApplicationBuilder.run(String …)方法并配合@SpringBootApplication或@EnableAutoConfiguration注解方式完成。
从Spring Boot应用进程来看,整体的生命周期大体上总结如下:
SpringApplication初始化阶段
SpringApplication运行阶段
SpringApplication结束阶段
SpringApplication应用退出
SpringApplication初始化阶段属于运行前的准备阶段,大多数Spring Boot应用直接或间接地使用SpringApplication API驱动Spring应用,SpringApplication 允许指定应用类型,Web还是非Web,Banner的输出、配置默认属性的内容等,这些状态变更的操作只要在run()方法之前指定即可。简单而言,SpringApplication 的准备阶段主要由两阶段完成:构造阶段和配置阶段。
1、SpringApplication构造阶段
SpringApplication构造阶段在其构造器中完成,在日常开发中,很少直接与SpringApplication构造器打交道,而是调用其静态方法run(Class,String …)来启动应用,实际上其构造过程就在其中:
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);
}
无论使用哪个方法都需要Class类型的primarySource 参数,这个参数有什么意义呢?
1.1、理解SpringApplication主配置类
SpringApplication主配置类的概念和实现是从Spring Boot2.0开始引入的。通常情况下,引导类将作为primarySource参数的内容,这些引导类基本上具备一个特点,不是标注@SpringBootApplication就是标注@EnableAutoConfiguration,由于@SpringBootApplication元标注@EnableAutoConfiguration,因此两者均被底层Spring应用上下文视作@EnableAutoConfiguration处理。当引导类作为primarySource参数时,Spring应用上下文将其视为Configuration Class处理。
主配置类属性primarySource除初始化构造器参数外,还能通过SpringApplication.addPrimarySources()方法追加修改:
private Set> primarySources;
public void addPrimarySources(Collection> additionalPrimarySources) {
this.primarySources.addAll(additionalPrimarySources);
}
1.2、SpringApplication的构造过程
已知SpringApplication.run()方法的执行会伴随SpringApplication对象的构造,其调用的构造器为:
public SpringApplication(Class... primarySources) {
this(null, primarySources);
}
@SuppressWarnings({
"unchecked", "rawtypes" })
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 = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
根据调用链路,实际执行的构造器为SpringApplication(ResourceLoader, Class),其中主配置类primarySources被SpringApplication对象primarySources属性存储,随后依次调用deduceFromClasspath()、setInitializers()、setListeners()和deduceMainApplicationClass()方法。按照方法名的语义,他们分别为:推断Web应用类型、加载Spring应用上下文初始化器、加载Spring应用事件监听器和推断应用引导类。
1.3、推断Web应用类型
推断Web应用类型属于当前Spring Boot应用Web类型的初始化过程,因为该类型可在SpringApplication构造后及run方法之前,再通setWebApplicationType(WebApplicationType)方法调整。在推断Web应用类型的过程中,由于当前Spring应用上下文尚未准备,所以实现采用的是检查当前ClassLoader下基准Class的存在性判断:
public enum WebApplicationType {
NONE,
SERVLET,
REACTIVE;
private static final String[] SERVLET_INDICATOR_CLASSES = {
"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, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
}
WebApplicationType.deduceFromClasspath()利用ClassUtils.isPresent(String,ClassLoader)方法依次判断DispatcherHandler、DispatcherServlet、ServletContainer、Servlet、ConfigurableWebApplicationContext的存在性组合情况,从而推断Web应用类型:
当DispatcherHandler存在,并且DispatcherServlet和ServletContainer不存在时,换言之,Spring Boot仅依赖WebFlux存在时,此时的Web应用类型为REACTIVE。
当Servlet和ConfigurableWebApplicationContext均不存在时,当前应用为非Web应用,即NONE。
其余情况为SERVLET。
当WebApplicationType.deduceFromClasspath()执行完毕,SpringApplication的构造进入加载Spring应用上下文初始化器的过程。
1.4、加载Spring应用上下文初始化器(ApplicationContextInitializer)
private Collection getSpringFactoriesInstances(Class type) {
return getSpringFactoriesInstances(type, new Class[] {
});
}
private Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 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;
}
此处同样运用了Spring工厂加载机制方法SpringFactoriesLoader.loadFactoryNames(type, classLoader)。结合本例的场景,该方法将返回所有META-INF/spring.factory资源中配置的ApplicationContextInitializer实现类名单:
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
当getSpringFactoriesInstances方法获取实现类名单后,调用createSpringFactoriesInstances方法初始化这些实现类:
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;
}
传递空数组的parameterTyps和args方法参数,则ApplicationContextInitializer实现类必须存在默认构造器。挑选ContextIdApplicationContextInitializer进行检验:
public class ContextIdApplicationContextInitializer
implements ApplicationContextInitializer, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE - 10;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ContextId contextId = getContextId(applicationContext);
applicationContext.setId(contextId.getId());
applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
}
private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
ApplicationContext parent = applicationContext.getParent();
if (parent != null && parent.containsBean(ContextId.class.getName())) {
return parent.getBean(ContextId.class).createChildId();
}
return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}
private String getApplicationId(ConfigurableEnvironment environment) {
String name = environment.getProperty("spring.application.name");
return StringUtils.hasText(name) ? name : "application";
}
static class ContextId {
private final AtomicLong children = new AtomicLong(0);
private final String id;
ContextId(String id) {
this.id = id;
}
ContextId createChildId() {
return new ContextId(this.id + "-" + this.children.incrementAndGet());
}
String getId() {
return this.id;
}
}
}
当SpringApplication构建器执行该方法后,加载Spring应用事件监听器的动作立即执行。
1.5、加载Spring应用事件监听器(ApplicationContextInitializer)
与加载ApplicationContextInitializer逻辑相同。
1.6、推断应用引导类
推断应用引导类是SpringApplication构造过程的末尾动作,其执行方法为deduceMainApplicationClass():
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;
}
该方法根据当前线程执行栈来判断其栈中哪个类包含main方法。尽管这个方法的实现并不严谨,不过可覆盖绝大多数以Java的标准main方法引导的情况。
至此在SpringApplication构造的过程中,SpringApplication属性primarySources、webApplicationType、initializers、listeners和mainApplicationClass均得到了初始化,下面讨论他们在不同阶段所扮演的角色。
2、SpringApplication配置阶段
配置阶段位于构造阶段和运行阶段之间,该阶段是可选的,主要用于调整或补充构造阶段的状态、左右运行时行为,以SpringApplication setter方法为代表,用于调整SpringApplication的行为,补充行为则以add*方法为主,或许通过setter方法配置SpringApplication 过于繁琐,因此Spring Boot引入SpringApplicationBuilder以提升API的便利性,多数情况下无须调整SpringApplication的默认状态。
2.1、自定义SpringApplication
自定义SpringApplication一共有三种方式:
调整SpringApplication设置
增加SpringApplication配置源
调整Spring Boot外部化配置
2.2、调整SpringApplication设置
几乎所有SpringApplication调整应用行为的方法都与SpringApplicationBuilder方法一一对应:
SpringApplication方法API
SpringApplicationBuilder方法API
2.3、增加SpringApplication配置源
SpringApplication构造参数primarySources是从Spring Boot2.0开始引入的,作为主配置类,还有一个sources属性充当配置源的角色,分别来自@Configuration Class、XML配置文件和package。
public void setSources(Set sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<>(sources);
}
public Set