Spring 源码系列
1、Spring 学习之扩展点总结之后置处理器(一)
2、Spring 学习之扩展点总结之后置处理器(二)
3、Spring 学习之扩展点总结之自定义事件(三)
4、Spring 学习之扩展点总结之内置事件(四)
5、Spring 学习之扩展点总结之@Import(五)
6、Spring 学习之AOP总结之基础介绍(六)
7、Spring 学习之AOP总结之实现代理(七)
8、SpringBoot 学习之自动配置基本原理(八)
9、SpringBoot 学习之启动原理(九)
10、ElasticSearch学习随笔之SpringBoot Starter 操作
11、图数据库 Neo4j 学习之SpringBoot整合
12、SpringBoot 学习之常用 Filter / Advice 总结
13、SpringBoot+FastJson 优雅的过滤 Response Body
14、Spring Security + Oauth2 认证授权
文章目录
前言
SpringBoot 自动装配基本上介绍完了,主要的几个注解和借助 Spring 的 EventListener,通过每个 starter jar包里面的 spring.factories 配置文件来实现,把 spring.factories 里面配置好的 Bean 进行初始化并放进 IOC 容器以便其他实例中来使用,基本原理是这样的。不过我们都知道,当一个SpringBoot项目构建出来后,我们直接可以通过 java -jar xxxx.jar 来启动,我们就可以访问了;不像我们之前开发的 war 包,需要放进一个web servlet 容器来执行访问,SpringBoot如何做到这一点呢,接下来我们就一探究竟。
1、 一个SpringBoot应用包含什么?
1.1 如何打包成一个Jar包?
通过SpringBoot构建三步走我们就可以轻松构建出一个Boot项目来,第一步则是编写 pom.xml 文件,pom 文件就是 Maven 来管理项目依赖的配置文件,我们大部分项目基本都是用 Maven 来构建的,Maven 也是在 Java 世界里主流的项目构建工具(Gradle也不错),那么如何把一个项目打包成一个启动 Jar 包呢?就是通过 Maven。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.5.RELEASE</version>
</plugin>
</plugins>
</build>
上面的配置是通过 Maven 来打包一个 Jar 包必须要使用到的,是一个 Maven 插件,如何打包成一个Jar 包呢,就是通过 Maven 的这个插件,通过 mvn clean install 或者 mvn clean package,Maven 就会把我们的 Boot 应用打包成一个 Jar包了,如此方便又简单。
1.2 Jar 包的目录结构?
把一个Jar包(就是一个zip格式的压缩包)通过解压工具打开,发现有三个目录,如下图:
Jar包的层级结构如下:
SpringInitialize-1.0-SNAPSHOT.jar
├── META-INF
│ └── MANIFEST.MF
├── BOOT-INF
│ ├── classes
│ │ └── 应用程序类
│ └── lib
│ └── 第三方依赖jar
└── org
└── springframework
└── boot
└── loader
└── springboot启动程序
SpringBoot应用启动主要就是靠 META-INF 下面的 MANIFEST.MF文件。
1.3 META-INF 内容有什么?
配置项 | 说明 |
---|---|
Manifest-Version: 1.0 | 应用版本号 |
Implementation-Title: SpringInitialize | 可执行应用名称 |
Implementation-Version: 1.0-SNAPSHOT | 可执行应用版本号 |
Start-Class: com.selftest.SpringInitializeApp | 自定义开发的启动类 |
Spring-Boot-Classes: BOOT-INF/classes/ | 自定义开发的类 |
Spring-Boot-Lib: BOOT-INF/lib/ | 我们自己开发通过POM引入的第三方jar包 |
Build-Jdk-Spec: 1.8 | 构建JDK 版本是 1.8 |
Spring-Boot-Version: 2.2.5.RELEASE | SpringBoot应用版本号 |
Created-By: Maven JAR Plugin 3.2.2 | Maven打包插件 |
Main-Class: org.springframework.boot.loader.JarLauncher | Jar 启动的 Main 函数 |
2、java -jar 做了什么?
首先看一下官网对 java -jar 命令做了解释:
By default, the first argument that is not an option of the java command is the fully qualified name of the class to be called. If the -jar option is specified, its argument is the name of the JAR file containing class and resource files for the application. The startup class must be indicated by the Main-Class manifest header in its source code.
用我蹩脚的英语翻译如下:
默认情况下,java 命令的第一个参数不是完全地有资格的去调用,如果 -jar 参数是明确规定的,后面的参数是 jar 的应用程序名,该 jar 包含的是 class 和 资源,在 manifest 文件头中必须定义 Main-Class 启动类。
通过官网解释,java -jar 做了什么呢?自我总结就是,java 命令携带参数 -jar,调用了 Main-Class 指定的启动类 (org.springframework.boot.loader.JarLauncher),这个启动类就会启动加载启动我们的应用程序。
2.1 什么是 Archive?
- Archive 即归档文件,这个概念在 Linux 下比较常见。
- 通常就是一个 tar / zip 格式的压缩。
- jar 就是 zip 格式压缩包。
2.2 JarLauncher 介绍
org.springframework.boot.loader.JarLauncher 启动类中做了什么呢?
JarLauncher 继承了 ExecutableArchiveLauncher 类, ExecutableArchiveLauncher 又继承了 Launcher。
public class JarLauncher extends ExecutableArchiveLauncher {
}
public abstract class ExecutableArchiveLauncher extends Launcher {
}
首先调用了 JarLauncher 中的 main 方法来启动,main 方法中 new 出来了一个 JarLuncher 对象,调用了 lunch() 方法。
public static void main(String[] args) throws Exception {
(new JarLauncher()).launch(args);
}
最终调用了 run() 方法,调用了我们自己的启动类的 main 方法。
public void run() throws Exception {
Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.setAccessible(true);
mainMethod.invoke((Object)null, this.args);
}
3、SpringBoot应用(Jar包)启动流程总结
- SpringBoot 应用打包之后,生成一个 jar包,包含了应用依赖的 Jar 包和SpringBoot启动是需要的类。
- 通过 java -jar 调用 xxx.jar 文件,调用到 Main-Class 指定的 JarLuncher 启动类,负责创建一个 loader 来加载 lib 下面的 依赖 jar 包,并以一个新的线程启动应用 Main 方法。
- 启动了我们自己的应用的启动类之后,通过 EventListener 监听,扫描每个 starter jar 包下面的 spring.factories 配置来初始化需要的 Bean。
4、启动关键源码
4.1、创建 SpringApplication
在经过了 SpringBoot三步走成功构建出来一个 Boot 项目之后,我们就可以通过主启动类的 SpringApplication.run(SpringInitializeApp.class, args); 方法来启动了,现在跟踪下去看看 run() 方法里做了什么?
ctrl + 单击开始跟踪,run() > 还是 run() > new操作了。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
这里开始实例化了一个 SpringApplication对象,紧接着调用了 run(args) 方法,那主要的应就是 SpringApplication的构造函数和 run(args) 方法里面都做了什么了。
先看看 new SpringApplication() 实例化了什么东西。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//把启动类赋值给类成员变量 primrySources
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//根据classpath 下面的类,推断出是 webflux, 还是 servlet 容器
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//从 spring.factories 文件中加载所有的
//key 是 org.springframework.boot.BootstrapRegistryInitializer 的 Beans
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
//从 spring.factories 文件中加载所有的
//key 是 org.springframework.context.ApplicationContextInitializer 的 Beans
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从 spring.factories 文件中加载所有的
//key 是 org.springframework.context.ApplicationListener 的 Beans
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断出主启动类("main".equals(stackTraceElement.getMethodName())
this.mainApplicationClass = deduceMainApplicationClass();
}
4.2、run 启动
通过 new SpringApplication() 得到一个 SpringApplication 的实例,就可以调用 run() 方法启动了,那就跟踪进去看看 run() 方法里面都做了啥事。
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
//初始化并注册上下文
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
//开启了HandLess模式
configureHeadlessProperty();
//读取 spring.factories 的 SpringApplicationRunListener 组件,用来发布或者运行监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//发布 ApplicationStartingEvent 事件,运行时开始发布
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//根据命令行参数,初始化 ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//与初始化环境(基于监听器读取环境变量,配置文件的配置)
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 忽略beanInfo 的 Bean
configureIgnoreBeanInfo(environment);
// 打印 Banner 横幅
Banner printedBanner = printBanner(environment);
// 根据 webApplicationType 创建 Spring 上下文
context = createApplicationContext();
//为本应用程序设置 applicationStartUp, 默认是 DEFAULT, 此不同的设计用于最小化开销,并且不记录事件
context.setApplicationStartup(this.applicationStartup);
//预初始化 Spring 上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//这里加载 Spring IOC 容器,这里真正的调用了 Spring 的 refresh() 方法,把 Bean 加载到容器中,加载配置类 invokeBeanFactoryPostProcessors Bean工厂的后置处理器
refreshContext(context);
//在 refresh() 方法执行后,SringApplication 预留了方法,作为回调方法
afterRefresh(context, applicationArguments);
//记录本地启动的时间
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
//调用 SpringApplicationRunListeners 的 started 方法,说明 服务以及就绪
listeners.started(context, timeTakenToStartup);
//在启动完成后,SpringApplication 还会调用我们自定义的 ApplicationRunner 和 CommandLineRunner 的 run() 方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
//执行完成后,ApplicationListener 发布"spring.boot.application.ready"消息,启动结束
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}