jar包天天见,可是你知道它的运行机制吗

转载自  jar包天天见,可是你知道它的运行机制吗

今天介绍两个大家每天都在用但是却很少去了解它的知识点:spi 和 jar 运行机制,废话不多说,开始正题。

spi

spi 是 Java 提供的一套用来被第三方实现或者扩展的 API ,它可以用来启用框架扩展和替换组件。spi 机制是这样的:读取 META-INF/services/ 目录下的元信息,然后 ServiceLoader 根据信息加载对应的类,你可以在自己的代码中使用这个被加载的类。要使用 Java SPI,需要遵循如下约定:

  • 当服务提供者提供了接口的一种具体实现后,在 jar 包的 META-INF/services 目录下创建一个以 “接口全限定名” 命名的文件,内容为实现类的全限定名;

  • 接口实现类所在的 jar 包放在主程序的 classpath 中;

  • 主程序通过 java.util.ServiceLoder 动态装载实现模块,它通过扫描 META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到 JVM ;

  • SPI 的实现类必须携带一个不带参数的构造方法;

现在我们来简单的使用一下吧。

 

spi 使用示例

建一个 maven 项目,定义一个接口 ( com.test.SpiTest ),并实现该接口( com.test.SpiTestImpl);然后在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 ( com.test.SpiTest),内容是要应用的实现类( com.test.SpiTestImpl)。

public interface SpiTest {void test();}public class SpiTestImpl implements SpiTest {@Overridepublic void test() {System.out.println("test");}}

然后在我们的应用程序中使用 ServiceLoader来加载配置文件中指定的实现。

public static void main(String[] args) {ServiceLoader<SpiTest> load = ServiceLoader.load(SpiTest.class);SpiTest next = load.iterator().next();next.test();}

这便是 spi 的使用方式了,简约而不简单。

 

spi 技术的应用

那这一项技术有哪些方面的应用呢?最直接的 jdbc 中我们需要指定数据库驱动的全限定名,这便是 spi 技术。还有不少框架比如 dubbo ,都会预留 spi 扩展点比如:dubbo spi

为什么要这么做呢?在 Spring 框架中我们注入一个 bean 很容易,通过注解或者 xml 配置即可,然后在其他的地方就能使用这个 bean 。在非 Spring 框架下,我们想要有同样的效果就可以考虑 spi 技术了。

写过 SpringBoot 的 starter 的都知道,需要在 src/main/resources/ 下建立 /META-INF/spring.factories 文件。这其实也是一种spi技术的变形。

 

jar 机制

通常项目中我们打 jar 包都是通过 maven 来进行的,导致很多人忽略了这个东西的存在,就像很多人不知道 jdb.exe 是啥玩意一样。下面我们不借助任何工具来打一个 jar 包并对 jar 文件结构进行解析。

命令行打 jar 包

首先我们建立一个普通的 java 项目,新建几个 class 类,然后在根目录下新建 META-INF/MAINFEST.MF这个文件包含了 jar 的元信息,当我们执行 java -jar 的时候首先会读取该文件的信息做相关的处理。我们来看看这个文件中可以配置哪些信息 :

  • Manifest-Version:用来定义 manifest 文件的版本,例如:Manifest-Version: 1.0

  • Main-Class:定义 jar 文件的入口类,该类必须是一个可执行的类,一旦定义了该属性即可通过 java -jar x.jar 来运行该 jar 文件。

  • Class-Path:指定该 jar 包所依赖的外部 jar 包,以当前 jar 包所在的位置为相对路径,无法指定 jar 包内部的 jar 包

  • 签名相关属性,包括 Name, Digest-Algorithms, SHA-Digest 等

定义好元信息之后我们就可以打 jar 包了,以下是打包的一些常用命令

  • 默认打包

生成的test.jar中就含test目录和jar自动生成的META-INF目录(内含MAINFEST.MF清单文件)

  1. jar -cvf test.jar test

  • 查看包内容

  1. jar -tvf test.jar

  • 解压jar包

  1. jar -xvf test.jar

  • 提取jar包部分内容

  1. jar -xvf test.jar test\test.class

  • 追加内容到jar包

追加 MAINFEST.MF 清单文件以外的文件,会追加整个目录结构

  1. jar -uvf test.jar other\ss.class

  • 追加清单文件

会追加整个目录结构( test.jar 会包含 META-INF 目录)

  1. jar -uMvf test.jar META-INF\MAINFEST.MF

  • 创建自定义MAINFEST.MF的jar包

  1. jar -cMvf test.jar test META-INF

通过 -m 选项配置自定义 MAINFEST.MF 文件时,自定义MAINFEST.MF 文件必须在位于工作目录下才可以

  1. jar -cmvf MAINFEST.MF test.jar test

jar 运行的过程

jar 运行过程和类加载机制有关,而类加载机制又和我们自定义的类加载器有关,现在我们先来了解一下双亲委派模式。

java 中类加载器分为三个:

  • BootstrapClassLoader 负责加载 ${JAVA_HOME}/jre/lib 部分 jar 包

  • ExtClassLoader 加载 ${JAVA_HOME}/jre/lib/ext 下面的 jar 包

  • AppClassLoader 加载用户自定义 -classpath 或者 Jar 包的 Class-Path 定义的第三方包

类的生命周期为:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using) 和 卸载(Unloading)七个阶段。

当我们执行 java -jar 的时候 jar 文件以二进制流的形式被读取到内存,但不会加载到 jvm 中,类会在一个合适的时机加载到虚拟机中。类加载的时机:

  • 遇到 new、getstatic、putstatic 或 invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先对其进行初始化。这四条指令的最常见的 Java 代码场景是使用 new 关键字实例化对象的时候,读取或设置一个类的静态字段调用一个类的静态方法的时候。

  • 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类。

当触发类加载的时候,类加载器也不是直接加载这个类。首先交给 AppClassLoader ,它会查看自己有没有加载过这个类,如果有直接拿出来,无须再次加载,如果没有就将加载任务传递给 ExtClassLoader ,而 ExtClassLoader 也会先检查自己有没有加载过,没有又会将任务传递给 BootstrapClassLoader ,最后 BootstrapClassLoader 会检查自己有没有加载过这个类,如果没有就会去自己要寻找的区域去寻找这个类,如果找不到又将任务传递给 ExtClassLoader ,以此类推最后才是 AppClassLoader 加载我们的类。这样做是确保类只会被加载一次。通常我们的类加载器只识别 classpath (这里的 classpath 指项目根路径,也就是 jar 包内的位置)下 .class 文件。jar 中其他的文件包括 jar 包被当做了资源文件,而不会去读取里面的 .class 文件。但实际上我们可以通过自定义类加载器来实现一些特别的操作

 

Tomcat 的类加载器

Tomcat 的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个 web 应用自己的类加载器(WebAppClassLoader) 会优先加载,加载不到时再交给 commonClassLoader 走双亲委托。

tomcat 的类加载器:

  • Common 类加载器:负责加载 /common 目录的类库,这儿存放的类库可被 tomcat 以及所有的应用使用。

  • Catalina 类加载器:负责加载 /server 目录的类库,只能被 tomcat 使用。

  • Shared 类加载器:负载加载 /shared 目录的类库,可被所有的 web 应用使用,但 tomcat 不可使用。

  • WebApp 类加载器:负载加载单个 Web 应用下 classes 目录以及 lib 目录的类库,只能当前应用使用。

  • Jsp 类加载器:负责加载 Jsp ,每一个 Jsp 文件都对应一个 Jsp 加载器。

我们将一堆 jar 包放到 tomcat 的项目文件夹下, tomcat 运行的时候能加载到这些 jar 包的 class 就是因为这些类加载器对读取到的二进制数据进行处理解析从中拿到了需要的类

SpringBoot 的 jar 包

当我们将一个 SpringBoot 项目打好包之后,不妨解压看看里面的结构是什么样子的的

  1. run.jar

  2. |——org

  3. | |——springframework

  4. | |——boot

  5. | |——loader

  6. | |——JarLauncher.class

  7. | |——Launcher.class

  8. |——META-INF

  9. | |——MANIFEST.MF

  10. |——BOOT-INF

  11. | |——class

  12. | |——Main.class

  13. | |——Begin.class

  14. | |——lib

  15. | |——commons.jar

  16. | |——plugin.jar

  17. | |——resource

  18. | |——a.jpg

  19.  

  20. | |——b.jpg

classpath 可加载的类只有 JarLauncher.classLauncher.classMain.classBegin.class。在 BOOT-INF/libBOOT-INF/class 里面的文件不属于 classloader 搜素对象直接访问的话会报 NoClassDefDoundErr 异常。Jar 包里面的资源以 Stream 的形式存在(他们本就处于 Jar 包之中),java 程序时可以访问到的。当 springboot 运行 main 方法时在 main 中会运行 org.springframework.boot.loader.JarLauncherLauncher.class 这两个个加载器(你是否还及得前文提到过得 spi 技术),这个加载器去加载受 stream 中的 jar 包中的 class。这样就实现了加载 jar 包中的 jar 这个功能否则正常的类加载器是无法加载 jar 包中的 jar 的 class 的,只会根据 MAINFEST.MF 来加载 jar 外部的 jar 来读取里面的 class。

如何自定义类加载器

public class MyClassLoader extends ClassLoader{private String classpath;public MyClassLoader(String classpath) {this.classpath = classpath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 该方法是根据一个name加载一个类,我们可以使用一个流来读取path中的文件然后从文件中解析出class来}}
调用 defineClass() 方法加载类
public static void main(String []args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{//自定义类加载器的加载路径MyClassLoader myClassLoader=new MyClassLoader("D:\\lib");//包名+类名Class c=myClassLoader.loadClass("com.test.Test")if(c!=null){// 做点啥}}

 

总结

本文从比较基础的层面解读了我们频繁使用却大部分人不是很了解的两个知识点—— spi 和 jar 机制。希望大家看完这篇文章后能对 SpringBoot 中的一些“黑魔法”有更深入的了解,而不是停留在表面。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/323233.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nssl1270-创世纪【树形dp,基环树】

正题 题目大意 每个物品有一个可以限制的物品&#xff0c;要求一个集合内所有的物品都有一个不在集合内物品限制。求这个集合可以保护的最多物品 解题思路 类似没有上司的舞会 其实就是在基环树森林&#xff0c;我们可以利用二次树形dp的方法。 先找到环&#xff0c;然后强行…

从头编写 asp.net core 2.0 web api 基础框架 (5) EF CRUD

Github源码地址&#xff1a;https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratch 这是第一大部分的最后一小部分。要完成CRUD的操作。 Repository Pattern 我们可以直接在Controller访问DbContext&#xff0c;但是可能会有一些问题: …

做这个网站原因之二

讲讲我最近干了什么事吧以及以后该干啥 其实大部分我学到的东西&#xff0c;都写在博客上了&#xff0c;可以通过博客看到一个人的学习轨迹。除了这些&#xff0c;我也尝试拍些视频去学习一些新的东西。视频剪辑&#xff0c;特效什么的都在尝试。 有个视频我也只是随便尝试尝试…

ssl提高组周四备考赛【2018.11.1】

前言 呆学校呆4天依旧不想复习期中&#xff0c;期中考凉透了。 成绩 RankRankRankPersonPersonPersonScoreScoreScoreAAABBBCCC1112014lyk2014lyk2014lyk2002002001001001007070703030302222017wyc2017wyc2017wyc1801801801001001008080800003332017xjq2017xjq2017xjq17017017…

spring cloud+dotnet core搭建微服务架构:配置中心续(五)

前言 上一章最后讲了&#xff0c;更新配置以后需要重启客户端才能生效&#xff0c;这在实际的场景中是不可取的。由于目前Steeltoe配置的重载只能由客户端发起&#xff0c;没有实现处理程序侦听服务器更改事件&#xff0c;所以还没办法实现彻底实现这一功能。这一章的例子&…

聊聊最近吧

讲讲我最近干了什么事吧以及以后该干啥 其实大部分我学到的东西&#xff0c;都写在博客上了&#xff0c;可以通过博客看到一个人的学习轨迹。除了这些&#xff0c;我也尝试拍些视频去学习一些新的东西。视频剪辑&#xff0c;特效什么的都在尝试。 有个视频我也只是随便尝试尝试…

C# 实现虚拟数字人

随着Ai技术的提升和应用&#xff0c;虚拟数字人被广泛应用到各行各业中。为我们的生活和工作提供了非常多的便利和色彩。 通过设置虚拟数字人的位置大小&#xff0c;可以让数字人可以在电脑屏幕各个位置显示&#xff1a; 虚拟数字人素材&#xff1a; 虚拟数字人(实际有语音&am…

AspectCore.Extension.Reflection : .NET Core反射扩展库

在从零实现AOP的过程中&#xff0c;难免会需要大量反射相关的操作&#xff0c;虽然在.net 4.5/.net core中反射的性能有了大幅的优化&#xff0c;但为了追求极致性能&#xff0c;自己实现了部分反射的替代方案&#xff0c;包括构造器调用、方法调用、字段读写&#xff0c;属性读…

P2468-[SDOI2010]粟粟的书架【主席树,二维前缀和】

正题 评测记录:https://www.luogu.org/recordnew/lists?uid52918&pidP2468 题目大意 对于每一个询问&#xff0c;求一个区域内最少取多少个数使它们的和不小于a。 解题思路 我们肯定取最大的 对于前50%的数据&#xff0c;用 sumi,j,ksum_{i,j,k}sumi,j,k​表示(1,1)(1,…

【分享】通过手游赚¥

这本来是个回答&#xff0c;但是在知乎被删了&#xff0c;于是我决定还是在自己网站再发一份&#xff0c;特么知乎店大欺人&#xff0c;我一这么水回答&#xff0c;还被认为是广告营销 首先说明一点&#xff0c;这个完全是自我经历&#xff0c;一种分享吧。觉得假的自然假。 我…

C++描述杭电OJ 2008.数值统计 ||

C描述杭电OJ 2008.数值统计 || Problem Description 统计给定的n个数中&#xff0c;负数、零和正数的个数。 Input 输入数据有多组&#xff0c;每组占一行&#xff0c;每行的第一个数是整数n&#xff08;n<100&#xff09;&#xff0c;表示需要统计的数值的个数&#xff…

Azure Cosmos DB技术性解读

Azure Cosmos DB是微软公司打造的一项全球分布式、横向分区、多模型数据库服务。该服务允许客户弹性&#xff08;及独立形式&#xff09;跨越任意数量地理服务区对吞吐量与存储进行扩展。Azure Cosmos DB可立足第99百分位比例提升99.99%高可用性水平&#xff0c;提供可预测吞吐…

NOIP2018-普及总结

前言 原本说要去提高的&#xff0c;然后市里瞎搞&#xff0c;就去不了了QVQQVQQVQ。 总结 这次一看感觉题目比较难&#xff0c;所以基本凉凉… 首先这次有很多失误&#xff0c;特别是T2T2T2&#xff0c;其实很容易就分析出要用longlonglong\ \ longlong long的&#xff0c;但…

语音服务——阿里云

一、语音通知 语音通知是指通过调用语音呼叫的API&#xff0c;从运营商网络向指定号码发起一通呼叫&#xff0c;呼叫被应答后&#xff0c;播放一段指定的音频。 根据音频形式的不同&#xff0c;语音通知的API分为两个&#xff1a; 若播放的音频为固定内容的音频文件&#xf…

【博客】csdn搬家到wordpress

在wordpress的插件中搜索cnblogs2wp&#xff0c;安装后&#xff0c;在工具->导入->选博客搬家&#xff0c; 遇到了些问题 总是遇到博客地址不对 https://blog.csdn.net/weixin_43560272 首先修改了后缀 这是我的博客首页地址绝对没错的啊 后缀绝对改了的 总是说地址…

C++描述杭电OJ 2009.求数列的和 ||

C描述杭电OJ 2009.求数列的和 || Problem Description 数列的定义如下&#xff1a; 数列的第一项为n&#xff0c;以后各项为前一项的平方根&#xff0c;求数列的前m项的和。 Input 输入数据有多组&#xff0c;每组占一行&#xff0c;由两个整数n&#xff08;n<10000&…

P2323-[HNOI2006]公路修建问题【并查集】

正题 评测记录:https://www.luogu.org/recordnew/lists?uid52918&pidP2323 题目大意 有n个点&#xff0c;m条边&#xff0c;对于每条边修二级公路和一级公路修建价格不同(一级比二级贵)。要求留下n-1条边使其成为一棵树且至少有k条一级公路&#xff0c;要求最贵的公路价…

Microsoft加入量子计算的竞争

Microsoft在Ignite大会上宣布了自己的量子计算新平台的预览版&#xff0c;并公开了借助近期粒子物理学方面的进展推出拓扑量子计算机的计划。 Microsoft的量子计算平台预览版将包括一个量子计算模拟器&#xff0c;以及一种集成在Visual Studio中的量子计算编程新语言。据Micros…

Window系统多硬盘设置新引导盘

一、系统启动过程 电脑通电后&#xff0c;首先是启动BIOS程序&#xff0c;BIOS自检完毕后&#xff0c;找到硬盘上的主引导记录MBR&#xff0c;MBR读取DPT&#xff08;分区表&#xff09;&#xff0c;从中找出活动的主分区&#xff0c;然后读取活动主分区的PBR&#xff08;分区引…

【Java】jdk和eclipse下载安装

&#xff08;以前忘了写这方面的安装&#xff09; 附一段测试java环境的代码 public class hello {public static void main(String[] args) {System.out.println("Hello World dsadasdaSasdasd");} }jdk安装&#xff1a; 这里我采用不同方式 直接360软件管家搜索jd…