在Spring Boot的应用中,一般都是直接使用SpringApplication的静态方法run来运行,当然也可以先创建SpringApplication实例并设置一些属性,然后调用其对象方法run来运行。实际上在静态的run方法中也是通过创建一个SpringApplication实例然后调用实例方法run。所以两种方法本质上没有区别。 本文分析Spring Boot的构造原理,也就是创建SpringApplication实例的过程。
本文分析的Spring Boot源码版本是2.7.x,Spring Framework源码版本是5.3.22。
SpringApplication构造方法
SpringApplication中有两个构造方法,其中一个没有设置资源加载器(ResourceLoader)。
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details). The instance can be customized before calling
* {@link #run(String...)}.
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #SpringApplication(ResourceLoader, Class...)
* @see #setSources(Set)
*/
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details). The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* 这里primarySources并没有强制要求一定是主类,一般其他直接或间接标注@EnableAutoConfiguration的类都可以
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@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));
// 推断web应用类型,共有三种
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 加载启动注册初始化器,默认在SpringBoot中是没有提供的,但在Spring Cloud中会使用。
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 加载并初始化ApplicationContextInitializer及相关实现类(目前的版本共有5个),然后赋值给属性initializers
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 同理,加载ApplicationListener及相关实现类
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断main方法所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}重点关注第二个构造器的实现,可以发现其内部主要做了下面几件事情:
- 属性赋值:这没有什么好说的,就是把资源加载器(resourceLoader)和主配置源(primarySources)这两个参数赋值给当前类中的属性。
- 推断应用类型;
- 加载一些预配置的类并创建其实例:
- 加载并创建启动注册初始化器(BootstrapRegistryInitializer)对象,默认情况下在Spring Boot中是没有在spring.factories文件中配置该类型的。
- 加载并创建应用上下文初始化器(ApplicationContextInitializer)对象,会加载下面这些类型:
propertiesspring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories# 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- 加载并创建应用监听器(ApplicationListener)对象,会加载下面这些类型:
propertiesspring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.env.EnvironmentPostProcessorApplicationListener - 推断主方法(main)所在类;
其中几个加载类的操作都是调用的同一个方法,所以一起分析。
推断应用类型
Spring Boot支持两种类型的web应用的自动配置:Servlet和Reactive。在构造过程中就判断是哪种类型,是为后续运行过程做准备。运行过程中会创建ApplicationContext和Environment对象,都会判断当前的应用是哪种类型。 当然Spring Boot也支持非web类型,不过一般用得少。
/**
* An enumeration of possible types of web application.
*
* @author Andy Wilkinson
* @author Brian Clozel
* @since 2.0.0
*/
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an
* embedded web server.
* 非Web应用
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
* 基于Servlet的Web应用
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
* 基于Reactive的Web应用
*/
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";
// 基于classpath下面是否存在某些类来推断WEB类型
static WebApplicationType deduceFromClasspath() {
// DispatcherServlet和ServletContainer不存在,且存在DispatcherHandler则为REACTIVE类型
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// Servlet和ConfigurableWebApplicationContext任意一个不存在,则返回NONE
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET; // 默认返回SERVLET
}
}可以发现,Spring Boot是通过判断类路径下是否有相应的类来判断是哪种类型,这种方法在Spring的框架中经常被使用。
getSpringFactoriesInstances方法
在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
// 加载预配置在spring.factories中的配置项
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 创建实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}loadFactoryNames方法
SpringFactoriesLoader类是Spring Framework提供的。
该方法的作用是获取预配置在spring.factories中的目标配置。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 获取类名称
String factoryTypeName = factoryType.getName();
// 从所有spring.factories文件中的配置项获取目标配置,这里的factoryTypeName是key,从map中获取value。
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 从缓存中获取数据
Map<String, List<String>> result = cache.get(classLoader);
// 缓存命中则直接返回
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// 获取所有META-INF/spring.factories文件所在的路径
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
// 遍历每一个spring.factories文件
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 加载spring.factories文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 遍历spring.factories文件中的每一项
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 获取key
String factoryTypeName = ((String) entry.getKey()).trim();
// 处理逗号分割的数组
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
// 将每一项配置添加到列表中
for (String factoryImplementationName : factoryImplementationNames) {
// 如果key不存在则创建一个新的列表(第二个参数)
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
// 添加到列表中
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
// 对结果中的每个列表进行去重并转化为不可修改列表
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
// 将结果保存到缓存中
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}从Spring Boot 2.7开始,官方不再推荐使用/META-INF/spring.factories文件的方式进行自动配置。从Spring Boot 3.x开始,会移除掉相关支持。 而新的方式则是使用/META-INF/spring/<key>.imports文件,这里的key就是原先在spring.factories文件中的key,而新文件中的内容则是原先的值。
createSpringFactoriesInstances方法
该方法的作用是创建loadFactoryNames方法加载到的那些预配置好的类的实例。
@SuppressWarnings("unchecked")
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;
}接下来就是对这些创建好的对象排序,该过程较简单,省略分析过程。
推断main方法所在类
这里Spring Boot的实现方式比较巧妙,通过创建一个异常并获取其栈桢列表。然后遍历该列表,从中会获取main方法所在的类。
private Class<?> deduceMainApplicationClass() {
try {
// 通过异常的栈帧来判断入口类
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) { // 如果方法名为main则说明是入口类
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}通过这个例子也可以看出,一般对于开发者而言被动处理的异常(如惹人烦的NullPointerException),却可以主动使用它来实现一些功能。
总结
本文总体来说比较简单,主要分析了SpringApplication的构造方法的过程,一些资料将其称为Spring Boot的构造过程或原理,所以本文也以此作为标题。 Spring Boot的构造原理是在为后续的运行过程做准备,更多细节请参考Spring Boot的运行过程一文。