本篇基于 Spring Boot 2.4.4 版本进行分析
Spring Boot 的入口类
@SpringBootApplication
public class SpringBootBestPracticeApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootBestPracticeApplication.class, args);
}
}
做过 Spring Boot 项目的都知道,上面是 Spring Boot 最简单通用的入口类。入口类的要求是最顶层包下面第一个含有 main 方法的类,使用注解 @SpringBootApplication 来启用 Spring Boot 特性,使用 SpringApplication.run 方法来启动 Spring Boot 项目。
来看一下这个类的run方法调用关系源码:
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);
}
第一个参数primarySource:加载的主要资源类
第二个参数 args:传递给应用的应用参数
先用主要资源类来实例化一个 SpringApplication 对象,再调用这个对象的 run 方法,所以我们分两步来分析这个启动源码。
SpringApplication 的实例化过程
接着上面的 SpringApplication 构造方法进入以下源码:
public SpringApplication(Class>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
// 1、资源初始化资源加载器为 null
this.resourceLoader = resourceLoader;
// 2、断言主要加载资源类不能为 null,否则报错
Assert.notNull(primarySources, "PrimarySources must not be null");
// 3、初始化主要加载资源类集合并去重
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 4、推断当前 WEB 应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 5、设置回调接口,可用于在使用BootstrapRegistry之前初始化它。从springboot2.4开始有
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
// 6、设置应用上线文初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 7、设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 8、推断主入口应用类
this.mainApplicationClass = deduceMainApplicationClass();
}
可知这个构造器类的初始化包括8个过程。
前三个过程是加载资源,主要分析之后几个过程
this.webApplicationType = WebApplicationType.deduceFromClasspath();
来看下 deduceWebApplicationType 方法和相关的源码:
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext"
};
public enum WebApplicationType {
/**
* 非 WEB 项目
*/
NONE,
/**
* SERVLET WEB 项目
*/
SERVLET,
/**
* 响应式 WEB 项目
*/
REACTIVE
}
5、设置回调接口,可用于在使用BootstrapRegistry之前初始化它。从springboot2.4开始有
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
看下getSpringFactoriesInstances方法,这个方法就是通过工厂模式获取类
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;
}
@SuppressWarnings("unchecked")
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;
}
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
ApplicationContextInitializer 的作用是什么?源码如下。
public interface ApplicationContextInitializer {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
用来初始化指定的 Spring 应用上下文,如注册属性资源、激活 Profiles 等。
7、 设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
ApplicationListener 的作用是什么?源码如下。
@FunctionalInterface
public interface ApplicationListener extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
看源码,这个接口继承了 JDK 的 java.util.EventListener 接口,实现了观察者模式,它一般用来定义感兴趣的事件类型,事件类型限定于 ApplicationEvent的子类,这同样继承了 JDK 的 java.util.EventObject 接口。
设置监听器和设置初始化器调用的方法是一样的,只是传入的类型不一样,设置监听器的接口类型为: getSpringFactoriesInstances,对应的 spring-boot-autoconfigure-2.4.3.RELEASE.jar!/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,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
可以看出有好几个监听器
8.推断主入口应用类
his.mainApplicationClass = 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 的栈帧,从来得到入口类的名字再返回该类。
总结
源码分析内容有点多,也很麻烦,本章暂时分析到 SpringApplication 构造方法的初始化流程,下章再继续分析其 run 方法