Spring Boot 运行源码剖析
本文是 Spring Boot 运行源码剖析内容,内容夹杂了不少的 Spring Boot 源代码片段。
有兴趣的读者,可以按照章节名称跳跃阅读。本文分为两大章节,分别是:
- SpringBoot 工程准备
- 简单入手 SpringBoot 工程,了解 Web 开发。
- 从启动类剖析
- 强迫作者,学习分析源代码。
一、SpringBoot 工程准备
首先,花 2 分钟构建一个 SpringBoot 工程。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.live</groupId>
<artifactId>springBoot</artifactId>
<version>0.0.1</version>
<name>springBoot</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>13</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.properties
spring.profiles.active=dev
spring.devtools.add-properties=false
application.properties
spring.profiles=dev
logging.level.web=debug
MyApplication.java
package cn.live;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
启动成功日志
. ____ _ __ _ _
/\ / ___'_ __ _ _(_)_ __ __ _
( ( )___ | '_ | '_| | '_ / _` |
\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |___, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.6.RELEASE)
2020-04-06 16:50:28.783 INFO 4205 --- [ restartedMain] cn.live.MyApplication : Starting MyApplication on FoamValue-MBP.local with PID 4205 (/Users/chenxinjie/workspace-2020-02/springBoot/target/classes started by chenxinjie in /Users/chenxinjie/workspace-2020-02/springBoot)
2020-04-06 16:50:28.785 INFO 4205 --- [ restartedMain] cn.live.MyApplication : The following profiles are active: dev
2020-04-06 16:50:29.768 INFO 4205 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-04-06 16:50:29.781 INFO 4205 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-04-06 16:50:29.781 INFO 4205 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.33]
2020-04-06 16:50:29.857 INFO 4205 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-04-06 16:50:29.857 DEBUG 4205 --- [ restartedMain] o.s.web.context.ContextLoader : Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT]
2020-04-06 16:50:29.857 INFO 4205 --- [ restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1020 ms
2020-04-06 16:50:29.997 DEBUG 4205 --- [ restartedMain] o.s.b.w.s.ServletContextInitializerBeans : Mapping filters: filterRegistrationBean urls=[/*] order=-2147483647, characterEncodingFilter urls=[/*] order=-2147483648, formContentFilter urls=[/*] order=-9900, requestContextFilter urls=[/*] order=-105
2020-04-06 16:50:29.997 DEBUG 4205 --- [ restartedMain] o.s.b.w.s.ServletContextInitializerBeans : Mapping servlets: dispatcherServlet urls=[/]
2020-04-06 16:50:30.118 INFO 4205 --- [ restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-04-06 16:50:30.129 DEBUG 4205 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
2020-04-06 16:50:30.183 DEBUG 4205 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : 2 mappings in 'requestMappingHandlerMapping'
2020-04-06 16:50:30.209 DEBUG 4205 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2020-04-06 16:50:30.219 DEBUG 4205 --- [ restartedMain] .m.m.a.ExceptionHandlerExceptionResolver : ControllerAdvice beans: 0 @ExceptionHandler, 1 ResponseBodyAdvice
2020-04-06 16:50:30.315 INFO 4205 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2020-04-06 16:50:30.319 INFO 4205 --- [ restartedMain] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
2020-04-06 16:50:30.370 INFO 4205 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-04-06 16:50:30.374 INFO 4205 --- [ restartedMain] cn.live.MyApplication : Started MyApplication in 1.941 seconds (JVM running for 2.381)
浏览器打开效果
在浏览器中输入 `http://localhost:8080/actuator` 可以展现如下效果:
{"_links":{"self":{"href":"http://localhost:8080/actuator","templated":false},"health":{"href":"http://localhost:8080/actuator/health","templated":false},"health-path":{"href":"http://localhost:8080/actuator/health/{*path}","templated":true},"info":{"href":"http://localhost:8080/actuator/info","templated":false}}}
小结
依稀还记得 2011 年那会儿,Spring 还没有现在这么流行。那时更多的还是,手动下载一个个的 jar 包,然后放在一个 Java Web 工程下面去尝试。Spring Framework + Struts2 + Hibernate 的架构,包含很多 jar 的基础上,还有一些特定版本冲突的问题。那时候,从零构建一个 Java Web 工程,是一件非常苦痛的任务。
而现在,Spring Boot + Maven 的方式,简直不要太香了。
二、从启动类剖析
MyApplication.java 是开发人员自定义的启动类,但实际上有效的代码只有两行。分别是 `@SpringBootApplication` 和 `SpringApplication.run()`。
从 `@SpringBootApplication`、`SpringApplication.run()` 两个角度来拆分的话,可以得到下面的树状结构。
MyApplication.java
|-- @SpringBootApplication
| |-- @SpringBootConfiguration
| |-- @Configuration
| |-- @EnableAutoConfiguration
| |-- @AutoConfigurationPackage
| |-- @Import(AutoConfigurationPackages.Registrar.class)
| |-- @Import(AutoConfigurationImportSelector.class)
| |-- @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
| |-- @Repeatable(ComponentScans.class)
| |-- @ComponentScans
|--SpringApplication.run()
SpringBootApplication 注解部分
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。
1. 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) })
- @SpringBootApplication 的作用范围在接口、类、枚举、注解接口类。
- @SpringBootApplication 在 class 字节码文件中存在,运行时可以通过反射获取到。
- @SpringBootApplication 将被包含在 javadoc 中去。
- @SpringBootApplication 修饰的类,它的子类可以继承 @SpringBootApplication 注解。
- @SpringBootApplication 等效于同时使用 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个注解。
2. SpringBootConfiguration 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
- @SpringBootConfiguration 的作用范围在接口、类、枚举、注解接口类。
- @SpringBootConfiguration 在 class 字节码文件中存在,运行时可以通过反射获取到。
- @SpringBootConfiguration 将被包含在 javadoc 中去。
3. Configuration 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
- @Configuration 的作用范围在接口、类、枚举、注解接口类。
- @Configuration 在 class 字节码文件中存在,运行时可以通过反射获取到。
- @Configuration 将被包含在 javadoc 中去。
- @Configuration 注解使用 @Component 之后,会被实例化到 Spring 容器中。
4. EnableAutoConfiguration 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
- @EnableAutoConfiguration 的作用范围在接口、类、枚举、注解接口类。
- @EnableAutoConfiguration 在 class 字节码文件中存在,运行时可以通过反射获取到。
- @EnableAutoConfiguration 将被包含在 javadoc 中去。
- @EnableAutoConfiguration 修饰的类,它的子类可以继承 @EnableAutoConfiguration 注解。
- @EnableAutoConfiguration 修饰的类,会将 AutoConfigurationImportSelector 自动注入到 Spring 容器中。
5. AutoConfigurationPackage 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
- @AutoConfigurationPackage 的作用范围在接口、类、枚举、注解接口类。
- @AutoConfigurationPackage 在 class 字节码文件中存在,运行时可以通过反射获取到。
- @AutoConfigurationPackage 将被包含在 javadoc 中去。
- @AutoConfigurationPackage 修饰的类,它的子类可以继承 @AutoConfigurationPackage 注解。
- @AutoConfigurationPackage 修饰的类,会将 AutoConfigurationPackages.Registrar 自动注入到 Spring 容器中。
6. ComponentScan 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
- @ComponentScan 在 class 字节码文件中存在,运行时可以通过反射获取到。
- @ComponentScan 的作用范围在接口、类、枚举、注解接口类。
- @ComponentScan 将被包含在 javadoc 中去。
7. ComponentScans 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
- @ComponentScan 在 class 字节码文件中存在,运行时可以通过反射获取到。
- @ComponentScan 的作用范围在接口、类、枚举、注解接口类。
- @ComponentScan 将被包含在 javadoc 中去。
以上是 @SpringBootApplication 注解的展开内容。所有的注解加起来,只做了两件事。
- 标注
- 将指定类注入到 Spring 容器中
- AutoConfigurationImportSelector
- AutoConfigurationPackages.Registrar
- TypeExcludeFilter
- AutoConfigurationExcludeFilter
SpringApplication.run() 部分
1. new SpringApplication(primarySources)
@SuppressWarnings({ "unchecked", "rawtypes" })
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 = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
- 初始化 resourceLoader
- 初始化 PrimarySources
- 初始化 webApplicationType
- 初始化 ApplicationContext 构造器
- 初始化 ApplicationListener 应用监听器
- 初始化 mainApplicationClass
2. SpringApplication.run(args)
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;
}
- 初始化应用计数器,并启动。
- 初始化应用监听组,并启动。
- 准备环境变量
- Banner 彩蛋
- 创建 ApplicationContext
- 获取排除工厂实例
- 准备 ApplicationContext
- 刷新 ApplicationContext
- 刷新后处理 ApplicationContext
- 停止应用计数器
- 回调监听组
- 回调 ApplicationRunner、CommandLineRunner 接口
- 运行应用监听组
小结
简单翻阅了源代码,了解 `@SpringBootApplication` 都包含哪些注解标记。以及 `SpringApplication.run()` 执行的之后,都有哪些步骤。
- Dynamite动态排序库
- WordPress 顶部管理工具条添加自定义栏目
- WordPress 在后台文章和页面列表显示对应的ID
- WordPress 修改默认的 wp_ 数据库前缀
- WordPress自动在uploads文件夹内创建子文件夹
- MP6 Light:浅色型的扁平化WordPress 后台管理界面
- WiX安装选项--环境变量
- 加速!缓存Python函数的运行结果:Memoization
- WiX安装选项--注册程序集到GAC和VS的设计时环境
- 5 款前沿的WordPress主题后台选项开发框架推荐
- es6 class
- WiX安装选项---开始菜单项
- 代码编辑器Sublime Text 3 免费使用方法与简体中文汉化包下载
- TCP/IP 选项TcpTimedWaitDelay设置
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Android Studio使用recyclerview实现展开和折叠功能(在之前的微信页面基础之上)
- 解决django 多个APP时 static文件的问题
- Android Studio如何获取SQLite数据并显示到ListView上
- Android自定义短信倒计时view流程分析
- 使用Android studio3.6的java api方式调用opencv
- thinkphp5 + ajax 使用formdata提交数据(包括文件上传) 后台返回json完整实例
- php+iframe 实现上传文件功能示例
- php5.3/5.4/5.5/5.6/7常见新增特性汇总整理
- 如何在OpenStack Kolla上部署Tungsten Fabric(附14个常见的配置问题)
- 浮点数的秘密
- 由多线程内存溢出产生的实战分析
- 消失的Java进程-Linux OOM Killer
- Myers‘Diff之贪婪算法
- DiffUtil和它的差量算法
- C语言高效编程与代码优化