工作如何繁忙,生活如何糟心,至少求知的这一刻是我的…
前言
基于Spring Boot 2.3.1。
从入口开始拆轮
拆轮子可以从它的入口方法开始拆起,先通读再细致地看。Spring Boot的入口已经是老生常谈了:
@SpringBootApplication
public class DemoApplicationMain {
public static void main(String[] args) {
SpringApplication.run(DemoApplicationMain.class, args);
}
}
从SpringApplication#run方法进去,看它做了什么:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
先看看SpringApplication(primarySources)的实现,该方法的实现实际调用了其重载方法。
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
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();
// 加载Spring自身的初始化工厂方法
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 加载应用监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 获取启动main方法的所在类
this.mainApplicationClass = deduceMainApplicationClass();
}
可以看到,SpringApplication的构造器做了以下操作,通读即可,如需深入了解可自行查看源码,多打断点:
- 推测web应用环境:通过反射获取classpath中的web应用环境,没有ClassNotFound,从而推测出是否为web应用;
- 加载Spring自身的初始化工厂方法:读取spring-boot工程的spring.factories资源文件,从中获取到ApplicationContextInitalizer.class的各个实现类;
- 加载应用监听器:同上读取,从中获取到ApplicationListener.class的各个实现类;
- 获取启动main方法的所在类:通过主动抛出异常,遍历其异常栈直到异常栈的方法名为main,获取此时的类名。
接着看run方法做了什么:
public ConfigurableApplicationContext run(String... args) {
// stopWatch,Spring实现的任务监控,在引入Spring的工程中需要监控某些方法运行时间可以用它,没必要自己实现了。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// Spring Boot启动错误回调报告器。
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置java.awt.headless属性为true,如果当前运行设备缺失显示器、键鼠等外设,又需要相应外设的运算能力,那么需要配置该属性为true。
// 比如服务器基本没有显示器,如果需要输出终端日志信息,那么就要开启它。
configureHeadlessProperty();
// 读取spring.factories,获取SpringApplicationRunListener.class的实现类,并一一实例化,目前仅一个实现类。
// 即EventPublishingRunListener,用于处理Spring刷新上下文之前的事件,见下边的刷新上下文refreshContext(context);。
// 这里可以打开思路,可以在Spring Boot真正启动前,通过发布一些事件,来做一些前处理。
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 缓存入参并提供对入参的一定解析能力。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境,包括web环境、解析入参、确认生效的profiles。
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 确认入参中是否配置了spring.beaninfo.ignore, 默认不配置。详见参考文献[1]。
configureIgnoreBeanInfo(environment);
/**
* 打印Banner,一般打印以下默认Banner。
* . ____ _ __ _ _
* /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
* ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
* \\/ ___)| |_)| | | | | || (_| | ) ) ) )
* ' |____| .__|_| |_|_| |_\__, | / / / /
* =========|_|==============|___/=/_/_/_/
* :: Spring Boot :: (v2.3.1.RELEASE)
**/
Banner printedBanner = printBanner(environment);
// 创建应用上下文,如包含web应用上下文,那么此时会通过反射创建;如果没有包含web应用上下文,那么就创建默认的上下文。
context = createApplicationContext();
// 读取spring.factories中的配置,获取SpringBootExceptionReporter.class的实现类
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新Spring上下文
refreshContext(context);、
// 无实现,有需要可继承SpringApplication.class覆盖该方法。
afterRefresh(context, applicationArguments);
// 停止监控,输出如:
// c.l.blog.NuvoleBiancheApplicationMain : Started DemoApplicationMain in 13.35 seconds (JVM running for 26.514)
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 向所有监听器发布ApplicationStartedEvent。
listeners.started(context);
// 将ApplicationRunner和CommandLineRunner一一运行起来。
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 向exceptionReporters报告异常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 向所有监听器发布ApplicationReadyEvent。
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
解析prepareContext见这篇;
解析refreshContext见这篇。
参考文献
[1] https://docs.spring.io/spring-framework/docs/4.2.0.RELEASE/javadoc-api/org/springframework/beans/CachedIntrospectionResults.html