一步一步分析SpringBoot启动源码(一)

分享 未结 精帖 5 16538
KSE-music
KSE-music LV4 2018-07-14
悬赏:20积分
零、背景
一个Spring Boot应用启动方式有很多种,这里我讲解的是官方推荐的一种,也是绝大多数人所选择的一种启动方式讲解。SpringApplication作为驱动类,源码只有1190行,而我们的入口是其中的一个静态方法run,好废话不多说,进入我们今天的正题。
一、最简单的一个Spring Boot应用(以下简称boot应用)入口类如下图所示:


1.run方法的第一个参数是加载的是我们指定源,第二个是应用参数(可变长类型参数)一般通过java main方法输入进来,返回的是ConfigurableApplicationContext。进入run方法,发现其调用 的是一个重载的run的方法只不过它接受的是多个指定的源。这个run方法里创建的一个SpringApplication对象并将指定源传进去。在构造器里调用了私有初始化方法initialize。



2.在initialize方法里,首先判断是否有指定源,有则添加到LinkedHashSet中。其次是推断是否是web应用,正如Spring Boot的核心原理:约定优于配置一样,只要在classpath下同时存在类javax.servlet.Servlet和类org.springframework.web.context.ConfigurableWebApplicationContext则是web应用。接下来是设置ApplicationContextInitializer和ApplicationListener,原理一样。我们以设置ApplicationContextInitializer为例,getSpringFactoriesInstances方法的作用就是在classpath*下META-INF/spring.factories配置文件中查找key为org.springframework.context.ApplicationContextInitializer的实现类。



然后循环通过反射创建实例对象并通过注解比较器排序。最后将这些ApplicationContextInitializer一并添加到boot应用的成员变量initializers中。获得到ApplicationListener如下:



其中ConfigFileApplicationListener是之后需要讲解的重点对象。在initialize方法最后执行的是deduceMainApplicationClass,看方法名就知道是推断主应用类,这个方法比较有意思,因为它是通过构造一个运行时异常,再从栈中查找方法名为main的class,即TestApplication.class。在这一些列的准备工作之后,接一下来又调用了一个run方法,此run方法不同于另两个,因为它这里是要运行boot应用并创建Spring应用上下文(ApplicationContext,IOC容器的高富帅接口)以及刷新工作,

从类图上我们可以看到它间接继承了bean的管理容器BeanFactory和资源加载器ResourceLoader。

3.真正开始启动的run方法


从图上看整个应用启动结束也没多少行代码啊,但是真的每个方法里面都看完是真tm的不容易啊,而这里面的”罪魁祸首”就是refreshContext里面的refresh方法即上下文刷新,这个方法我会单拎出来讲解,其实它不属于boot的一部分,而是整个spring的核心即解析bean定义描述以及实例化bean。
下面的代码中加解释的方法比较简单,在接下来的分析过程就不会单独讲解了。
	public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); //创建一个马表,用于记录应用启动时间
stopWatch.start();//启动
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
Banner printedBanner = printBanner(environment);//打印控制台Spring Boot banner以及版本号
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop(); //结束计时
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);//打印应用启动时间,即我们经常看到的Started TestApplication in 1.223 seconds (JVM running for 1.761)
}
return context;//返回可配置的应用上下文
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
回帖