SpringBoot框架目前广泛应用在各大企业的各种工业级项目中,如Java作为天翼云TeleDB后台团队主语言,SpringBoot框架在各大项目中广泛应用,并且积累了沉淀了很多SpringBoot框架的使用经验和对SpringBoot框架进行了很多源码级的研究。这里主要对SpringBoot框架启动类SpringApplication进行源码剖析,方便开发者对SpringBoot框架的运作机制有更深的理解,提高解决工作中遇到相关问题的效率。
1.1 应用启动:SpringApplicaiton 的 run 方法剖析
基于 SpringBoot 框架搭建的项目是可以通过执行项目启动类 Application 的 main 方法并以独立进程的方式来启动运行,其中由 SpringBoot 官网提供的项目压缩包的项目启动类 Application 的典型实现如下:其中 Demo 是对应项目的名字。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
// 完成SpringBoot框架和应用的启动运行
SpringApplication.run(DemoApplication.class, args);
}
}
由 Application 类的定义可知,该类使用了 @SpringBootApplication 注解修饰,在 main 方法中只有一个方法调用,即调用了 SpringApplication 类的静态方法 run,在 run 方法内部完成应用的启动运行,所以了解该 run 方法的内部实现是深入学习 SpringBoot 框架的核心设计的必经之路。
SpringApplication 类的静态方法 run 的定义如下:使用当前的项目启动类 Application 和类的启动参数 args 作为方法入参,在内部会最终创建一个 SpringApplication 类的对象并调用该对象的成员方法 run 方法完成 SpringBoot 框架,或者称为应用的启动。
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 创建一个SpringApplication对象并调用该对象的成员方法run
return new SpringApplication(primarySources).run(args);
}
SpringBoot 框架在内部实现当中也是依赖 Spring 框架的 IOC 容器来完成 Java 对象的相关管理的,如 Java 对象的创建和对象间的依赖注入。所以在 SpringApplication 对象的成员方法 run 中也是通过 Spring 框架的 Application 接口实现类来完成 IOC 容器的创建,具体实现如下:
public ConfigurableApplicationContext run(String... args) {
// 启动耗时跟踪器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 应用上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 应用启动监听器,实现异步处理
SpringApplicationRunListeners listeners = getRunListeners(args);
// 监听器准备启动
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 将命令行参数加载到环境变量environment中
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 1. 创建应用上下文,即Spring IOC容器
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 2. 初始化Spring IOC容器前的准备工作
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 3. 初始化 Spring IOC 容器
refreshContext(context);
// 4. 初始化Spring IOC容器后的相关工作
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 监听器启动完成
listeners.started(context);
// 从Context中获取CommandRunner并调用SpringBoot的CommandRunner相关类
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 监听器执行
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
// 返回Spring IOC容器对应的Application接口实现类对象
return context;
}
在 run 方法内部主要完成 Spring IOC 容器的创建与初始化,各个步骤的作用请看以上代码注释,而关于 Spring IOC 容器创建与初始化的核心方法调用顺序为:
-
createApplicationContext 方法调用:创建Spring IOC容器,即创建 Application 接口的实现类对象,createApplicationContext 方法的核心实现如下:基于 Servlet 实现的 Java web 项目使用的是 SpringBoot 框架对于 Application 接口的实现类 AnnotationConfigServletWebServerApplicationContext,在该实现类的 refresh 方法中会完成 Servlet 引擎的加载和启动,从而实现在指定监听端口监听客户端请求,即与 Tomcat 或 Jetty 相同的功能。
protected ConfigurableApplicationContext createApplicationContext() {
// 根据应用类型确定需要使用哪个Application接口的实现类
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
// 基于servlet实现的Java Web项目,使用Application接口实现类为:
// AnnotationConfigServletWebServerApplicationContext
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
// 默认为基于Java类配置的AnnotationConfigApplicationContext
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
// 创建Application接口的实现类对象实例
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
} -
prepareContext 方法调用:完成初始化Spring IOC容器前的准备工作。步骤 1 只是创建了 Application 接口的实现类对象 context,此时并没有真正启动 Spring IOC 容器,故在该步骤会继续为下一步的 Spring IOC 容器的启动与初始化进行一些准备工作。该步骤的一个核心功能是将项目启动类 Application 加载到Spring IOC容器中,即生成 Application 类对应的 BeanDefinition 对象,具体在 prepareContext 方法内部的 load 方法完成:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 将环境属性集合注入到context中
context.setEnvironment(environment);
// 后置处理context
postProcessApplicationContext(context);
// 省略其他代码
// Application类是sources集合中的一个元素
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 将项目启动类Application加载到Spring IOC容器中
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
} -
refreshContext 方法调用:该方法是实际完成 Spring IOC 容器的初始化,即 Java 对象的加载与创建,Java 对象间的依赖注入等,具体为调用 Spring 框架中定义 IOC 容器初始化流程的 AbstractApplication 抽象类的 refresh 方法来完成,这个方法的具体实现在上一小节已经介绍过,如有疑问可以回去看看。
private void refreshContext(ConfigurableApplicationContext context) {
// 调用AbstractApplicaiton的refresh方法来完成Spring IOC容器的初始化
refresh(context);
if (this.registerShutdownHook) {
try {
// 注册关闭hook
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}以上 refresh 方法的实现:
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
// 调用AbstractApplicationContext的refresh方法
((AbstractApplicationContext) applicationContext).refresh();
} -
afterRefresh 方法调用:完成 Spring IOC 容器初始化后的一些处理功能,该方法目前是一个 protected 修饰的空方法,主要用于拓展性方面考虑。
通过以上代码分析可知,SpringBoot 框架在内部主要是通过拓展实现 Spring 框架的 Application 接口来自定义 SpringBoot 框架的 IOC 容器的初始化过程,实现 SpringBoot 框架自身的功能,如基于 Java 进程独立启动运行等。
1.2 应用总配置类:Application 类与 @SpringApplicationContext 注解
细心的读者可能会留意到项目启动类 Application ,如以上介绍的 DemoApplication 会使用 @SpringApplicationContext 注解,如下:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
为什么需要使用 @SpringApplicationContext 注解,这个注解的作用是什么?其实这个注解是 SpringBoot 框架提供的一个核心注解,该注解是 SpringBoot 框架实现 Java类配置替代 XML 文件配置、SpringBoot 的自动配置特性的入口,该注解的定义如下:有定义可知,该注解是一个复合注解,由多个注解组成,核心包括:@SpringBootConfiguration 注解,@EnableAutoConfiguration 注解和 @ComponentScan 注解。
@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 {
// 省略其他代码
}
SpringBoot 框架推崇的是不使用 XML 文件来作为基于 Spring 框架搭建的项目的应用配置,而是使用基于 Java 类配置的方式,所以由 @SpringBootApplication 注解的定义可知,该注解由多个注解组成,其中一个注解是 @Configuration,所以项目启动类 Application 其实就是一个 Java 配置类来的,我们可以在该类中对当前的项目进行配置,如定义某个 Java 对象,或者称为 bean 对象,则可以结合 @Bean 注解来实现。关于 @Configuration 的更多知识在后面章节详细分析。
除此之外,单单基于 @Configuration 注解实现基于 Java 类的应用配置是不够的,SpringBoot 框架需要使用 @ComponentScan 注解来完成相关 Java 对象的自动扫描和注册,如使用了 @Controller 注解,@Service 注解或 @Repository 注解的类的对象的加载。
最后 @EnableAutoConfiguration 注解是 SpringBoot 实现自动配置的核心注解,该注解主要用于启动 SpringBoot 框架的自动配置特性,具体的自动配置生效需要配合 SpringBoot 的 starter 包和 @Configuration 注解来实现。
@Configuration 注解,@ComponentScan 注解是 Spring 框架的两个核心注解,以及 @EnableAutoConfiguration 是 SpringBoot 框架的自动配置实现的核心注解,关于这些注解的更多知识,包括用户和实现原理在后面章节再详细分析,使得读者朋友能够更好地理解 Spring 框架和 SpringBoot 框架的使用,达到知其然知其所以然。
1.3 总结
SpringBoot 框架定义了 SpringApplication 类来作为自身的一个框架入口类,即基于 SpringBoot 框架搭建的项目,在代码层面都需要通过 SpringApplication 类来使用 SpringBoot 框架,具体为在项目启动类 Application 的静态 main 方法中调用 SpringApplication 类的静态方法 run。
SpringApplication 类的静态方法 run 在内部会为当前项目创建一个对应的 SpringApplication 类对象,最终调用该对象的成员方法 run 来完成 Spring 的 IOC 容器的创建与初始化,以及完成 servlet 引擎的加载,从而可以实现以独立 Java 进程启动的方式来提供服务。
除此之外,SpringBoot 框架推崇的是基于 Java 类的方式来对项目进行配置,从而实现基于 SpringBoot 搭建的项目不再需要如传统的 Spring 项目一样,使用繁琐的 XML 配置文件来管理项目配置。而 SpringBoot 框架对于这个功能的实现方式主要是通过定义一个 @SpringBootApplication 注解来作为实现入口,然后 SpringBoot 框架会基于该注解来实现基于 Java 类配置的功能和 SpringBoot 的自动配置功能。