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);
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值