SpringApplication的初始化简介
在入口类主要通过SpringApplication的静态方法–run方法进行SpringApplication类的实例化操作,然后再针对实例化对象调用另一个run方法完成整个项目的初始化和启动。本章节重点围绕此过程的前半部分(即SpringApplication类的实例化)来讲解。
public class SpringApplication {
......
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
//创建SpringApplication对象并执行某run方法
return (new SpringApplication(primarySources)).run(args);
}
......
通过入口类的方法进入,可以看到SpringApplication的实例化只是在它提供的静态run方法中创建了一个SpringApplication对象。其中参数primarySources为加载的主要资源类,通常就是SpringBoot的入口类,args为传递给应用程序的参数信息。
SpringApplication实例化流程
上面了解了进行SpringApplication实例化的基本方法,下面我们通过一张简单的流程图来系统地学习在创建SpringApplication对象时都进行了那些核心操作,如下图:
由上图可以看出,在SpringApplication对象实例化的过程中主要做了三件事:参数赋值给成员变量、应用类型及方法推断和ApplicationContext相关内容加载及实例化。
SpringApplication构造方法参数
下面来看下SpringApplication两个构造方法的核心源代码。
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
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();
}
SpringApplication提供了两个构造方法,核心业务逻辑在第二个构造方法中实现。
- resourceLoader:为资源加载的接口,在Spring Boot启动时打印对应的banner信息,默认采用的就是DefaultResourceLoader。实战过程中,如果程序未按照Spring Boot的“约定”将banner的内容放置于classPath下,或者文件名不是banner.*格式,默认资源加载器是无法加载到对应的banner信息的。此时可通过ResourceLoader来指定需要加载的文件路径。
- primarySources:为可变参数,默认传入Spring Boot入口类。如果作为项目的引导类,此参数传入的类需要满足一个条件,就是被注解@EnableAutoConfiguration或其组合注解标注。由于@SpringBootApplication注解包含了@EnableAutoConfiguration注解,因此被@SpringBootApplication注解标注的类可作为参数传入。当然,该参数可传入其他普通类。但只有传入被@EnableAutoConfiguration标注的类才能够开启SpringBoot的自动配置。
同时,在SpringApplication类中还提供了追加primarySources的方法,代码如下:
public void addPrimarySources(Collection<Class<?>> additionalPrimarySources) {
this.primarySources.addAll(additionalPrimarySources);
}
回到primarySources参数中,在实例化SpringApplication类过程中并没有对primarySources参数多做处理,只是将其转化为Set集合,并赋值给SpringApplication的私有成员变量Set<Class<?>>primarySources,代码如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
......
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
......
}
primarySources 私有变量依旧为LinkedHashSet,它具有去重的特性,至此,SpringApplication构造时参数赋值对应变量这一步变完成了。
Web类型推断
完成变量赋值之后,在SpringApplication的构造方法中便调用了WebApplicationType的deduceFromClasspath方法来进行Web应用类型的推断。SpringApplication构造方法相关代码如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
......
this.webApplicationType = WebApplicationType.deduceFromClasspath();
......
}
该行代码调用了deduceFromClasspath方法并将获得的web应用类型赋值给私有成员变量webApplicationType。
WebApplicationType为枚举类,它定义了可能的web应用类型,该枚举类提供了一类定义:枚举类型,推断类型的方法和用于推断的常量。枚举类型包括非web应用,基于SERVLET的web应用和基于REACTIVE的web应用。代码如下:
public enum WebApplicationType {
NONE,
SERVLET,
REACTIVE;
......
}
WebApplicationType内针对web应用类型提供了两个推断方法:deduceFromClasspath和deduceFromApplicationContext方法。再次我们使用了deduceFromClasspath方法,下面重点分析该方法的实现:
public enum WebApplicationType {
......
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";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
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;
}
......
}
方法deduceFromClasspath是基于classpath中类是否存在进行类型推断的,就是判断指定的类是否存在于classpath下,并根据判断的结果来进行组合推断该应用属于什么类型。deduceFromPath在判断的过程中用到了ClassUtils的isParent方法。isParent方法的核心机制就是通过反射创建指定的类,根据在创建过程中是否抛出异常来判断该类是否存在。
通过上面的源代码,我们可以看到deduceFromClasspath的推断逻辑如下:
- 当DispatcherHandler存在,并且DispatcherServlet和ServletContainrer都不存在,则返回类型为WebApplicationType.REACTIVE。
- 当SERVLET或ConfigurationContext任何一个不存在时,说明当前应用为非Web应用,返回WebApplicationType.NONE。
- 当应用部位REACTIVE Web应用,并且SERVLET和ConfigurationWebApplcationContext都存在情况下,则为SERVLET的Web应用,返回WebApplicationType.SERVLET。
ApplicationContextInitializer加载
源码分析
applicationContextInitializer是Spring IOC容器提供的一个接口,它是一个回调接口,主要目的是允许用户在ConfigurableApplicationContext类型(或其子类型)的ApplicationContext做refresh方法调用刷新之前,对ConfigurableApplicationContext实例做进一步的社会或处理,通常用于程序上下文进行编程初始化的Web应用程序中。
ApplicationContextInitializer接口只定义了一个initialze方法,代码如下:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C var1);
}
ApplicationContextInitializer接口的initialize方法主要是为了初始化指定的应用上下文。儿对应的上下文有参数传入,参数为ConfigurableApplicationContext的子类。
在完成web应用类型推断,ApplicationContextInitializer便开始进行加载工作,该过程分为两部分:获取相关实例和设置实例。对应的方法为getSpringFactoriesInstances和setInitializers。
SpringApplication中获得实例相关方法源码如下:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
getSpringFactoriesInstances方法是通过SpringClassLoader类的loadFactoryName方法来获得META-INF/spring.factories文件中注册的对应配置。在springBoot2.2.1版本找那个,该文件内具体的配置代码如下:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
配置代码中后面的类为接口ApplicationContextInitializer的具体实现类。当获得这些配置的全限定名之后,便可调用createSpringFactoriesInstances方法进行相应的实现操作。
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
//便利加载到的类名(全限定类名)
for (String name : names) {
try {
//获取class
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;
}
完成获取配置类集合和实例化操作之后,调用setInitializers方法将实例化的集合添加到SpringApplication的成员变量initializers中,类型为List<ApplicationContextInitializer<?>>,代码如下:
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>(initializers);
}
setInitializers方法将收到的initializers作为参数创建了一个新的list,并将其赋值给SpringApplication的initializers成员变量。由于是创建了新的List,并且直接赋值,因此该方法一旦被调用,变回导致数据覆盖,使用时需注意。
ApplicationListener加载
完成了ApplicationContextInitializer的加载之后,便会进行ApplicationListener的加载,他的常见应用场景为:当容器初始化完成之后,需要处理一些如数据的加载,初始化缓存、特定任务的注册等。而在此阶段,更多的是用于ApplicationContext管理bean过程的场景。
Spring事件传播机制是基于观察者模式(Observer)实现的,比如,在ApplicationContext管理Bean生命周期的过程中,会将一些改变定义为事件(ApplicationEvent)。ApplicationContext通过ApplicationListener监听ApplicationEvent,当事件被发布之后,ApplicationListener来对事件做出具体的操作。
ApplicationListener的整个配置和加载流程与ApplicationContextInitializer完全一致,也是通过SpringFactoriesLoader的loadFactoryNames方法获得META-INF/spring.factories中对应配置,然后再进行实例化,最后将获得的结果集合添加到SpringApplication的成员变量listeners中,代码如下:
private List<ApplicationListener<?>> listeners;
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
同样的,在调用setListeners方法时会进行覆盖赋值的操作,之前加载的内容会被清除。
下面我们来看看ApplicationListener这里的基本使用。ApplicationListener接口和ApplicationEvent类配合使用,可实现ApplicationContext的事件处理。如果容器中存在ApplicationListener的Bean,当ApplicationContext调用publishEvent方法时,对应的Bean会被触发。这就是上文提到的观察者模式的实现。
在接口ApplicationListener中只定义了一个onApplicationEvent方法,当监听事件被触发时,onApplicationEvent方法会被执行,接口ApplicationListener的源代码如下:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
onApplicationEvent方法一般用于处理应用程序事件,参数event为ApplicationEvent的子类,是具体响应(接收到)的事件。
当ApplicationContext被初始化或刷新时,会触发ContextRefreshedEvent事件。
SpringApplication的定制化配置
基础配置
基础配置与在application.properties文件中的配置一样,用来修改SpringBoot预置参数。比如,我们想在启动程序的时候不打印Banner信息,可以通过在application。properties文件中设置"spring.main.banner-mode=off"来进行关闭。当然,我们也可以通过SpringApplication提供的相关方法来进行同样的操作。例如以下方式:
public static void main(String[] args){
SpringApplication app = new SpringApplication(MySpringConfiguration.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
除了上面讲到的setInitializers个setListeners方法之外,其他的Setter方法都具有类似的功能,比如我们可以通过setWebApplicationType方法来代替SpringBoot默认的自动类型推断。
针对这些settter方法,SpringBoot还专门提供了流式处理类SpringApplicationBuilder,我们将他的功能与SpringApplication逐一对照,可知SpringApplicationBuilder的优点是使代码更简洁,流畅。
配资源配置
除了直接通过Setter方法进行参数的配置,我们还可以通过配置配置源参数对整个配置文件或配置类进行配置。可以通过两个途径进行配置:SpringApplication构造方法参数或SpringApplication提供的setSource方法进行配置。在上面我们已经了解了通过Class<?>…primarySources参数来配置普通类。因此,配置类可通过SpringApplication的构造方法进行指定。但这种方法有一个弊端就是无法指定XML配置和基于package的配置。
另一种配置形式为直接调用setSources方法进行配置,方法源代码如下:
public void setSources(Set<String> sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<>(sources);
}
该方法的参数为String集合,可传递类名,package名称和xml配置资源。