引言

Spring Boot 大大简化了我们的开发配置,节省了大量的时间,确实比较方便。但是对于新手来说,如果不了解个中原理,难免会遇到坑。

本文作者将带领大家走近神秘的 Spring Boot,一步步破开它的神秘面纱,探索 Spring Boot 的启动原理。

开发任何基于 Spring Boot 的项目,我们都会使用以下的启动类:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

可以看到,Application 类中定义了注解 @SpringBootApplication,main 方法里通过 SpringApplication.run 来启动整个应用程序。
因此要研究 Spring Boot 的启动原理,我们就需要从这两个地方入手。

强大的 SpringBootApplication

首先,我们先来看看 SpringBootApplication 源码是怎么定义这个注解的:

/*** 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, attribute = "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*/@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")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、@EnableAutoConfiguration、@ComponentScan。
因此,正如上一篇所讲的一样,我们将 SpringBootApplication 替换成这三个注解也是相同的效果:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

每次我们都写这三个注解比较麻烦,因此我们只写 @SpringBootApplication 就行了。

下面,我们分别来介绍这三个注解。

SpringBootConfiguration

我们先来看看它的源码:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

它其实就是一个 Configuration,但是 Spring Boot 推荐用 SpringBootConfiguration 来代替 Configuration。

Spring Boot 社区推荐使用 JavaConfig 配置,所以要用到 @Configuration。

我们先来看看 SpringMVC 基于 XML 是如何配置的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"default-lazy-init="true"><!--bean定义-->
</beans>

而 JavaConfig 的配置是这样的:

import org.springframework.boot.SpringBootConfiguration;@SpringBootConfiguration
public class WebConfig {//bean定义
}

任何标注了 SpringBootConfiguration 或 Configuration 的类都是一个 JavaConfig。

我们再来看看基于 XML 的 Bean 是如何定义的:

<bean id="service" class="ServiceImpl"></bean>

而 JavaConfig 的配置是这样的:

import org.springframework.boot.SpringBootConfiguration;@SpringBootConfiguration
public class WebConfig {//bean定义@Beanpublic Service service(){return new ServiceImpl();}
}

任何标注了 Bean 的方法都被定义为一个 Bean,我们可以在任何 Spring 的 IoC 容器中注入进去。

EnableAutoConfiguration

这个注解尤为重要,它的作用是自动将 JavaConfig 中的 Bean 装载到 IoC 容器中。

ComponentScan

这个注解的作用是自动扫描并加载符合条件的组件(如:Component、Bean 等),我们可以通过 basePakcages 来指定其扫描的范围,如果不指定,则默认从标注了 @ComponentScan 注解的类所在包开始扫描。如下代码:

@ComponentScan(basePackages = "com.lynn")

因此,Spring Boot 的启动类最好放在 root package 下面,因为默认不指定 basePackages,这样能保证扫描到所有包。

以上只是从表面来研究 Spring Boot 的启动原理,那么,为什么通过 SpringBootApplication 和 SpringApplication.run() 就能启动一个应用程序,它的底层到底是怎么实现的呢?别急,我们马上来一探究竟。

源码解析

我们知道,启动类先调用了 SpringApplication 的静态方法 run,跟踪进去后发现,它会先实例化 SpringApplication,然后调用 run 方法。

/*** Static helper that can be used to run a {@link SpringApplication} from the* specified sources using default settings and user supplied arguments.* @param sources the sources to load* @param args the application arguments (usually passed from a Java main method)* @return the running {@link ApplicationContext}*/public static ConfigurableApplicationContext run(Object[] sources, String[] args) {return new SpringApplication(sources).run(args);}

所以,要分析它的启动源码,首先要分析 SpringApplicaiton 的构造过程。

SpringApplication 构造器

在 SpringApplication 构造函数内部,它会调用内部的一个定义为 private 的方法 initialize:

public SpringApplication(Object... sources) {initialize(sources);
}private void initialize(Object[] sources) {if (sources != null && sources.length > 0) {this.sources.addAll(Arrays.asList(sources));}this.webEnvironment = deduceWebEnvironment();setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}

通过上述代码,我们分析到 SpringApplication 实例化时有以下几个步骤:

1.将所有 sources 加入到全局 sources 中,目前只有一个 Application。

2.判断是否为 Web 程序(javax.servlet.Servlet、org.springframework.web.context.ConfigurableWebApplicationContext 这两个类必须存在于类加载器中)。

判断过程可以参看以下源码:

private static final String[] WEB_ENVIRONMENT_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
private boolean deduceWebEnvironment() {for (String className : WEB_ENVIRONMENT_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return false;}}return true;}

3.设置应用程序初始化器 ApplicationContextInitializer,做一些初始化的工作。

4.设置应用程序事件监听器 ApplicationListener。

5.找出启动类,设置到 mainApplicationClass 中。

SpringApplication 的执行流程

SpringApplication 构造完成后,就会调用 run 方法,这时才真正的开始应用程序的执行。

先来看看源码:

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);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);}return context;}catch (Throwable ex) {handleRunFailure(context, listeners, analyzers, ex);throw new IllegalStateException(ex);}}

通过上述源码,将执行流程分解如下:

1.初始化 StopWatch,调用其 start 方法开始计时。
2.调用 configureHeadlessProperty 设置系统属性 java.awt.headless,这里设置为 true,表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能。
3.遍历 SpringApplicationRunListeners 并调用 starting 方法。
4.创建一个 DefaultApplicationArguments 对象,它持有 args 参数,就是 main 函数传进来的参数调用 prepareEnvironment 方法。
5.打印 banner。
6.创建 Spring Boot 上下文。
7.初始化 FailureAnalyzers。
8.调用 prepareContext。
9.调用 AbstractApplicationContext 的 refresh 方法,并注册钩子。
10.在容器完成刷新后,依次调用注册的 Runners。
11.调用 SpringApplicationRunListeners 的 finished 方法。
12.启动完成并停止计时。
13.初始化过程中出现异常时调用 handleRunFailure 进行处理,然后抛出 IllegalStateException 异常。

第03课:Spring Boot 启动原理相关推荐

  1. Spring Boot(四):Spring Boot启动原理分析

    文章目录 Spring Boot启动原理分析 一.依赖导入原理 二.Spring Boot包扫描原理 三.Spring Boot自动配置原理 Spring Boot启动原理分析 一.依赖导入原理 父项 ...

  2. 强大的Spring Boot启动监听器事件-初始化系统账号密码

    文章目录 前言 一.SpringApplicationEvents 事件类型 1.1 ApplicationStartingEvent 1.2 ApplicationEnvironmentPrepar ...

  3. Spring Boot启动过程源码分析--转

    https://blog.csdn.net/dm_vincent/article/details/76735888 关于Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码(基于Sp ...

  4. Java微框架Spring Boot 运行原理深入解读

    本文节选自< JavaEE开发的颠覆者--Spring Boot实战 >一书.本书从Spring 基础.Spring MVC 基础讲起,从而无难度地引入Spring Boot 的学习.涵盖 ...

  5. 19年8月 字母哥 第三章 spring boot 配置原理实战 用热点公司网不行

    第三章 spring boot 配置原理实战 3.1.结合配置加载讲解bean自动装配原理 3.2.详解YAML语法及占位符语法 3.3.获取自定义配置的两种实现方法 3.4.配置文件注入值数据校验 ...

  6. spring boot 启动类

    做项目用到spring boot 感觉spring boot用起来比较流畅.想总结一下,别的不多说,从入口开始. spring boot启动类Application.class 不能直接放在main/ ...

  7. Spring Boot 注解原理

    Spring Boot 注解原理 首先,先看SpringBoot的主配置类: @SpringBootApplication public class StartEurekaApplication {p ...

  8. Spring源码深度解析(郝佳)-学习-Spring Boot体系原理

      Spring Boot是由Pivotal团队提供的全新框架,其设计目的用来简化新Spring应用初始化搭建以及开发过程,该框架使用了我写的方式进行配置,从而开发人员不再需要定义样板化的配置,通过这 ...

  9. Spring boot 启动后执行特定的操作

    有时候我们需要在应用启动完成后执行一些特定的操作,比如: 删除一些临时文件或者Redis中的缓存 将一些字典类的数据加载到缓存,这样就不用每次去数据库中查了,有些关联数据从缓存中取得赋值就可以了,不再 ...

最新文章

  1. 【 Verilog HDL 】清晰的时序逻辑描述方法之计数器的描述范例
  2. 一周一论文(翻译)——[SIGMOD 2015] TIMELY RTT-based Congestion Control for the Datacenter
  3. C++ vector 容器浅析
  4. 倒计时几秒_和平精英:倒计时0秒时进圈会不会被淘汰?主播展示极限卡圈
  5. 【机器视觉】 dev_set_tool_geometry算子
  6. 动态规划—01背包问题
  7. Shell的sort、uniq、tr、cut、命令和 正则表达式
  8. FFTW 和 CUFFT 的使用对比
  9. iOS 随笔 允许所有不安全网络访问项目
  10. vocabulary of ERP
  11. System.Windows.Forms.ListView
  12. 人性”的三张图,改变无数人!
  13. java adt教程_用Eclipse安装ADT插件搭建Android环境(图文)
  14. 数据库系统概论:ER图设计
  15. 基于Unity的A星算法实现
  16. SIP协议详解(中文)-5
  17. java调用考勤机_怎样把考勤机上的数据用java程序得到?
  18. 34个漂亮的应用程序后台管理界面分享
  19. C报错ld returned 1 exit status可能的原因
  20. 利用WNMP部署woniunote

热门文章

  1. Core Location和MapKit的一些简单使用
  2. 《当程序员的那些狗日日子》(三十五)欲去还留
  3. sql2005 Agent XPs 选项释义
  4. mysql忘记root密码解决方法(版本mysql-8.0.12)
  5. aaaaaaaaaaa
  6. 9.7号Linux学习笔记
  7. P1631 序列合并
  8. css禁用鼠标点击事件
  9. php 时间倒计时代码 个人写法 有好的想法的欢迎贴出来分享
  10. Java EE 简介