为更好的理解SpringBoot的启动原理,在分析之前我们可以先回顾一下关于SpringBoot的相关知识。
什么是SpringBoot?SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程,使用“习惯优于配置”的理念,整合了Spring平台和一些第三方类库,只需要很少的配置就可以快速开始你的项目。
为什么要使用SpringBoot?SpringBoot自身有很多优点,比如快速构建项目、内嵌web服务器、自动配置、自动管理依赖、自带应用监控等等。
SpringBoot的启动过程如图:
一、简述
根据启动过程流程图,可以看出SpringBoot整个启动流程可分为两个核心步骤,分别是:
- 初始化SpringApplication对象。
- 执行该对象的run方法。
因此,SpringBoot的启动都是通过@SpringBootApplication注解和SpringApplication.run方法来实现的。
springboot的标准启动入口
@SpringBootApplication
public class GraduationProjectApplication {
public static void main(String[] args) {
SpringApplication.run(GraduationProjectApplication.class, args);
}
}
二、源码分析
1.@SpringBootApplication注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { //这两个排除过滤器TypeExcludeFilter和AutoConfigurationExcludeFilter不知道用来干什么的
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//等同于EnableAutoConfiguration注解的exclude属性
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
//等同于EnableAutoConfiguration注解的excludeName属性
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
//等同于ComponentScan注解的basePackages属性
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
//等同于ComponentScan注解的basePackageClasses属性
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
可以看到@SpringBootApplication注解实际上是SpringBoot提供的一个复合注解,由以下三个注解组成:
@SpringBootConfiguration:来源于@Configuration,二者功能都是将当前类标注为配置类,并将当前类里以@Bean 注解标记的方法的实例注入到srping容器中。
@EnableAutoConfiguration:启用自动配置其可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前 IOC 容器之中。
@ComponentScan:对应于XML配置形式中的context:component-scan,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解大致包括:@Controller @Entity @Component @Service @Repository。
因此,@SpringBootApplication注解主要作为一个配置类,能够触发包扫描和自动配置的逻辑,从而使得SpringBoot的相关bean被注册进Spring容器。
2.创建SpringBootApplication对象
SpringApplication类的run方法,这个方法就做了2件事:一是创建SpringApplication对象,二是启动SpringApplication。
SpringApplication构造器源码如下:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
//断言primarySources不能为null,如果为null,抛出异常提示
Assert.notNull(primarySources, "PrimarySources must not be null");
//启动类传入的Class
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//判断当前项目类型,有三种:NONE、SERVLET、REACTIVE
this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));
//设置ApplicationContextInitializer
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//判断主类,初始化入口类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
//判断主类
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException var6) {
}
return null;
}
在构造器里主要就做了2件事,1是设置初始化器,2是设置监听器。
设置初始化器的源码如下:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return this.getSpringFactoriesInstances(type, new Class[0]);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
//从类路径的META-INF处读取相应配置文件spring.factories,然后进行遍历,读取配置文件中Key(type)对应的value
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//将names的对象实例化
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
ApplicationContextInitializer.class从类路径的META-INF处读取相应配置文件spring.factories并实例化对应Initializer。
设置监听器:和设置初始化器一个样,都是通过getSpringFactoriesInstances函数实例化监听器。
创建了SpringApplication实例之后,就完成了SpringApplication类的初始化工作。
3.SpringApplication.run()方法
得到SpringApplication实例后,接下来就调用实例方法run()。
run方法源码如下:
public ConfigurableApplicationContext run(String... args) {
//创建计时器
StopWatch stopWatch = new StopWatch();
//开始计时
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
//定义上下文对象
ConfigurableApplicationContext context = null;
//Headless模式设置
this.configureHeadlessProperty();
//加载SpringApplicationRunListeners监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//发送ApplicationStartingEvent事件
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//封装ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//配置环境模块
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//根据环境信息配置要忽略的bean信息
this.configureIgnoreBeanInfo(environment);
//打印Banner标志
Banner printedBanner = this.printBanner(environment);
//创建ApplicationContext应用上下文
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//ApplicationContext基本属性配置
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文
this.refreshContext(context);
//刷新后的操作,由子类去扩展
this.afterRefresh(context, applicationArguments);
//计时结束
stopWatch.stop();
//打印日志
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//发送ApplicationStartedEvent事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕
listeners.started(context);
//查找容器中注册有CommandLineRunner或者ApplicationRunner的bean,遍历并执行run方法
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
//发送ApplicationFailedEvent事件,标志SpringBoot启动失败
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
try {
//发送ApplicationReadyEvent事件,标志SpringApplication已经正在运行,即已经成功启动,可以接收服务请求。
listeners.running(context);
return context;
} catch (Throwable var9) {
//报告异常,但是不发送任何事件
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
首先遍历执行所有通过SpringFactoriesLoader,在当前classpath下的META-INF/spring.factories中查找所有可用的SpringApplicationRunListeners并实例化。调用它们的starting()方法,通知这些监听器SpringBoot应用启动。
创建并配置当前SpringBoot应用将要使用的Environment,包括当前有效的PropertySource以及Profile。
遍历调用所有的SpringApplicationRunListeners的environmentPrepared()的方法,通知这些监听器SpringBoot应用的Environment已经完成初始化。
打印SpringBoot应用的banner,SpringApplication的showBanner属性为true时,如果classpath下存在banner.txt文件,则打印其内容,否则打印默认banner。
根据启动时设置的applicationContextClass和在initialize方法设置的webEnvironment,创建对应的applicationContext。
创建异常解析器,用在启动中发生异常的时候进行异常处理(包括记录日志、释放资源等)。
设置SpringBoot的Environment,注册Spring Bean名称的序列化器BeanNameGenerator,并设置资源加载器ResourceLoader,通过SpringFactoriesLoader加载ApplicationContextInitializer初始化器,调用initialize方法,对创建的ApplicationContext进一步初始化。
调用所有的SpringApplicationRunListeners的contextPrepared方法,通知这些Listener当前ApplicationContext已经创建完毕。
最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。
调用所有的SpringApplicationRunListener的contextLoaded方法,加载准备完毕的ApplicationContext。
调用refreshContext,注册一个关闭Spring容器的钩子ShutdownHook,当程序在停止的时候释放资源(包括:销毁Bean,关闭SpringBean的创建工厂等)。
注: 钩子可以在以下几种场景中被调用:
1)程序正常退出
2)使用System.exit()
3)终端使用Ctrl+C触发的中断
4)系统关闭
5)使用Kill pid命令杀死进程
获取当前所有ApplicationRunner和CommandLineRunner接口的实现类,执行其run方法遍历所有的SpringApplicationRunListener的finished()方法,完成SpringBoot的启动。
总结
SpringBoot看起来只有一个@SpringBootApplication注解和一个run方法,但其实是经过高度封装后的。通过对启动原理的分析也能感受到SpringBoot所具有的优势。例如使用了spring.factories文件来完成自动配置,提高了扩展性。在启动时使用观察者模式,以事件发布的形式通知,降低耦合,易于扩展等等。