1. 環境搭建
代碼已經上傳至 https://github.com/masteryourself-tutorial/tutorial-spring ,詳見
tutorial-spring-boot-core/tutorial-spring-boot-listener
工程
1.1 配置文件
1. META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\
pers.masteryourself.tutorial.spring.boot.listener.extend.MyApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=\
pers.masteryourself.tutorial.spring.boot.listener.extend.MySpringApplicationRunListener
1.2 核心代碼
1. MySpringApplicationRunListener
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
/**
* 必須要有這個構造函數
*
* @param application
* @param args
*/
public MySpringApplicationRunListener(SpringApplication application, String[] args) {
}
@Override
public void starting() {
System.out.println("SpringApplicationRunListener...starting...");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("SpringApplicationRunListener...environmentPrepared...");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...contextPrepared...");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...contextLoaded...");
}
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...started...");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener...running...");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("SpringApplicationRunListener...failed...");
}
}
2. MyApplicationContextInitializer
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("MyApplicationContextInitializer...initialize..." + applicationContext);
}
}
3. MyApplicationRunner
必須要放到容器中
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("MyApplicationRunner...run....");
}
}
4. MyCommandLineRunner
必須要放到容器中
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner...run..." + Arrays.asList(args));
}
}
2. 源碼解析
2.1 流程圖
2.2 核心代碼剖析
1. org.springframework.boot.WebApplicationType#deduceFromClasspath
這個環境在之後的 getOrCreateEnvironment()
和 createApplicationContext()
方法均有用到
static WebApplicationType deduceFromClasspath() {
// 如果當前 ClassLoader 能加載到 "org.springframework.web.reactive.DispatcherHandler",那麼就返回 REACTIVE 環境
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// 如果加載不到 "javax.servlet.Servlet" 和 "org.springframework.web.context.ConfigurableWebApplicationContext"
// 那麼就認爲是 NONE,即非 web 環境
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 返回 SERVLET 環境
return WebApplicationType.SERVLET;
}
2. org.springframework.boot.SpringApplication#deduceMainApplicationClass
查找 main()
方法所在的類,這個方法很靈性,值得參考
private Class<?> deduceMainApplicationClass() {
try {
// 在當前代碼中 new RuntimeException(),然後根據棧信息查找 main 方法所在的類
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;
}
3. org.springframework.boot.SpringApplication#createApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 之前已經判斷過應用程序類型,根據類型選擇是 Web IOC 容器還是普通的 IOC 容器
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);
}
4. org.springframework.boot.SpringApplication#callRunners
調用 ApplicationRunner
和 CommandLineRunner
組件的 run()
方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 注意這兩個方法都是 Spring IOC 容器中獲取的,所以使用它們只需要注入即可
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
// 調用 ApplicationRunner 的 run 方法
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
// 調用 CommandLineRunner 的 run 方法
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}