前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot 核心模块原理剖析

SpringBoot 核心模块原理剖析

作者头像
CSDN技术头条
发布2018-04-18 17:07:40
1.4K0
发布2018-04-18 17:07:40
举报
文章被收录于专栏:CSDN技术头条CSDN技术头条
微服务始终一个相对热门的话题,SpringBoot 则以其轻量级、内嵌 Web 容器、一键启动、方便调试等特点被越来越多的微服务实践者所采用。

知其然还要知其所以然,你了解 SpringBoot 中三大核心模块的实现原理吗?

三大核心模块

spring-boot-load 模块

正常情况下一个类加载器只能找到加载路径的 jar 包里当前目录或者文件类里面的 *.class 文件,SpringBoot 允许我们使用 java -jar archive.jar 运行包含嵌套依赖 jar 的 jar 或者 war 文件。

spring-boot-autoconfigure 模块

Spring的出现给我们管理 Bean 的依赖注入提供了便捷,但是当我们需要使用通过 pom 引入的 jar 里面的一个 Bean 时候,还是需要手动在 XML 配置文件里面配置。Springboot 则可以依据 classpath 里面的依赖内容自动配置 Bean 到 Spring 容器。

spring-boot 模块

提供了一些特性用来支持 SpringBoot 中其它模块,本文会讲解到该模块都提供了哪些功能以及实现原理。

spring-boot-loader 模块

Java 原生类加载器局限及改进思路

Java 中每种 ClassLoader 都会去自己规定的路径下查找字节码文件并加载到内存(可以参考《Java 类加载器揭秘》这场 Chat)。这里需要补充的是 Classloader 只能加载扫描路径当前目录或者当前目录文件夹下的 .class 文件,或当前目录文件夹下 jar 文件里面的.class文件。如果这个 jar 里面又嵌套了其他 jar 包文件,那么这些嵌套 jar 里面的 *.class 文件是不会被 ClassLoader 加载的。

如上图,假设类加载器 cl 扫描字节码文件路径 /Users/zhuizhumengxiang,那么 cl 可以加载到 a.class、b.class 和 c.jar 里面的 c1.class 文件,但是加载不到 c2.jar 和 c3.jar 里面的 .class 文件,因为 c2.jar 和 c3.jar 是嵌套 jar。

为了能够加载嵌套 jar 里面的资源,之前的做法都是把嵌套 jar 里面的 class 文件和应用的 class 文件打包为一个 jar,这样就不存在嵌套 jar 了,但是这样做就不能很清晰的知道哪些是应用自己的,哪些是应用依赖的,另外多个嵌套 jar 里面的 class 文件可能内容不一样但是文件名却一样时候又会引发新的问题。

spring-boot-loader 模块则允许我们使用 java -jar archive.jar 方式运行包含嵌套依赖 jar 的 jar 或者 war 文件,它提供了三种类启动器(JarLauncher、 WarLauncher 和 PropertiesLauncher),这些类启动器的目的都是为了能够加载嵌套在 jar 里面的资源(比如 class 文件、配置文件等)。JarLauncher、WarLauncher 固定去查找当前 jar 的 lib 目录里面的嵌套 jar 文件里面的资源。本文则只介绍 jar 文件。

那么我们可以先思考下,如果让我们自己做一个可以加载嵌套 jar 里面的资源的工具模块,我们会怎么做呢?

我们知道 Java 中的 AppClassLoader 和 ExtClassLoader 都是继承自 URLClassLoader 并通过构造函数传递自定义的扫描路径,那么我们是不是也可以继承 URLClassLoader,然后把嵌套 jar 里面的多个 jar 的路径作为一个 URLClassLoader 的扫描路径呢?这样该 URLClassLoader 就可以找到嵌套 jar 里面的资源了。

URLClassLoader 的构造函数会传递一个 URL[] urls 作为该加载器的类扫描路径,那么针对上图中嵌套的 jar,我们可以创建一个 URLClassLoader,它的 urls 路径内容为:

/Users/zhuizhumengxiang/

/Users/zhuizhumengxiang/c.jar/c2.jar

/Users/zhuizhumengxiang/c.jar/c3.jar

  • 根据第一个路径 URLClassLoader 加载器可以查找到 a.class、b.class 和 c.jar 里面的 c1.class 文件。
  • 根据第二个路径可以加载到 c2.jar 里面的 .class 文件。
  • 根据第三个路径可以加载到 c3.jar 里面的 .class 文件。

这是一个可以解决嵌套 jar 的思路,但是还有一个问题需要解决,就是默认情况下我们启动 main 函数所在的类时候用的类加载器是 AppClassLoader,而它的加载路径是 classpath。那么我们自定义的 URLClassLoader 什么时候使用呢?

为了使用这个自定义 URLClassLoader,可以想办法让我们自定义的 URLClassLoader 来加载我们的 main 函数,但是一个逃离不了的现实是当使用 Java 命令启动 main 函数所在类时候使用的总是 AppClassLoader,那么现在只有在中间加一层来解决这个问题。

具体来说是使用 Java 命令启动时候启动一个中间类的 main 函数,这个中间类里面自定义 URLClassLoader,然后使用自定义 URLClassLoader 来加载我们真正的 main 函数。

如上图 Application 假设为含有 main 函数的类,之前是直接使用 AppClassLoader 进行加载,那么现在我们先使用 APPClassLoader 加载 Launcher 类,该类内部在创建一个 URLClassLoader 用来加载我们的 Application 类。下面具体介绍下 spring-boot-loader是如何解决嵌套 jar 问题的。

spring-boot-loader 模块提供的 jar 目录结构

为了解决嵌套 jar 问题,Springboot 中 jar 文件格式规定如下。

archive.jar

|

+-META-INF(1)

| +-MANIFEST.MF

+-org(2)

| +-springframework

| +-boot

| +-loader

| +-<spring boot loader classes>

+-com(3)

| +-mycompany

| +project

| +-YouClasses.class

+-lib(4)

+-dependency1.jar

+-dependency2.jar

  • 结构(1)是 jar 文件中 MANIFEST.MF 文件的存放处。
  • 结构(2)是 Spring-boot-loader 本身需要的 class 放置处。
  • 结构(3)是应用本身的文件资源放置处。
  • 结构(4)是应用依赖的 jar 固定放置处,即 lib 目录。

那么 spring-boot 是如何去创建这个结构并且按照这个结构加载资源呢?

首先在打包时候会使用 spring-boot-maven-plugin 插件重写打成的 jar 文件,会设置META-INF/MANIFEST.MF 中的 Main-Class:org.springframework.boot.loader.JarLauncher、Start-Class: com.mycompany.project.MyApplication,并拷贝 spring-boot-loader包里面的 class 文件到结构(2),应用依赖的 jar 拷贝到(4),应用本身的类拷贝到(3)。

接着,运行 java -jar archive.jar ,Launcher 会加载 JarLauncher 类并执行其中的 main 函数,JarLauncher 主要关心构造一个合适的 URLClassLoader 加载器用来调用我们应用程序(MyApplication)的 main 方法。

SpringBoot 的这种格式可以明确地让我们知道应用本身包含哪些类,应用依赖了哪些类。

spring-boot-maven-plugin 插件打包流程分析

SpringBoot 应用打包时候需要引入如下 Maven 插件才会生成上面介绍的结构的 jar。

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<version>1.5.9.RELEASE</version>

<executions>

<execution>

<goals>

<goal>repackage</goal>

</goals>

</execution>

</executions>

</plugin>

本文使用 Springboot 版本为 1.3.5.RELEASE,Maven 插件版本为 1.5.9.RELEASE。

当我们执行 mvn clean package 进行打包生成 jar 文件后,spring-boot-maven-plugin 插件会对 jar 文件进行重写,具体重写步骤,请见下面的时序图。

  • 步骤(1)是 Maven 插件执行的入口类。
  • 步骤(2)设置是否从 jar 本节里面排除掉 spring-boot-devtools 的 jar 包,默认是不排除。这个可以在引入插件的地方配置,如下:

<configuration>

<excludeDevtools>true</excludeDevtools>

</configuration>

  • 步骤(5)(6)是主要环节,就是设置 MANIFEST.MF,Main-Class: org.springframework.boot.loader.JarLauncher、Start-Class: com.mycompany.project.MyApplication,并写入到文件,注意这里 MyApplication 代表了 SpringBoot 里面启动整体应用的包含 main 函数的那个类,也就是加了 @SpringBootApplication 注解的那个类。
  • 步骤(7)写入应用依赖的 jar 包到 lib 目录。
  • 步骤(8)拷贝 spring-boot-load 包里面的 class 文件到 jar 包的结构(2)处。

注:这里读者可以先思考下为何要拷贝本来应该放入到 lib 里 spring-boot-

loader.jar 里面的 class 到结构(2)?

JarLauncher 执行流程分析

为了解决嵌套 jar 资源加载问题,上节讲解了 Boot 提供的专用 Maven 插件用来修改 jar 包的 Main-Class: org.springframework.boot.loader.JarLauncher、Start-Class: com.mycompany.project.MyApplication,修改后的结果是当我们执行 java -jar archive.jar 时候会启动 JarLauncher 的 main 函数,而不是我们 SpringBoot 应用里 MyApplication 的 main 函数,下面看看 JarLauncher 的具体执行时序图。

全文还包括:

  • spring-boot-autoconfigure 模块
  • spring-boot 模块
  • 总结
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CSDN技术头条 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档