日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2023/12/3 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 jar包天天见,可是你知道它的运行机制吗 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)載自??jar包天天見(jiàn),可是你知道它的運(yùn)行機(jī)制嗎

今天介紹兩個(gè)大家每天都在用但是卻很少去了解它的知識(shí)點(diǎn):spi 和 jar 運(yùn)行機(jī)制,廢話(huà)不多說(shuō),開(kāi)始正題。

spi

spi 是 Java 提供的一套用來(lái)被第三方實(shí)現(xiàn)或者擴(kuò)展的 API ,它可以用來(lái)啟用框架擴(kuò)展和替換組件。spi 機(jī)制是這樣的:讀取 META-INF/services/ 目錄下的元信息,然后 ServiceLoader 根據(jù)信息加載對(duì)應(yīng)的類(lèi),你可以在自己的代碼中使用這個(gè)被加載的類(lèi)。要使用 Java SPI,需要遵循如下約定:

  • 當(dāng)服務(wù)提供者提供了接口的一種具體實(shí)現(xiàn)后,在 jar 包的 META-INF/services 目錄下創(chuàng)建一個(gè)以 “接口全限定名” 命名的文件,內(nèi)容為實(shí)現(xiàn)類(lèi)的全限定名;

  • 接口實(shí)現(xiàn)類(lèi)所在的 jar 包放在主程序的 classpath 中;

  • 主程序通過(guò) java.util.ServiceLoder 動(dòng)態(tài)裝載實(shí)現(xiàn)模塊,它通過(guò)掃描 META-INF/services 目錄下的配置文件找到實(shí)現(xiàn)類(lèi)的全限定名,把類(lèi)加載到 JVM ;

  • SPI 的實(shí)現(xiàn)類(lèi)必須攜帶一個(gè)不帶參數(shù)的構(gòu)造方法;

現(xiàn)在我們來(lái)簡(jiǎn)單的使用一下吧。

?

spi 使用示例

建一個(gè) maven 項(xiàng)目,定義一個(gè)接口 ( com.test.SpiTest ),并實(shí)現(xiàn)該接口( com.test.SpiTestImpl);然后在 src/main/resources/ 下建立 /META-INF/services 目錄, 新增一個(gè)以接口命名的文件 ( com.test.SpiTest),內(nèi)容是要應(yīng)用的實(shí)現(xiàn)類(lèi)( com.test.SpiTestImpl)。

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

然后在我們的應(yīng)用程序中使用 ServiceLoader來(lái)加載配置文件中指定的實(shí)現(xiàn)。

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

這便是 spi 的使用方式了,簡(jiǎn)約而不簡(jiǎn)單。

?

spi 技術(shù)的應(yīng)用

那這一項(xiàng)技術(shù)有哪些方面的應(yīng)用呢?最直接的 jdbc 中我們需要指定數(shù)據(jù)庫(kù)驅(qū)動(dòng)的全限定名,這便是 spi 技術(shù)。還有不少框架比如 dubbo ,都會(huì)預(yù)留 spi 擴(kuò)展點(diǎn)比如:dubbo spi

為什么要這么做呢?在 Spring 框架中我們注入一個(gè) bean 很容易,通過(guò)注解或者 xml 配置即可,然后在其他的地方就能使用這個(gè) bean 。在非 Spring 框架下,我們想要有同樣的效果就可以考慮 spi 技術(shù)了。

寫(xiě)過(guò) SpringBoot 的 starter 的都知道,需要在 src/main/resources/ 下建立 /META-INF/spring.factories 文件。這其實(shí)也是一種spi技術(shù)的變形。

?

jar 機(jī)制

通常項(xiàng)目中我們打 jar 包都是通過(guò) maven 來(lái)進(jìn)行的,導(dǎo)致很多人忽略了這個(gè)東西的存在,就像很多人不知道 jdb.exe 是啥玩意一樣。下面我們不借助任何工具來(lái)打一個(gè) jar 包并對(duì) jar 文件結(jié)構(gòu)進(jìn)行解析。

命令行打 jar 包

首先我們建立一個(gè)普通的 java 項(xiàng)目,新建幾個(gè) class 類(lèi),然后在根目錄下新建 META-INF/MAINFEST.MF這個(gè)文件包含了 jar 的元信息,當(dāng)我們執(zhí)行 java -jar 的時(shí)候首先會(huì)讀取該文件的信息做相關(guān)的處理。我們來(lái)看看這個(gè)文件中可以配置哪些信息 :

  • Manifest-Version:用來(lái)定義 manifest 文件的版本,例如:Manifest-Version: 1.0

  • Main-Class:定義 jar 文件的入口類(lèi),該類(lèi)必須是一個(gè)可執(zhí)行的類(lèi),一旦定義了該屬性即可通過(guò) java -jar x.jar 來(lái)運(yùn)行該 jar 文件。

  • Class-Path:指定該 jar 包所依賴(lài)的外部 jar 包,以當(dāng)前 jar 包所在的位置為相對(duì)路徑,無(wú)法指定 jar 包內(nèi)部的 jar 包

  • 簽名相關(guān)屬性,包括?Name,?Digest-Algorithms,?SHA-Digest?等

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

  • 默認(rèn)打包

生成的test.jar中就含test目錄和jar自動(dòng)生成的META-INF目錄(內(nèi)含MAINFEST.MF清單文件)

  • jar -cvf test.jar test

    • 查看包內(nèi)容

  • jar -tvf test.jar

    • 解壓jar包

  • jar -xvf test.jar

    • 提取jar包部分內(nèi)容

  • jar -xvf test.jar test\test.class

    • 追加內(nèi)容到j(luò)ar包

    追加 MAINFEST.MF 清單文件以外的文件,會(huì)追加整個(gè)目錄結(jié)構(gòu)

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

    • 追加清單文件

    會(huì)追加整個(gè)目錄結(jié)構(gòu)( test.jar 會(huì)包含 META-INF 目錄)

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

    • 創(chuàng)建自定義MAINFEST.MF的jar包

  • jar -cMvf test.jar test META-INF

  • 通過(guò) -m 選項(xiàng)配置自定義 MAINFEST.MF 文件時(shí),自定義MAINFEST.MF 文件必須在位于工作目錄下才可以

  • jar -cmvf MAINFEST.MF test.jar test

  • jar 運(yùn)行的過(guò)程

    jar 運(yùn)行過(guò)程和類(lèi)加載機(jī)制有關(guān),而類(lèi)加載機(jī)制又和我們自定義的類(lèi)加載器有關(guān),現(xiàn)在我們先來(lái)了解一下雙親委派模式。

    java 中類(lèi)加載器分為三個(gè):

    • BootstrapClassLoader 負(fù)責(zé)加載?${JAVA_HOME}/jre/lib?部分 jar 包

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

    • AppClassLoader 加載用戶(hù)自定義 -classpath 或者 Jar 包的 Class-Path 定義的第三方包

    類(lèi)的生命周期為:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using) 和 卸載(Unloading)七個(gè)階段。

    當(dāng)我們執(zhí)行 java -jar 的時(shí)候 jar 文件以二進(jìn)制流的形式被讀取到內(nèi)存,但不會(huì)加載到 jvm 中,類(lèi)會(huì)在一個(gè)合適的時(shí)機(jī)加載到虛擬機(jī)中。類(lèi)加載的時(shí)機(jī):

    • 遇到 new、getstatic、putstatic 或 invokestatic 這四條字節(jié)碼指令時(shí),如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先對(duì)其進(jìn)行初始化。這四條指令的最常見(jiàn)的 Java 代碼場(chǎng)景是使用 new 關(guān)鍵字實(shí)例化對(duì)象的時(shí)候,讀取或設(shè)置一個(gè)類(lèi)的靜態(tài)字段調(diào)用一個(gè)類(lèi)的靜態(tài)方法的時(shí)候。

    • 使用 java.lang.reflect 包的方法對(duì)類(lèi)進(jìn)行反射調(diào)用的時(shí)候,如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。

    • 當(dāng)初始化一個(gè)類(lèi)的時(shí)候,如果發(fā)現(xiàn)其父類(lèi)還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類(lèi)的初始化。

    • 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶(hù)需要指定一個(gè)要執(zhí)行的主類(lèi)(包含 main() 方法的那個(gè)類(lèi)),虛擬機(jī)會(huì)先初始化這個(gè)主類(lèi)。

    當(dāng)觸發(fā)類(lèi)加載的時(shí)候,類(lèi)加載器也不是直接加載這個(gè)類(lèi)。首先交給 AppClassLoader ,它會(huì)查看自己有沒(méi)有加載過(guò)這個(gè)類(lèi),如果有直接拿出來(lái),無(wú)須再次加載,如果沒(méi)有就將加載任務(wù)傳遞給 ExtClassLoader ,而 ExtClassLoader 也會(huì)先檢查自己有沒(méi)有加載過(guò),沒(méi)有又會(huì)將任務(wù)傳遞給 BootstrapClassLoader ,最后 BootstrapClassLoader 會(huì)檢查自己有沒(méi)有加載過(guò)這個(gè)類(lèi),如果沒(méi)有就會(huì)去自己要尋找的區(qū)域去尋找這個(gè)類(lèi),如果找不到又將任務(wù)傳遞給 ExtClassLoader ,以此類(lèi)推最后才是 AppClassLoader 加載我們的類(lèi)。這樣做是確保類(lèi)只會(huì)被加載一次。通常我們的類(lèi)加載器只識(shí)別 classpath (這里的 classpath 指項(xiàng)目根路徑,也就是 jar 包內(nèi)的位置)下 .class 文件。jar 中其他的文件包括 jar 包被當(dāng)做了資源文件,而不會(huì)去讀取里面的 .class 文件。但實(shí)際上我們可以通過(guò)自定義類(lèi)加載器來(lái)實(shí)現(xiàn)一些特別的操作

    ?

    Tomcat 的類(lèi)加載器

    Tomcat 的類(lèi)加載機(jī)制是違反了雙親委托原則的,對(duì)于一些未加載的非基礎(chǔ)類(lèi)(Object,String等),各個(gè) web 應(yīng)用自己的類(lèi)加載器(WebAppClassLoader) 會(huì)優(yōu)先加載,加載不到時(shí)再交給 commonClassLoader 走雙親委托。

    tomcat 的類(lèi)加載器:

    • Common 類(lèi)加載器:負(fù)責(zé)加載?/common?目錄的類(lèi)庫(kù),這兒存放的類(lèi)庫(kù)可被 tomcat 以及所有的應(yīng)用使用。

    • Catalina 類(lèi)加載器:負(fù)責(zé)加載?/server?目錄的類(lèi)庫(kù),只能被?tomcat?使用。

    • Shared?類(lèi)加載器:負(fù)載加載?/shared?目錄的類(lèi)庫(kù),可被所有的?web?應(yīng)用使用,但?tomcat?不可使用。

    • WebApp?類(lèi)加載器:負(fù)載加載單個(gè)?Web?應(yīng)用下?classes?目錄以及?lib?目錄的類(lèi)庫(kù),只能當(dāng)前應(yīng)用使用。

    • Jsp?類(lèi)加載器:負(fù)責(zé)加載?Jsp?,每一個(gè)?Jsp?文件都對(duì)應(yīng)一個(gè)?Jsp?加載器。

    我們將一堆 jar 包放到 tomcat 的項(xiàng)目文件夾下, tomcat 運(yùn)行的時(shí)候能加載到這些 jar 包的 class 就是因?yàn)檫@些類(lèi)加載器對(duì)讀取到的二進(jìn)制數(shù)據(jù)進(jìn)行處理解析從中拿到了需要的類(lèi)

    SpringBoot 的 jar 包

    當(dāng)我們將一個(gè) SpringBoot 項(xiàng)目打好包之后,不妨解壓看看里面的結(jié)構(gòu)是什么樣子的的

  • 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 可加載的類(lèi)只有 JarLauncher.class, Launcher.class, Main.class, Begin.class。在 BOOT-INF/lib 和 BOOT-INF/class 里面的文件不屬于 classloader 搜素對(duì)象直接訪(fǎng)問(wèn)的話(huà)會(huì)報(bào) NoClassDefDoundErr 異常。Jar 包里面的資源以 Stream 的形式存在(他們本就處于 Jar 包之中),java 程序時(shí)可以訪(fǎng)問(wèn)到的。當(dāng) springboot 運(yùn)行 main 方法時(shí)在 main 中會(huì)運(yùn)行 org.springframework.boot.loader.JarLauncher 和 Launcher.class 這兩個(gè)個(gè)加載器(你是否還及得前文提到過(guò)得 spi 技術(shù)),這個(gè)加載器去加載受 stream 中的 jar 包中的 class。這樣就實(shí)現(xiàn)了加載 jar 包中的 jar 這個(gè)功能否則正常的類(lèi)加載器是無(wú)法加載 jar 包中的 jar 的 class 的,只會(huì)根據(jù) MAINFEST.MF 來(lái)加載 jar 外部的 jar 來(lái)讀取里面的 class。

    如何自定義類(lèi)加載器

    public class MyClassLoader extends ClassLoader{private String classpath;public MyClassLoader(String classpath) {this.classpath = classpath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 該方法是根據(jù)一個(gè)name加載一個(gè)類(lèi),我們可以使用一個(gè)流來(lái)讀取path中的文件然后從文件中解析出class來(lái)}} 調(diào)用 defineClass() 方法加載類(lèi) public static void main(String []args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException{//自定義類(lèi)加載器的加載路徑MyClassLoader myClassLoader=new MyClassLoader("D:\\lib");//包名+類(lèi)名Class c=myClassLoader.loadClass("com.test.Test")if(c!=null){// 做點(diǎn)啥}}

    ?

    總結(jié)

    本文從比較基礎(chǔ)的層面解讀了我們頻繁使用卻大部分人不是很了解的兩個(gè)知識(shí)點(diǎn)—— spi 和 jar 機(jī)制。希望大家看完這篇文章后能對(duì) SpringBoot 中的一些“黑魔法”有更深入的了解,而不是停留在表面。

    總結(jié)

    以上是生活随笔為你收集整理的jar包天天见,可是你知道它的运行机制吗的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。