使用第三方jar_Java 中的 jar ,天天见,可是你知道它的运行机制吗?

点击上方“Java之间”,选择“置顶或者星标”

你关注的就是我关心的!

224250a918bcf153da87e503ff6c04e5.png

作者:muggle

博客地址:http://muggle.javaboy.org/

今天介绍两个大家每天都在用但是却很少去了解它的知识点: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 {

@Override

public 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清单文件)

jar -cvf test.jar test

  • 查看包内容

jar -tvf test.jar

  • 解压jar包

jar -xvf test.jar

  • 提取jar包部分内容

jar -xvf test.jar test\test.class

  • 追加内容到jar包

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

jar -uvf test.jar other\ss.class

  • 追加清单文件

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

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

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

jar -cMvf test.jar test META-INF

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

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 项目打好包之后,不妨解压看看里面的结构是什么样子的的

run.jar

|——org

| |——springframework

| |——boot

| |——loader

| |——JarLauncher.class

| |——Launcher.class

|——META-INF

| |——MANIFEST.MF

|——BOOT-INF

| |——class

| |——Main.class

| |——Begin.class

| |——lib

| |——commons.jar

| |——plugin.jar

| |——resource

| |——a.jpg

| |——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。

如何自定义类加载器
  • 继承 ClassLoader 重写 findClass() 方法

public class MyClassLoader extends ClassLoader{

private String classpath;

public MyClassLoader(String classpath) {

this.classpath = classpath;

}

@Override

protected 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 中的一些“黑魔法”有更深入的了解,而不是停留在表面。

e68803737c4f722e4aff3b952125ab06.png

最近热文阅读:

1、JVM 的那些设置参数你都知道吗

2、教你几个有趣又好玩的Linux命令

3、IDEA 中比较骚的技巧!你可能没用过

4、面试官:你是怎么用explain分析sql执行性能的?

5、用了10多年的 Tomcat 居然有bug,这能忍?

6、前后分离接口规范

7、MySQL 为什么建议使用自增主键?

8、Java面试官:给Java面试者的八点建议

9、面试官:MyBatis你只写了接口为啥就能执行SQL啊?

10、面试题总结:可能是全网最好的MySQL重要知识点

079eeb6a21eb3c08387eee3efb18741a.png

关注公众号,你想要的Java都在这里

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
DirectX修复工具(DirectX Repair)是一款系统级工具软件,简便易用。本程序为绿色版,无需安装,可直接运行。 本程序的主要功能是检测当前系统的DirectX状态,如果发现异常则进行修复。程序主要针对0xc000007b问题设计,可以完美修复该问题。本程序中包含了最新版的DirectX redist(Jun2010),并且全部DX文件都有Microsoft的数字签名,安全放心。 本程序为了应对一般电脑用户的使用,采用了易用的一键式设计,只要点击主界面上的“检测并修复”按钮,程序就会自动完成校验、检测、下载、修复以及注册的全部功能,无需用户的介入,大大降低了使用难度。在常规修复过程中,程序还会自动检测DirectX加速状态,在异常时给予用户相应提示。 本程序适用于多个操作系统,如Windows XP(需先安装.NET 2.0,详情请参阅“致Windows XP用户.txt”文件)、Windows Vista、Windows 7、Windows 8、Windows 8.1、Windows 8.1 Update、Windows 10,同时兼容32位操作系统和64位操作系统。本程序会根据系统的不同,自动调整任务模式,无需用户进行设置。 本程序的V4.0版分为标准版、增强版以及在线修复版。所有版本都支持修复DirectX的功能,而增强版则额外支持修复c++的功能。在线修复版功能与标准版相同,但其所需的数据包需要在修复时自动下载。各个版本之间,主程序完全相同,只是其配套使用的数据包不同。因此,标准版和在线修复版可以通过补全扩展包的形式成为增强版。本程序自V3.5版起,自带扩展功能。只要在主界面的“工具”菜单下打开“选项”对话框,找到“扩展”标签,点击其中的“开始扩展”按钮即可。扩展过程需要Internet连接,扩展成功后新的数据包可自动生效。扩展用时根据网络速度不同而不同,最快仅需数秒,最慢需要数分钟,烦请耐心等待。如扩展失败,可点击“扩展”界面左上角小锁图标切换为加密连接,即可很大程度上避免因防火墙或其他原因导致的连接失败。 本程序自V2.0版起采用全新的底层程序架构,使用了异步多线程编程技术,使得检测、下载、修复单独进行,互不干扰,快速如飞。新程序更改了自我校验方式,因此使用新版本的程序时不会再出现自我校验失败的错误;但并非取消自我校验,因此程序安全性与之前版本相同,并未降低。 程序有更新系统c++功能。由于绝大多数软件运行时需要c++的支持,并且c++的异常也会导致0xc000007b错误,因此程序在检测修复的同时,也会根据需要更新系统中的c++组件。自V3.2版本开始使用了全新的c++扩展包,可以大幅提高工业软件修复成功的概率。修复c++的功能仅限于增强版,标准版及在线修复版在系统c++异常时(非丢失时)会提示用户使用增强版进行修复。除常规修复外,新版程序还支持C++强力修复功能。当常规修复无效时,可以到本程序的选项界面内开启强力修复功能,可大幅提高修复成功率。请注意,请仅在常规修复无效时再使用此功能。 程序有两种窗口样式。正常模式即默认样式,适合绝大多数用户使用。另有一种简约模式,此时窗口将只显示最基本的内容,修复会自动进行,修复完成10秒钟后会自动退出。该窗口样式可以使修复工作变得更加简单快速,同时方便其他软件、游戏将本程序内嵌,即可进行无需人工参与的快速修复。开启简约模式的方法是:打开程序所在目录下的“Settings.ini”文件(如果没有可以自己创建),将其中的“FormStyle”一项的值改为“Simple”并保存即可。 新版程序支持命令行运行模式。在命令行中调用本程序,可以在路径后直接添加命令进行相应的设置。常的命令有7类,分别是设置语言的命令、设置窗口模式的命令,设置安全级别的命令、开启强力修复的命令、设置c++修复模式的命令、控制Direct加速的命令、显示版权信息的命令。具体命令名称可以通过“/help”或“/?”进行查询。 程序有高级筛选功能,开启该功能后用户可以自主选择要修复的文件,避免了其他不必要的修复工作。同时,也支持通过文件进行辅助筛选,只要在程序目录下建立“Filter.dat”文件,其中的每一行写一个需要修复文件的序号即可。该功能仅针对高级用户使用,并且必须在正常窗口模式下才有效(简约模式时无效)。 本程序有自动记录日志功能,可以记录每一次检测修复结果,方便在出现问题时,及时分析和查找原因,以便找到解决办法。 程序的“选项”对话框中包含了7项高级功能。点击"常规”选项卡可以调整程序的基本运行情况,包括日志记录、安全级别控制、调试模式开启等。只有开启调试模式后才能在C
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页