Spring Boot项目的启动流程
工作也有两年之余了,做过的Spring Boot项目也蛮多的,但是对他的启动流程之前没有系统的学习以及记录过,而且之前面试也遇到过问Spring Boot的启动流程。那么今天我们就整体的分析一下,Spring Boot的启动流程吧。
Spring Boot优点
在开始之前我们先介绍一下Spring Boot
用我的英语四级水平给大家翻译一下。
这个是Spring Boot官网对它自己的描述
Spring Boot可以很容易地创建独立的、基于Spring的生产级应用程序,您只需“运行”即可。
我们对Spring平台和第三方库持有固执(我怎么感觉是个贬义词?)的看法,所以您可以毫不费力地开始。大多数Spring引导应用程序只需要最少的Spring配置。
这个是Spring Boot官网对它特性的介绍。
特性
- 创建独立的Spring应用程序
- 直接嵌入Tomcat、Jetty或Undertow(不需要部署WAR文件)
- 提供自以为是的“启动器”依赖项来简化构建配置
- 尽可能地自动配置Spring和第三方库
- 提供可用于生产的特性,如度量、运行状况检查和外部化配置
- 完全不需要生成代码,也不需要XML配置
Spring Boot启动过程
我们的Spring Boot项目一般都会有一个启动类(如下),这就是整个应用的入口,只要运行这个main函数就能启动Spring Boot项目。
@SpringBootApplication
public class TrainingCenterPlatformApplication {
public static void main(String[] args) {
SpringApplication.run(TrainingCenterPlatformApplication.class, args);
}
}
接下来我们就对这个类来做一些研究吧!
@SpringBootApplication 注解
通过SpringBootApplication注解的源码我们可以看到,它是由三个注解组成的组合注解(如下),那么我们就对这三个注解各自的功效。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
-
@SpringBootConfiguration:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { ... }
我们可以看到@SpringBootConfiguration 是来源于 @Configuration。所以他的功能与 @Configuration一样,都是将当前类标记为配置类,并且将当前类里以@Bean注解标记的方法返回的实例注入到Spring容器中,实例的名字即方法的名字(也可以在@Bean指定实例的名字)。
-
@EnableAutoConfiguration:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... }
@EnableAutoConfiguration 注解启用自动配置,其可以帮助 Spring Boot 应用将所有符合条件的 @Configuration 配置都加载到当前 IoC 容器之中,可以简要用图形示意如下
我们可以用文字描述一下这个过程:首先Spring Boot会扫描ClassPath下所有的 META-INF/spring.factories 配置文件,然后将spring.factories文件中的 EnableAutoConfiguration对应的配置项通过反射机制实例化为对应标注了 @Configuration 的形式的IoC配置类,然后注入到IoC容器。 -
@ComponentScan:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { ... }
它对应着我们之前基于XML配置的<context:component-scan>,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解包括@Component以及继承了它的子注解。
SpringApplication类
SpringApplication里面封装了一套Spring应用的启动流程,然后这对用户完全透明,因此我们上手Spring Boot时感觉会很简单。
SpringApplication实例的初始化
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断应用的类型:创建的是REACTIVE应用、SERVLET应用、NONE 三种中的某一种
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
//使用SpringFactoriesLoader查找并加载classpath下META-INF/spring.factories文件中所有可用
//的 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//使用SpringFactoriesLoader查找并加载classpath下META-INF/spring.factories文件中的所有可用
//的 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断并设置main方法的定义类
this.mainApplicationClass = deduceMainApplicationClass();
}
我们可以看一下这里面几个比较重要的方法:
-
WebApplicationType.deduceFromClasspath():
static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; }
这个方法是用来判断应用创建的类型,如果我们用的是Spring MVC框架的话这个方法会返回WebApplicationType.SERVLET,如果我们用的是Spring webFlux框架而且项目里没有引入Spring MVC的框架时返回的是WebApplicationType.REACTIVE。
-
getSpringFactoriesInstances(ApplicationContextInitializer.class)、getSpringFactoriesInstances(ApplicationListener.class)):
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
这两个方法其实最后调用的都是这个方法,目的就是为了去获取其接口类类对应的配置类列表。
ApplicationContextInitializer的作用是在Spring容器刷新之前执行的一个回调函数。通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活配置文件等。
ApplicationListener是一个接口,里面只有一个onApplicationEvent方法。如果在上下文中部署一个实现了ApplicationListener接口的bean,那么每当在一个ApplicationEvent发布到 ApplicationContext时,调用ApplicationContext.publishEvent()方法,这个bean得到通知。类似于Oberver设计模式。 -
deduceMainApplicationClass():
推断并设置main方法的定义类。private Class<?> deduceMainApplicationClass() { try { 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; }
SpringApplication的run()方法
talk is cheap, show you the code~
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//对程序部分代码进行计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
//获取并创建SpringApplicationRunListener 这里用到了观察者模式,将Spring Boot的启动状态实时通知给观察者
SpringApplicationRunListeners listeners = getRunListeners(args);
//通知starting,构建了一个ApplicationStartingEvent事件,并将其发布出去
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//创建参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//配置Environment
ConfigurableEnvironment environment =
prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//根据webApplicationType创建ApplicationContext
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//设置Environment,加载相关配置
prepareContext(bootstrapContext, context, environment, listeners,
applicationArguments, printedBanner);
//这个是最重要的一步,会调用Spring的refresh方法(ioc)
refreshContext(context);
//空方法
afterRefresh(context, applicationArguments);
stopWatch.stop();
//打印程序启动耗时
//Started TrainingCenterPlatformApplication in 6.776 seconds (JVM running for 23.098)
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//通知started
listeners.started(context);
//完成程序的最终启动,执行ApplicationRunner和CommandLineRunner实现类的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
//SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
好了,我觉的最重点的还是refresh方法,下一篇文章就这个方法做一个源码解析吧!