Spring Boot启动过程

​上篇《Spring Boot系列——5分钟构建一个应用》介绍了如何快速创建一个Spring Boot项目并运行。虽然步骤少流程简单,为开发者省去了很多重复性的配置工作,但是其底层实现并没有这么简单。

这篇,我们就通过入口类TutorialApplication看看Spring Boot是如何启动的。

注解

写过Spring Boot都知道需要有一个入口类,就是本例子中的TutorialApplication,而这个类上面必不可上的需要有一个@SpringBootApplication注解。

点击进入该注解,我们可以发现其是一个复合注解,包括@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。


/**
* Indicates a {@link Configuration configuration} class that declares one or more
* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
* auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
* annotation that is equivalent to declaring {@code @Configuration},
* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 1.2.0
*/
@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 { /**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {}; /**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {}; /**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {}; /**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {}; }

@SpringBootConfiguration

该注解底层其实就是@Configuration注解。熟悉Spring的发展里程碑就知道这是Java Config的配置形式。

通过该注解修饰,表示该类是一个配置类。

@EnableAutoConfiguration

该注解其实也是一个复合注解。


/**
* Enable auto-configuration of the Spring Application Context, attempting to guess and
* configure beans that you are likely to need. Auto-configuration classes are usually
* applied based on your classpath and what beans you have defined. For example, if you
* have {@code tomcat-embedded.jar} on your classpath you are likely to want a
* {@link TomcatServletWebServerFactory} (unless you have defined your own
* {@link ServletWebServerFactory} bean).
* <p>
* When using {@link SpringBootApplication}, the auto-configuration of the context is
* automatically enabled and adding this annotation has therefore no additional effect.
* <p>
* Auto-configuration tries to be as intelligent as possible and will back-away as you
* define more of your own configuration. You can always manually {@link #exclude()} any
* configuration that you never want to apply (use {@link #excludeName()} if you don't
* have access to them). You can also exclude them via the
* {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied
* after user-defined beans have been registered.
* <p>
* The package of the class that is annotated with {@code @EnableAutoConfiguration},
* usually via {@code @SpringBootApplication}, has specific significance and is often used
* as a 'default'. For example, it will be used when scanning for {@code @Entity} classes.
* It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're
* not using {@code @SpringBootApplication}) in a root package so that all sub-packages
* and classes can be searched.
* <p>
* Auto-configuration classes are regular Spring {@link Configuration} beans. They are
* located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
* Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
* often using {@link ConditionalOnClass @ConditionalOnClass} and
* {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
*
* @author Phillip Webb
* @author Stephane Nicoll
* @see ConditionalOnBean
* @see ConditionalOnMissingBean
* @see ConditionalOnClass
* @see AutoConfigureAfter
* @see SpringBootApplication
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {}; /**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {}; }

其实现也是通过类似@Import的方式注入AutoConfigurationImportSelector类,并借助该类将所有符合条件的Configuration注解修饰的配置类加载到Spring Boot容器中。从classpath中搜索所有的META-INF/spring.factories配置文件,将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应配置项通过反射的形式实例化为标注了@Configuration和javaconfig形式的IOC容器配置类,然后汇总为一个并加载到ioc容器中。

@ComponentScan

这个注解就不需要多介绍了吧,其作用自动扫描加载符合条件的bean。

SpringApplication

从项目的入口第一个碰到的就是SpringApplication类。


@SpringBootApplication
public class TutorialApplication { public static void main(String[] args) {
SpringApplication.run(TutorialApplication.class, args);
}
}

进入该类的静态方法run,可以看到其在构造SpringApplication对象


public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}

进入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));
this.webApplicationType = deduceWebApplicationType();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

主要做了如下几件事:

  • 加载Source,这里只有只有Application

  • 推断WebApplicationType,该枚举有三种类型NONE、SERVLET、REACTIVE。

  • 设置初始化器变量setInitializers,初始化后得到6个初始化变量,这些类在上面提到的spring.factories中可以找到

  • 设置监听器,与上面setInitializers实现类似,最终得到如下10个listeners

  • 最后推断带有main函数的所在类,即入口类,这里就是TutorialApplication

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;
}

run方法

看完SpringApplication是如何初始化后,我们来看下这个后面的run方法具体做了哪些事。


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);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
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);
}
return context;
}
  • StopWatch,这是一个spring-core中的工具类,用来给程序运行计时(对于经常遇到需要计算某方法或接口耗时的场景,这个比System.currentTimeMillis好用)

  • configureHeadlessProperty配置,设置系统属性 java.awt.headless,这里设置为 true,表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能。

  • 遍历listeners并启动

  • 封装入参args为AppliationArguments对象

  • 打印banner(就是我们启动时看到的spring标识)

  • 后面就是初始化上下文并加载上下文,具体实现就不进去看了

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

Spring Boot系列——Spring Boot如何启动的更多相关文章

  1. Spring基础系列-Spring事务不生效的问题与循环依赖问题

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9476550.html 一.提出问题 不知道你是否遇到过这样的情况,在ssm框架中开发we ...

  2. spring boot系列--spring security (基于数据库)登录和权限控制

    先说一下AuthConfig.java Spring Security的主要配置文件之一 AuthConfig 1 @Configuration 2 @EnableWebSecurity 3 publ ...

  3. Spring Boot 系列教程16-数据国际化

    internationalization(i18n) 国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式. 它要求从产品中抽离所有地域语言,国家/地区和 ...

  4. Spring Boot 系列教程15-页面国际化

    internationalization(i18n) 国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式. 它要求从产品中抽离所有地域语言,国家/地区和 ...

  5. Spring Boot 系列教程7-EasyUI-datagrid

    jQueryEasyUI jQuery EasyUI是一组基于jQuery的UI插件集合体,而jQuery EasyUI的目标就是帮助web开发者更轻松的打造出功能丰富并且美观的UI界面.开发者不需要 ...

  6. spring boot系列01--快速构建spring boot项目

    最近的项目用spring boot 框架 借此学习了一下 这里做一下总结记录 非常便利的一个框架 它的优缺点我就不在这背书了 想了解的可以自行度娘谷歌 说一下要写什么吧 其实还真不是很清楚,只是想记录 ...

  7. Spring Boot系列(一) Spring Boot准备知识

    本文是学习 Spring Boot 的一些准备知识. Spring Web MVC Spring Web MVC 的两个Context 如下图所示, 基于 Servlet 的 Spring Web M ...

  8. Spring Boot系列——如何集成Log4j2

    上篇<Spring Boot系列--日志配置>介绍了Spring Boot如何进行日志配置,日志系统用的是Spring Boot默认的LogBack. 事实上,除了使用默认的LogBack ...

  9. Spring Boot系列(三):Spring Boot整合Mybatis源码解析

    一.Mybatis回顾 1.MyBatis介绍 Mybatis是一个半ORM框架,它使用简单的 XML 或注解用于配置和原始映射,将接口和Java的POJOs(普通的Java 对象)映射成数据库中的记 ...

随机推荐

  1. Codeforces 744C Hongcow Buys a Deck of Cards 状压dp (看题解)

    Hongcow Buys a Deck of Cards 啊啊啊, 为什么我连这种垃圾dp都写不出来.. 不是应该10分钟就该秒掉的题吗.. 从dp想到暴力然后gg, 没有想到把省下的红色开成一维. ...

  2. Python 打印当前文件相对路径和绝对路径

    一.打印相对路径 print(__file__) 二.打印绝对路径 import os print(os.path.abspath(__file__)) 三.打印文件名 import os print ...

  3. Python字符串capitalize center 方法

    一.capitalize 首字母大写 name = "wohaoshuai" print(name.capitalize()) #首字母大写 Wohaoshuai 二.center ...

  4. 【Java】 剑指offer(28) 对称的二叉树

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 请实现一个函数,用来判断一棵二叉树是不是对称的.如果一棵二叉树和它 ...

  5. 为什么要编译Linux内核?

    新的内核修订了旧内核的bug,并增加了许多新的特性.如果用户想要使用这些新特性,或想根据自己的系统度身定制一个更高效,更稳定的内核,就需要重新编译Linux内核. 通常,更新的内核会支持更多的硬件,具 ...

  6. 多线程学习笔记九之ThreadLocal

    目录 多线程学习笔记九之ThreadLocal 简介 类结构 源码分析 ThreadLocalMap set(T value) get() remove() 为什么ThreadLocalMap的键是W ...

  7. [漏洞分析]thinkphp 5.1.25 insert、insetAll、update方法注入

    0x00 前言 这个洞,早在9月29号的时候我提交给先知,那时候tp还是5.1.25的版本,天还很蓝,我也还很年轻.时至今日这个洞依旧没有审核,而tp在这期间都已经更新到了5.1.29.在最近我去跟踪 ...

  8. Excel获取第一个表名

    Excel.Worksheet wsheet1 = (Excel.Worksheet)excelSql.Worksheets.get_Item(); wsheet1.Name获取sheet名称

  9. iOS 11开发教程(十八)iOS11应用视图之使用代码添加按钮

    iOS 11开发教程(十八)iOS11应用视图之使用代码添加按钮 由于使用编辑界面添加视图的方式比较简单,所以不在介绍.这里,直接讲解代码中如何添加.使用代码为主视图添加一个按钮的方式和在1.3.3节 ...

  10. 虚拟机性能监控与故障处理工具------JDK的命令行工具

    ①jps:虚拟机进程状况工具 功能:列出正在运行的虚拟机进程,并显示1.虚拟机执行主类名称以及2.这些进程的本地虚拟机唯一ID(LVMID). 使用频率最高的JDK命令行工具,其他的JDK工具大多需要 ...