
SpringBoot源码(一):初始化SpringApplication
SpringBoot 中文文档位置:http://felord.cn/_doc/_springboot/2.1.5.RELEASE/_book/pages/boot-documentation.html
一、springBoot核心功能优势
- 独立运行的Spring项目:Spring Boot可以以jar包的形式进行独立的运行,使用:java -jar xx.jar 就可以成功的运行项目,或者在应用项目的主程序中运行main函数即可;
- 内嵌的Servlet容器:内嵌容器,使得我们可以执行运行项目的主程序main函数,并让项目的快速运行
- 提供starter简化Manen配置:Spring Boot提供了一系列的starter pom用来简化我们的Maven依赖
- 自动配置Spring:Spring Boot会根据我们项目中类路径的jar包/类,为jar包的类进行自动配置Bean,这样一来就大大的简化了我们的配置。当然,这只是Spring考虑到的大多数的使用场景,在一些特殊情况,我们还需要自定义自动配置;(约定优于配置)
- 应用监控:Spring Boot提供了基于http、ssh、telnet对运行时的项目进行监控;
二、上代码
1、启动类
@SpringBootApplication
public class FastExportApplication {
public static void main(String[] args) {
SpringApplication.run(FastExportApplication.class, args);
}
}
2、Debug进入 SpringApplication.run 方法,最后到了一个构造函数中
/**
* 静态助手,可用于使用默认设置从指定源运行SpringApplication
* @param primarySource 要加载的主要源
* @param args 应用程序参数(通常从Java main方法传递)
* @return
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
/**
* 静态助手,可用于使用默认设置和用户提供的参数从指定源运行SpringApplication 。
* @param primarySources
* @param args
* @return
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
进入SpringApplication.java 构造函数:
/**
* 创建一个新的SpringApplication实例。 应用程序上下文将从指定的主要源加载Bean
*(有关详细信息,请参见class-level文档。可以在调用run(String...)之前自定义实例。
*/
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//上一步入参 resourceLoader 为 null
this.resourceLoader = resourceLoader;
//断言 参数primarySources 不能是空,debug:此时参数是 启动类 FastExportApplication.class
Assert.notNull(primarySources, "PrimarySources must not be null");
//debug 此时 primarySources 得到值 为 FastExportApplication.class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1、判断webApplicationType启动类型,枚举值如下:
// NONE:顾名思义,什么都没有,正常流程走,启动时不启动额外的web容器,比如Tomcat
// SERVLET:应用程序应该作为一个基于servlet的web应用程序运行,并且应该启动一个嵌入式servlet web服务器。
// REACTIVE:应用程序应该作为响应式web应用程序运行,并且应该启动嵌入式reactive web服务器。
//这个deduceFromClasspath()方法中最重要的功能:是根据 java.lang.Class#forName(String, boolean, ClassLoader) 的加载class功能,
// 而class加载是依赖jar包是否引起而判断的,所以如果引入了javax.servlet.Servlet的jar,则会启动SERVLET模式,
// 如果引入的jar是spring-boot-starter-webflux,而且没引入servlet相关的jar,则会启动REACTIVE模式。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//2、设置bootstrappers。从配置文件META-INF/spring.factories中获取key为org.springframework.boot.Bootstrapper的值
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
//3、设置所有可用的初始化器。从配置文件META-INF/spring.factories中获取key为org.springframework.context.ApplicationContextInitializer的值
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//4、设置所有可用的监听器。从配置文件META-INF/spring.factories中获取key为org.springframework.context.ApplicationListener的值
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//5、推断并设置main方法的定义类
this.mainApplicationClass = deduceMainApplicationClass();
}
本构造方法,其实主要做了一下几件事:
- 推断应用类型是否web
- 设置可用的初始化器
- 设置可用的监听器
- 推荐main方法的类
详细来说:
推断当前应用的类型:
/**
* An enumeration of possible types of web application.
* web应用程序可能类型的枚举
* @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 web服务器。
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
* 应用程序应该作为响应式web应用程序运行,并且应该启动嵌入式reactive web服务器。
*/
REACTIVE;
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
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;
}
}
- 关于 servlet web与Reactive Web 不懂的,请看这个链接
设置可用的初始化器及可用监听
主要的是进入这个getSpringFactoriesInstances()方法中。
位置:org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>)
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;
}
是如何获取的初始化器的名字?进入这个方法SpringFactoriesLoader.loadFactoryNames
/**
* 使用给定的类加载器从“ META-INF / spring.factories”加载给定类型的工厂实现的标准类名。
* 从Spring Framework 5.3开始,如果多次发现给定工厂类型的特定实现类名称,则将忽略重复项。
* @param factoryType 代表工厂的接口或抽象类
* @param classLoader 用于加载资源的ClassLoader; 可以为null以使用默认值
* @throws IllegalArgumentException 如果加载工厂名称时发生错
* @return List 初始化器的集合
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// debug classLoaderToUse 不是空,上一步 SpringApplication.getSpringFactoriesInstances 方法中已经 getClassLoader()
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
//获取类名,SpringApplication.java 构造函数 传入的参数factoryType 不是空。
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
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 {
//工厂位置,类开始定义的配置文件的位置:public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//spring.factories 只存在 spring-boot-2.4.4.jar、spring-boot-autoconfigure-2.4.4.jar 中
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
//从文件中获取值,进行遍历
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
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)));
//将spring.factories中的值放入缓存中,下次使用时,直接从缓存中获取即可
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
获取的初始化器个数截图:

获取的监听器个数截图:

推断启动类:
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;
}
debug 获取的执行的步骤路径:

最后源码图:

文章被以下专栏收录

推荐阅读

手把手带你剖析 Springboot 启动原理!

SpringBoot源码之属性文件加载原理剖析
手把手带你剖析 Springboot 启动原理!
我们开发任何一个Spring Boot项目,都会用到如下的启动类 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Applicati…
springboot自动配置原理
配置文件能配置的属性参照 https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#common-application-properties 1、自动配置原理: 1)SpringBoot启动的时候加载…
还没有评论