当前位置: 首页 > news >正文

SpringBoot 核心模块原理剖析

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

微服务始终一个相对热门的话题,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 的具体执行时序图。

转载于:https://my.oschina.net/u/3917490/blog/1927128

相关文章:

  • Confluence 6 的小型文字档案(Cookies)
  • WPF中使用amCharts绘制股票K线图
  • 装饰者模式--穿什么有这么重要?
  • 健身:手臂训练
  • SQLServer------查询结果为空的列赋默认值
  • 精简分页组件(手写)
  • Flutter 06:【小插曲】请慎重升级最新版本 AndroidStudio
  • 分页查询对象列表ListT findListByPage运用
  • centos /linux 修改目录或文件权限
  • Npm 多模块依赖解决方案
  • isset在php5.6-和php7.0+的一些差异
  • Nginx支持WebSocket反向代理-学习小结
  • linux服务器安装anaconda,然后远程使用jupyter
  • ES6是什么
  • iOS设备、版本用户量统计
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • go语言学习初探(一)
  • JavaScript对象详解
  • Mithril.js 入门介绍
  • Python进阶细节
  • TCP拥塞控制
  • Vue ES6 Jade Scss Webpack Gulp
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 前端性能优化——回流与重绘
  • 设计模式(12)迭代器模式(讲解+应用)
  • 思考 CSS 架构
  • 带你开发类似Pokemon Go的AR游戏
  • ​VRRP 虚拟路由冗余协议(华为)
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • #etcd#安装时出错
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • $ git push -u origin master 推送到远程库出错
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (TOJ2804)Even? Odd?
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (三十五)大数据实战——Superset可视化平台搭建
  • (十)c52学习之旅-定时器实验
  • (一)基于IDEA的JAVA基础1
  • (转)ObjectiveC 深浅拷贝学习
  • (转)创业家杂志:UCWEB天使第一步
  • (转载)OpenStack Hacker养成指南
  • **python多态
  • .net Application的目录
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .net framework 4.0中如何 输出 form 的name属性。
  • .NET Framework与.NET Framework SDK有什么不同?
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • .NET/C# 阻止屏幕关闭,阻止系统进入睡眠状态