我们经常看到在SpringBoot中会用到SpringApplication.run来启动我们的SpringBoot应用,那么接下来我们就好好了解一下SpringApplication这个类。其实SpringApplication分为两个阶段,一个是准备阶段,一个是运行阶段。
那么什么是准备阶段什么是运行阶段呢?这里我还是通过源码来说明定义,我们通过源码可以看到这么一样东西,在SpringApplication.run的时候,我们一定会看到这个调用方法:
public static ConfigurableApplicationContext run(Class>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
这个方法在SpringApplication这个类中,那么在new的时候我们定义为准备阶段,run的时候我们定义为运行阶段。
那么我们看看准备阶段做了一些什么样的事情呢?
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//1.配置 Spring Boot Bean 源
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//2.推断 Web 应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//3.加载应用上下文初始器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//4.加载应用事件监听器( ApplicationListener )
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//5.推断引导类(Main Class)
this.mainApplicationClass = deduceMainApplicationClass();
}
上面这段代码是构造函数里面的关键方法,我们可以归结这几个步骤:
1.配置 Spring Boot Bean 源
2.推断 Web 应用类型
3.加载应用上下文初始器
4.加载应用事件监听器( ApplicationListener )
5.推断引导类(Main Class)
这样讲肯定有些抽象,不着急,一个一个的讲清楚是什么意思。首先我们来讲解第一步,配置SpringBoot源,那么什么是SpringBoot源-------Java 配置 Class 或 XML 上下文配置文件集合,用于 Spring Boot BeanDefinitionLoader 读取 ,并且将配置源解析加载为Spring Bean 定义。
那么什么是Java配置Class呢----用于 Spring 注解驱动中 Java 配置类,大多数情况是 Spring 模式注解所标注的类,如 @Configuration 。
什么又是XML上下文配置文件呢-----用于 Spring 传统配置驱动中的 XML 文件。
那么源可以有多少个呢------一个或多个以上。
我们在SpringApplication.run的时候,习惯性的就是把main函数所在的类当做run方法的参数,必须要这样做吗?答案是否定的,我们来看一段代码
public class SpringApplicationBootstrap {
public static void main(String[] args) {
// SpringApplication.run(ApplicationConfiguration.class,args);
Set sources = new HashSet();
// 配置Class 名称
sources.add(ApplicationConfiguration.class.getName());
SpringApplication springApplication = new SpringApplication();
springApplication.setSources(sources);
springApplication.run(args);
}
@SpringBootApplication
public static class ApplicationConfiguration {
}
}
这里我们把SpringApplicationBootstrap这个引导类的@SpringBootApplication注解拿掉了,放到了静态内部类上,然后SpringApplication.run使用的就是ApplicationConfiguration而不是ApplicationConfiguration.class,照常运行,如果说用ApplicationConfiguration.class一定会报错。为什么,这就是我第一步所说的,要找源,@SpringBootApplication其实里面有@Configuration,所以ApplicationConfiguration是一个Bean源,可以被springApplication.setSources(sources);为什么是一个HashSet,这是因为Bean源可以有多个。
接下来讲解一下第二步,推断应用类型是什么。为什么我们的SpringBoot启动的时候,有时候会通过内嵌tomcat启动,有时候没有,此外2.0之后引入了webflux还多了一种web应用类型:reactive。所以我们还是要通过源码来看看SpringBoot是怎么推断应用类型的。
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;
}
其实就是看看类路径下有没有相关的.class文件来判断,我们知道reactive和servlet是不能共存的,所以对于应用类型是否是reactive非常饿苛刻,除了拥有org.springframework.web.reactive.DispatcherHandler类之外,类路径下不准有org.springframework.web.servlet.DispatcherServlet等类,才能认为是reactive类型,否则即使你引入了reative的相关包,也不会是reative类型。如果说连servlet类也没了,那么就是不同的SpringBoot应用,也就是说不是web类型。否则就是Servlet类型。总结如下:
根据当前应用 ClassPath 中是否存在相关实现类来推断 Web 应用的类型,包括:
Web Reactive: WebApplicationType.REACTIVE
Web Servlet: WebApplicationType.SERVLET
非 Web: WebApplicationType.NONE
第三步:加载应用上下文初始器
注意这里的措辞,是上下文初始器,不是上下文。上下文初始器会对上下文做一些初始化操作。什么是上下文----ApplicationContext、ConfigurableApplicationContext,这就是上下文,装了一堆Bean的东东。。。
怎么加载这个初始器的呢?
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.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;
}
从jar包或者文件系统中的META-INF的spring.factories下面去装载ApplicationContextInitializer类,我们可以证明我们的猜想,在你自己的SpringBoot工程的META-INF下,建立一个spring.factories
org.springframework.context.ApplicationContextInitializer=\
com.imooc.diveinspringboot.context.AfterHelloWorldApplicationContextInitializer,\
com.imooc.diveinspringboot.context.HelloWorldApplicationContextInitializer
然后加载我们自定义的两个上下文。
具体的实现如下:
public class AfterHelloWorldApplicationContextInitializer implements ApplicationContextInitializer, Ordered {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("After application.id = " + applicationContext.getId());
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationContextInitializer
implements ApplicationContextInitializer {
@Override
public void initialize(C applicationContext) {
System.out.println("ConfigurableApplicationContext.id = "+ applicationContext.getId());
}
}
两个上下文一个实现了优先级接口,一个使用了注解的形式标注了优先级。有优先级是因为在SpringBoot加载他们的时候会根据他们的优先级顺序做一些相关操作,包括我后面要讲的监听器,也是会有优先级的。那么是不是一定要标注优先级呢?答案是否定的,如果没有优先级,那么SpringBoot会按照自己的方式去解析,不过对于一些场景,必须要制定优先级,因为有的监听器或者上下文必须必其他的监听器或者上下文的优先级高。怎么排序的,看下面截图,不赘述了
这里面initialize方法是比较关键的,虽然我们这里只用了简单的打印,不过我们可以看看springBoot内置的初始器都干了什么,为了不占篇幅,就看一个SharedMetadataReaderFactoryContextInitializer
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addBeanFactoryPostProcessor(
new CachingMetadataReaderFactoryPostProcessor());
}
对上下文加了一些BeanFactoryPostProcessor,这也是我开篇说的,上下文初始器是对上下文进行初始化和赋值的,不是上下文!
第四步:加载应用事件监听器( ApplicationListener )
利用 Spring 工厂加载机制,实例化 ApplicationListener 实现类,并排序对象集合。其实和上面的上下文初始器很像。不过有几个点要注意一下,这里说的是监听器,不是运行监听器,运行监听器在下文的运行阶段会讲。两这个有差别。具体什么差别我会在下文点出来,这个东西也耗了我很长的时间去理解。这里我们要知道ApplicationListener最重要的是什么东西:
@Override
public void onApplicationEvent(SpringApplicationEvent event) {
if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
&& event instanceof ApplicationStartingEvent
&& preinitializationStarted.compareAndSet(false, true)) {
performPreinitialization();
}
if ((event instanceof ApplicationReadyEvent
|| event instanceof ApplicationFailedEvent)
&& preinitializationStarted.get()) {
try {
preinitializationComplete.await();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
我们看一下SpringBoot中的一个监听器实现,最主要的就是这个OnApplicationEvent方法,这个方法会在运行阶段,被运行监听器回调!
第五步:推断引导类(Main Class)
根据 Main 线程执行堆栈判断实际的引导类,什么意思?
首先要搞明白,为什么要推断实际的引导类,这是因为SpringApplication.run可以多个源,不能直接从这个run方法里面去找,第一步的例子已经说清楚了,找到了这个引导类后,对日志信息有相关帮助。
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;
}
源码里面巧妙的利用了RuntimeException的栈信息,一层一层找到main方法所在的类,然后巧妙的拿到了引导类。这种方法非常有干货性。
OK,准备阶段我讲完了,接下来说一下运行阶段,这个就是大头了!
SpringApplication.run中要做的工作:
加载:SpringApplication运行监听器(SpringApplicationRunListeners)采用了组合模式
运行:SpringApplication运行监听器
监听:SpringBoot事件、Spring事件
创建:应用上下文、Environment
失败:故障分析报告
回调:CommandLineRunner、ApplicationRunner
依然抽象,依然一一解析。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
首先加载SpringApplicationRunListeners,SpringApplicationRunListeners listeners = getRunListeners(args);就是干这件事情的。SpringApplicationRunListeners只有一个实现类,这个实现类是org.springframework.boot.context.event.EventPublishingRunListener,为什么这么说,因为spring-boot-xxx.jar里面就只有一个runListener,所以就它一个运行监听器!不含糊,不吹牛。
利用 Spring 工厂加载机制,读取 SpringApplicationRunListener 对象集合,并且封装到组合类SpringApplicationRunListeners。结果就只有这么一个。。。。。
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
他的构造方法才是关键,把整个准备阶段的监听器抓过来了,然后存起来了,存起来干嘛?就是要在下面发送事件,然后调用他们的onApplicationEvent方法!!!
运行监听器装载完了后,接下来就是运行!!关键的来了。listeners.starting()这个方法调用,
@Override
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
广播了一个事件,这个事件经过层层调用,你会看到这么一个东西:
@SuppressWarnings({"unchecked", "rawtypes"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
看到了那个可爱的onApplicationEvent了么~~~就是这个运行监听器干的事情!!
就大致讲到这里了,其实SpringApplicationRunListener 监听多个运行状态方法,总结如下:
监听方法 | 阶段说明 | Spring Boot 起始版本 |
starting() | Spring 应用刚启动 | 1.0 |
environmentPrepared(ConfigurableEnvironment) | ConfigurableEnvironment 准备妥当,允许将其调整 | 1.0 |
contextPrepared(ConfigurableApplicationContext) | ConfigurableApplicationContext 准备妥当,允许将其调整 | 1.0 |
contextLoaded(ConfigurableApplicationContext) | ConfigurableApplicationContext 已装载,但仍未启动 | 1.0 |
started(ConfigurableApplicationContext) | ConfigurableApplicationContext 已启动,此时 Spring Bean 已初始化完成 | 2.0 |
running(ConfigurableApplicationContext) | Spring 应用正在运行 | 2.0 |
failed(ConfigurableApplicationContext,Throwable) | Spring 应用运行失败 | 2.0 |
EventPublishingRunListener 监听方法与 Spring Boot 事件对应关系:
监听方法 | Spring Boot 事件 | Spring Boot 起始版本 |
starting() | ApplicationStartingEvent | 1.5 |
environmentPrepared(ConfigurableEnvironment) | ApplicationEnvironmentPreparedEvent | 1.0 |
contextPrepared(ConfigurableApplicationContext) | ||
contextLoaded(ConfigurableApplicationContext) | ApplicationPreparedEvent | 1.0 |
started(ConfigurableApplicationContext) | ApplicationStartedEvent | 2.0 |
running(ConfigurableApplicationContext) | ApplicationReadyEvent | 2.0 |
failed(ConfigurableApplicationContext,Throwable) | ApplicationFailedEvent | 1.0 |
运行阶段还有:
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
创建上下文然后又搞一堆事件。。。让可怜的准备的监听器做去吧~~~
除了创建上下文,还创建了environment:
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
这个东西就是存了配置文件中的一些配置项信息!非常重要,创建好了后又是
listeners.environmentPrepared(environment);
好了SpringApplication的内容就讲到这里了,欢迎大家指正,下一篇文章就是MVC的相关内容了~~~