Spring Boot 2.0之理解SpringApplication
-
-
SpringApplication的基本使用
-
SpringApplication 准备阶段
-
SpringApplication 运行阶段
-
刷新上下文
-
SpringApplication的基本使用
SpringApplication 运行
SpringApplication.run(SpringApplicationBootstrap.class,args);
自定义 SpringApplication
通过 SpringApplication API 调整
SpringApplication springApplication = new SpringApplication(SpringApplicationBootstrap.class);
springApplication.setBannerMode(Banner.Mode.CONSOLE);
springApplication.setWebApplicationType(WebApplicationType.NONE);
springApplication.setAdditionalProfiles("prod");
springApplication.setHeadless(true);
通过 SpringApplicationBuilder API 调整
流式方法处理,更优雅
new SpringApplicationBuilder(SpringApplicationBootstrap.class)
.bannerMode(Banner.Mode.CONSOLE)
.web(WebApplicationType.NONE)
.profiles("prod")
.headless(true)
.run(args);
SpringApplication 准备阶段
配置 Spring Boot Bean 源
Java 配置 Class 或 XML 上下文配置文件集合,用于 Spring Boot
BeanDefinitionLoader
读取并且将配置源解析加载为Spring Bean 定义
Java 配置 Class
用于 Spring 注解驱动中 Java 配置类,大多数情况是 Spring 模式注解所标注的类,如
@Configuration
。在spring boot中是
@SpringBootApplication
@SpringBootApplication
—>
@SpringBootConfiguration
—>
@Configuration
/**
* {@link SpringApplication}
* Created by Yuk on 2019/3/9.
*/
@SpringBootApplication
public class SpringApplicationBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringApplicationBootstrap.class,args);
}
}
@SpringBootApplication
public class SpringApplicationBootstrap {
public static void main(String[] args) {
//SpringApplication.run(SpringApplicationBootstrap.class,args);
Set sources = new HashSet();
sources.add(SpringApplicationBootstrap.class.getName());
SpringApplication application = new SpringApplication();
application.setSources(new HashSet<>(sources));
application.run(args);
}
}
这两种方式都可以正常启动,重点看
application.setSources(new HashSet<>(sources));
/**
* Set additional sources that will be used to create an ApplicationContext. A source
* can be: a class name, package name, or an XML resource location.
* <p>
* Sources set here will be used in addition to any primary sources set in the
* constructor.
* @param sources the application sources to set
* @see #SpringApplication(Class...)
* @see #getAllSources()
*/
public void setSources(Set<String> sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<>(sources);
}
看英文注释可以知道
A source can be: a class name, package name, or an XML resource location.
这里Set集合的sources可以是className(但必须是加了
@Configuration
),可以是包名,也可以是spring的配置文件
推断 Web 应用类型
根据当前应用 ClassPath 中是否存在相关实现类来推断 Web 应用的类型,包括:
-
Web Reactive
: WebApplicationType.REACTIVE -
Web Servlet
: WebApplicationType.SERVLET -
非 Web
: WebApplicationType.NONE
参考方法:
spring boot 2.0.7版本
this.webApplicationType = WebApplicationType.deduceFromClasspath();
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;
}
推断引导类(Main Class)
这里可能会有个疑问,引导类不就是Main Class吗?
注意,这样写确实是的
@SpringBootApplication
public class SpringApplicationBootstrap {
public static void main(String[] args) {
SpringApplication.run(ApplicationConfiguration.class,args);
}
}
但是这样写,引导类就不是当前运行main方法的class了
public class SpringApplicationBootstrap {
public static void main(String[] args) {
SpringApplication.run(ApplicationConfiguration.class,args);
}
@SpringBootApplication
public static class ApplicationConfiguration{
}
}
参考方法
:
org.springframework.boot.SpringApplication#deduceMainApplicationClass
抛出异常获取堆栈,然后判断堆栈的方法是否为main,从而获取类名,反射得到引导类
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;
}
我们可以断点看一下
加载应用上下文初始器 ( ApplicationContextInitializer )
初始化时可以拿到上下文
ConfigurableApplicationContext
,做一些其他事情。
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 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;
}
跟踪这个方法,最后发现会去
META-INF/spring.factories
中查找。利用 Spring 工厂加载机制,实例化
ApplicationContextInitializer
的实现类,并排序对象集合。
可以看到spring boot默认有两个实现类实现了
ApplicationContextInitializer
并且,
AnnotationAwareOrderComparator.sort(instances);
对实现类进行排序,看截图可知。
ApplicationContextInitializer
的实现类定义排序有三种方式:
1.实现Ordered接口
2.注解Order
3.注解priority
自定义上下文初始器
- 实现Ordered接口定义优先级
public class AfterApplicationContextInitiazer implements ApplicationContextInitializer,Ordered{
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; // 最低优先级
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("After ApplicationContext.id="+applicationContext.getId());
}
}
- @Order注解定义优先级
@Order(Ordered.HIGHEST_PRECEDENCE)// 最高优先级
public class HelloWorldApplicationContextInitiazer implements ApplicationContextInitializer{
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("helloWorld ApplicationContext.id="+applicationContext.getId());
}
}
spring.factories
# Initializers
org.springframework.context.ApplicationContextInitializer=\
com.imooc.diveinspringboot.context.AfterApplicationContextInitiazer,\
com.imooc.diveinspringboot.context.HelloWorldApplicationContextInitiazer
测试
控制台打印
helloWorld ApplicationContext.id=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@327b636c
After ApplicationContext.id=application
加载应用事件监听器( ApplicationListener )
利用 Spring 工厂加载机制,实例化
ApplicationListener
的实现类,并排序对象集合,与加载应用上下文初始器的机制一样。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
自定义事件监听器
这里只是自定义监听器,监听的是spring默认的事件
public class AfterApplicationListener implements ApplicationListener<ContextRefreshedEvent>,Ordered {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("After ApplicationListener :"+event.getApplicationContext().getId()
+",timestamp:"+event.getTimestamp());
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("HelloWorld ApplicationListener :"+event.getApplicationContext().getId()
+",timestamp:"+event.getTimestamp());
}
}
spring.factories
org.springframework.context.ApplicationListener=\
com.imooc.diveinspringboot.listen.AfterApplicationListener,\
com.imooc.diveinspringboot.listen.HelloWorldApplicationListener
测试
控制台打印
HelloWorld ApplicationListener :application,timestamp:1552139922079
After ApplicationListener :application,timestamp:1552139922079
spring的事件监听特别好用,可以多个监听一个事件,是
观察者模式
的一种体现。这里只是自定义了监听器,其实还可以自定义事件,到时候在讲spring源码的时候具体会写一篇文章。
SpringApplication 运行阶段
加载 SpringApplication 运行监听器( SpringApplicationRunListeners )
利用 Spring 工厂加载机制,读取
SpringApplicationRunListener
对象集合,并且封装到组合类
SpringApplicationRunListeners
在
SpringApplication
最终的
run
方法中
SpringApplicationRunListeners listeners = getRunListeners(args);
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
其中
getSpringFactoriesInstances
方法,上面也讲到过,就是根据类型去
spring.factories
中获取相对应的类。这里可以看到获取的是实现了
SpringApplicationRunListener
接口的实现类
运行 SpringApplication 运行监听器( SpringApplicationRunListeners )
SpringApplicationRunListener
有7个方法,对应容器运行阶段的7种状态,可以自定义实现不同方法。比如容器开始时要做什么,启动时要做什么等等。。。
public interface SpringApplicationRunListener {
void starting();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void started(ConfigurableApplicationContext context);
void running(ConfigurableApplicationContext context);
void failed(ConfigurableApplicationContext context, Throwable exception);
}
监听方法 | 阶段说明 | Spring Boot 起始版本 |
---|---|---|
starting() | Spring 应用刚启动 | 1.0 |
environmentPrepared(ConfigurableEnvironment) | ConfigurableEnvironment 准备妥当,允许将其调整 | 1.5 |
contextPrepared(ConfigurableApplicationContext) | ConfigurableApplicationContext 准备妥当,允许将其调整 | 1.0 |
contextLoaded(ConfigurableApplicationContext context) | ConfigurableApplicationContext 已装载,但仍未启动 | 1.0 |
started(ConfigurableApplicationContext context) | ConfigurableApplicationContext 已启动,此时 Spring Bean 已初始化完成 | 2.0 |
running(ConfigurableApplicationContext) | Spring 应用正在运行 | 2.0 |
failed(ConfigurableApplicationContext,Throwable) | Spring 应用运行失败 | 1.0 |
监听 Spring Boot 事件 / Spring 事件
Spring Boot 通过
SpringApplicationRunListener
的实现类
EventPublishingRunListener
利用 Spring Framework 事件API ,广播 Spring Boot 事件。
在
listeners.starting();
时,发布了一个
ApplicationStartingEvent
事件,如果自定义监听器监听了该事件,则会在这里触发监听器。
EventPublishingRunListener.java
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
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 |
Spring Framework 事件/监听器编程模型
-
Spring 应用事件
- 普通应用事件: ApplicationEvent
- 应用上下文事件: ApplicationContextEvent
-
Spring 应用监听器
- 接口编程模型: ApplicationListener
-
注解编程模型: @EventListener
@EventListener注解的处理
-
Spring 应用事广播器
- 接口: ApplicationEventMulticaster
- 实现类: SimpleApplicationEventMulticaster
- 执行模式:同步或异步
事件监听默认为同步,异步就是在
onApplicationEvent
方法上加
@Async
@Async
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("After ApplicationListener :"+event.getApplicationContext().getId()
+",timestamp:"+event.getTimestamp());
}
自定义 SpringApplication 运行监听器
public class HelloApplicationRunListener implements SpringApplicationRunListener{
@Override
public void starting() {
System.out.println("Hello ApplicationRunListener is starting.");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
}
@Override
public void started(ConfigurableApplicationContext context) {
}
@Override
public void running(ConfigurableApplicationContext context) {
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
添加到spring.factories中
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.imooc.diveinspringboot.run.HelloApplicationRunListener
运行报错:
Caused by: java.lang.NoSuchMethodException: com.imooc.diveinspringboot.run.HelloApplicationRunListener.<init>(org.springframework.boot.SpringApplication, [Ljava.lang.String;) at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:434) ... 6 more
构造方法报错,我们看一下spring boot的运行运行监听器
EventPublishingRunListener
怎么实现的,它有一个带参数的构造函数
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);
}
}
我么仿照着写一个一样的构造函数,启动正常,打印出了
Hello ApplicationRunListener is starting.
为什么要加一个这样的构造函数,是因为创建运行监听器实例的时候,通过带参数的构造函数实例化的
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<?> 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;
}
创建 Spring 应用上下文( ConfigurableApplicationContext )
根据准备阶段的推断 Web 应用类型创建对应的
ConfigurableApplicationContext
实例:
- Web Reactive: AnnotationConfigReactiveWebServerApplicationContext
- Web Servlet: AnnotationConfigServletWebServerApplicationContext
- 非 Web: AnnotationConfigApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
创建 Environment
根据准备阶段的推断 Web 应用类型创建对应的 ConfigurableEnvironment 实例:
- Web Reactive: StandardReactiveWebEnvironment
- Web Servlet: StandardServletEnvironment
- 非 Web: StandardEnvironment
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
刷新上下文
run
方法中会执行
refreshContext(context);
,最后执行的就是
AbstractApplicationContext
中的
refresh()
方法
spring加载流程之refresh()