SpringBoot 学习之启动原理以及核心源码(九)

15 篇文章 1 订阅
5 篇文章 0 订阅

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包目录
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.RELEASESpringBoot应用版本号
Created-By: Maven JAR Plugin 3.2.2Maven打包插件
Main-Class: org.springframework.boot.loader.JarLauncherJar 启动的 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?

  1. Archive 即归档文件,这个概念在 Linux 下比较常见。
  2. 通常就是一个 tar / zip 格式的压缩。
  3. 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包)启动流程总结

  1. SpringBoot 应用打包之后,生成一个 jar包,包含了应用依赖的 Jar 包和SpringBoot启动是需要的类。
  2. 通过 java -jar 调用 xxx.jar 文件,调用到 Main-Class 指定的 JarLuncher 启动类,负责创建一个 loader 来加载 lib 下面的 依赖 jar 包,并以一个新的线程启动应用 Main 方法。
  3. 启动了我们自己的应用的启动类之后,通过 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;
	}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值