07.Spring Boot 之啓動原理

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 流程圖

Spring Boot 啓動流程圖

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

調用 ApplicationRunnerCommandLineRunner 組件的 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);
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章