<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          自定義類加載器解決jar沖突問題

          共 8328字,需瀏覽 17分鐘

           ·

          2021-11-18 21:38

          碎碎念

          好吧,我是一個沒有意志力,貪圖安逸的人,懶惰、不思進(jìn)取的人!好久沒有發(fā)過文了,為啥?一個字:懶!

          引言

          目前維護(hù)的項(xiàng)目是一個非常非常原始的項(xiàng)目,這里可以發(fā)揮你的想象力,你能想到多原始就有多原始……

          最近需要在原有環(huán)境中重新搭建一個新的工程,當(dāng)然這個工程與原來其他七八十來個工程共用一個 JVM 環(huán)境。有經(jīng)驗(yàn)的老鐵看到這里腦海里肯定會有一堆的問題……好吧,其他暫不討論,這次主要是解決 jar 沖突的問題。

          因?yàn)樵泄こ讨杏玫揭粋€ jar 是低版本的,而新工程也用到這個 jar,但是需要高版本的。其他工程公用的低版本肯定是不能丟的,影響太大。那如果再引入高版本的 jar,肯定就會出現(xiàn)版本沖突問題。又因?yàn)闀r(shí)間緊迫,且為了安全高效,領(lǐng)導(dǎo)決定這個新工程按照原有模式進(jìn)行開發(fā),就是沿用原有工程的技術(shù)框架,能不改就不改,畢竟以后生產(chǎn)環(huán)境也是要在一起發(fā)布的,不能出現(xiàn)亂七八糟的問題。有些老鐵就想到解決版本沖突 maven 不就可以幫我們管理引用的 jar 嗎?那我要告訴你老鐵,你真的還很年輕!俺們現(xiàn)在的技術(shù)架構(gòu),沒有引入類似 maven 這項(xiàng)高級技術(shù)。別操……閉嘴!聽我繼續(xù)吹!

          如果再為它單獨(dú)申請一臺服務(wù)器,明顯也是不可能的,太不劃算(但事實(shí)上他們真的要這么干?。?。我個人認(rèn)為也是沒必要的。因?yàn)檫@么一個小的項(xiàng)目,而且技術(shù)上也沒有提升,其他不說,就單單因?yàn)檠赜昧四敲蠢系募夹g(shù)框架,我覺得它就不值得一臺新服務(wù)器,內(nèi)心感覺那樣很浪費(fèi)資源!

          怎么辦?!事情還是要繼續(xù)做的,稍作思考后,我想到了自定義類加載器!用它來解決 jar 版本沖突應(yīng)該可以的吧?

          網(wǎng)上先搜搜了解下,但網(wǎng)上那些博客已經(jīng)被資本侵蝕了,有用的不多,沒用的一堆,但可以告訴你這個東西有人做過,可以試試!試試就試試,正好也好久沒用復(fù)習(xí)了!

          快速回顧

          這里首先強(qiáng)烈推薦一本經(jīng)典的關(guān)于系統(tǒng)了解 JVM 的書籍,想必你已經(jīng)猜了,那就是周老板的《深入理解Java虛擬機(jī)》,現(xiàn)在已經(jīng)出了第三版了,我之前看的是第二版,準(zhǔn)準(zhǔn)準(zhǔn)準(zhǔn)準(zhǔn)準(zhǔn)準(zhǔn)備入手第三版看看,肯定也是一場盛宴,第二版每次想起都回味無窮!強(qiáng)烈推薦大家有條件的看看!?。『昧?,廢話不多說,開擼!

          7b03e9f8071e55283df3ca9a689d54a2.webp

          類加載器是什么

          類加載器負(fù)責(zé)實(shí)現(xiàn)類的加載動作,通過一個類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流。

          每一個類加載器,都有一個獨(dú)立的類名稱空間。即:在一個 JVM 中比較兩個類是否“相等”,只有這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個 Class 文件,被同一個虛擬機(jī)加載,只要加載他們的類加載器不同,這兩個類就必定不相等。

          簡單一句話,在同一個 JVM 環(huán)境中,如果要指定一個類,需要加載它的類加載器和其全限定名一起。

          類加載器分類

          從虛擬機(jī)的角度來講,只存在兩種不同的類加載器:

          1. 啟動類加載器(Bootstrap Classloader):使用 C++ 語言實(shí)現(xiàn),是虛擬機(jī)自身的一部分;

          負(fù)責(zé)加載在 /lib 目錄中,或者被 JVM 的 -Xbootclasspath 參數(shù)所指定的路徑中,并且是 JVM 識別的(JVM 僅按照文件名識別,比如:rt.jar,名字不符合的類庫即使放在 lib 目錄下也不會被加載)類庫加載到虛擬機(jī)內(nèi)存中。此加載器無法被 Java 程序直接引用。

          其他類加載器:由 Java 語言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全都繼承自 java.lang.ClassLoader 抽象類:

          1. 擴(kuò)展類加載器(Extension Classloader):負(fù)責(zé)加載 /lib/ext 目錄中,或者被 java.ext.dirs 系統(tǒng)變量所指定的路徑中的所有類庫。開發(fā)者可以直接使用此類加載器。

          2. 應(yīng)用程序類加載器(Application ClassLoader):也稱為系統(tǒng)類加載器,負(fù)責(zé)加載用戶路徑(ClassPath)下所指定的類庫,也就是說我們平時(shí)所編寫的代碼都是通過此類加載器進(jìn)行加載的。開發(fā)者可以直接使用這個類加載器,如果應(yīng)用程序中沒有自定義類加載器,一般情況下,這個就是應(yīng)用程序中默認(rèn)的類加載器。

          我們應(yīng)用程序都是由以上 3 種類加載器相互配合進(jìn)行加載的,如果有必要,還可以自定義類加載器。通過繼承 java.lang.ClassLoader 實(shí)現(xiàn)自己的類加載器。在程序運(yùn)行期間動態(tài)加載 class 文件,體現(xiàn)了 Java 動態(tài)實(shí)時(shí)類裝入特性。

          這些類加載器之間的層次關(guān)系如下圖:

          d4e3ad96202f6cab46baa3afe2cfa5da.webp

          雙親委派模型

          不同類加載器之間的層次關(guān)系稱為類加載器的雙親委派模型。雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里的父子關(guān)系并非繼承,而是組合關(guān)系。

          雙親委派模型的工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個類委派給父類加載器去完成,每一層次的類加載器都是如此,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時(shí),子加載器才會嘗試自己去加載。但它并不是一個強(qiáng)制性的約束模型,而是 Java 設(shè)計(jì)者推薦給開發(fā)者的一種類加載器實(shí)現(xiàn)方式。

          使用雙親委派模型對于保證 Java 程序的穩(wěn)定運(yùn)行很重要。例如類 java.lang.Object,它存放在 rt.jar 之中,無論哪一個類加載器都要加載這個類,但最終都會委派給啟動類加載器來加載,因此 Object 類在程序的各種類加載器環(huán)境中都是同一個類。相反,如果沒有使用雙親委派模型,由各個類加載器自行加載的話,如果用戶自己編寫了一個稱為 java.lang.Object 的類,并放在 ClassPath 中,那么系統(tǒng)中將出現(xiàn)多個不同的 Object 類,Java 類型體系中最基礎(chǔ)的行為也就無法保證,應(yīng)用程序也將變得一片混亂。

          在雙親委派模型下,如果編寫一個與 rt.jar 類庫中已有類重名的 Java 類,你將會發(fā)現(xiàn)它可以正常發(fā)現(xiàn),但是永遠(yuǎn)無法被加載運(yùn)行。

          雙親委派模型的實(shí)現(xiàn)

          雖然雙親委派模型非常重要,但是其實(shí)現(xiàn)還是比較簡單,邏輯也比較清晰易懂。實(shí)現(xiàn)雙親委派模型的代碼都集中在 java.lang.ClassLoader 的 loadClass() 方法中。

          其首先檢查當(dāng)前類是否已經(jīng)被加載過,若沒有加載則調(diào)用父加載器的loadClass()方法,若父加載器為空則默認(rèn)使用啟動類加載器作為父加載器。如果父類加載失敗,拋出ClassNotFoundException異常后,再調(diào)用自己的findClass()方法進(jìn)行加載。

          protected Class loadClass(String name, boolean resolve)        throws ClassNotFoundException    {        synchronized (getClassLoadingLock(name)) {            /*             * 首先通過從已加載的類中查找當(dāng)前類,來檢查當(dāng)前類是否已經(jīng)被加載過,             * 在沒有被加載過的情況下,執(zhí)行加載邏輯             */            Class c = findLoadedClass(name);            if (c == null) {                long t0 = System.nanoTime();                try {                    // 父類加載器是否為空,如果不為空則委派父加載器加載,否則啟動類加載器加載                    if (parent != null) {                        c = parent.loadClass(name, false);                    } else {                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                    // 如果父類加載器拋出 ClassNotFoundException                     // 說明父類加載器無法完成加載請求                }                if (c == null) {                    /**                     * 在父類加載器無法完成加載的情況下,調(diào)用本身的 findClass 方法來進(jìn)行加載。                     * findClass 是抽象類,需要在子類實(shí)現(xiàn)                     */                    long t1 = System.nanoTime();                    c = findClass(name);
          // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }

          重點(diǎn)方法說明

          類加載器中最重要的方法有以下三個,也是自定義類加載器時(shí)需要重點(diǎn)關(guān)注的:

          • loadClass:如上,該方法主要實(shí)現(xiàn)了雙親委派模型,是類加載器的模板方法,JVM 在加載類的時(shí)候都是通過 ClassLoader 的 loadClass 方法來加載的。如果要想打破雙親委派模型,需要重寫 loadClass 方法。

          • findClass:類加載器加載類的具體實(shí)現(xiàn)方法。自定義類加載器通過實(shí)現(xiàn)這個方法來加載需要的類。

          • definedClass:在 findClass 中使用,通過調(diào)用傳入的一個 Class 文件的字節(jié)數(shù)組,就可以在方法區(qū)生成一個 Class 對象,也就是 findClass 實(shí)現(xiàn)了類加載的功能。

          特別提一下,URLClassLoader 是 JDK 已經(jīng)實(shí)現(xiàn)的從指定URL地址來加載的類加載器,這個 URL 可以是本地目錄,也可以是網(wǎng)絡(luò)地址。擴(kuò)展類加載器和應(yīng)用類加載器就是繼承自 URLClassLoader.java 這個類。

          自定義類加載器

          只說不練假把式,終于到本文的重點(diǎn)了。

          快速回顧之后,我首先想到了一種自定義實(shí)現(xiàn)類加載器解決 jar 沖突的方法:

          1. 將引言中提到的新的高版本的 jar 放到磁盤的一個目錄,而這個目錄不屬于啟動類加載器所負(fù)責(zé)加載的目錄;

          2. 編寫一個自定義類加載器繼承自 URLClassLoader 類,讓其從我們指定的磁盤目錄來加載;

          3. 使用自定義類加載器的時(shí)候,父類加載器設(shè)置為 null。

          經(jīng)過上面對 loadClass 方法的分析可以知道,這種情況下,當(dāng)使用自定義類加載器加載一個類的時(shí)候,loadClass ?方法中調(diào)用的應(yīng)該是子類自己的 findClass 方法。并且在 URLClassLoader 中已經(jīng)實(shí)現(xiàn)了從指定地址加載的機(jī)制,而我們需要做的就是指定加載的 URL 地址,事實(shí)是這樣嗎?一試便知!

          /** * 自定義類加載器 */public class CustomClassLoader extends URLClassLoader {
          public CustomClassLoader(URL[] urls) { // 父類加載器設(shè)置為 null super(urls,null); }}
          /** * jar 包中的類 */public class D {
          public static String innerInvoke(){ System.out.println("from D.class innerInvoke:" + System.currentTimeMillis()); return "from D.class innerInvoke:" + System.currentTimeMillis(); }}
          public class DateUtils { public static String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"; public static String sysDateTime(){ System.out.println("CustomClassLoader invoke DateUtils.sysDateTime"); Date date = new Date(); SimpleDateFormat smf = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss); D.innerInvoke(); return smf.format(date); }}
          /** * ClassPath 下的類,由 AplicationClassLoader 加載 */public class DateUtils { public static String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss"; public static String sysDateTime(){ System.out.println("AplicationClassLoader invoke DateUtils.sysDateTime"); Date date = new Date(); SimpleDateFormat smf = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss); D.innerInvoke(); return smf.format(date); }}
          /** * 測試類 */public class CustomClassLoaderTest {
          private static final Logger logger = LogManager.getLogger();
          private URL[] urls;
          @Before public void init() throws Exception { urls = new URL[1]; File file = new File("E:\\jar\\commons-1.0-SNAPSHOT.jar"); urls[0] = file.toURI().toURL(); }
          @Test public void methodInvokeTest() throws Exception{ CustomClassLoader customClassLoader = new CustomClassLoader(urls); Class aClass = customClassLoader.loadClass("com.syh.utils.DateUtils"); Method method = aClass.getMethod("sysDateTime"); System.out.println(method.invoke(aClass.newInstance())); System.out.println(DateUtils.sysDateTime()); }
          }----------------------------------------運(yùn)行結(jié)果-----------------------------------------CustomClassLoader Loading DateUtils.classCustomClassLoader invoke DateUtils.sysDateTimefrom D.class innerInvoke:16356972767912021-11-01 00:21:16ApplicationClassLoader invoke DateUtils.sysDateTime2021-11-01 00:21:16
          Process finished with exit code 0


          非常騷氣,果然可以!?。。。。。。。?!只需要在調(diào)用父類加載器的時(shí)候指定加載地址,并且將父類加載器設(shè)為 null !

          但是程序中不可能只一次,或者只在一個地方使用我們這個自定義類加載器,但是每次都要重新創(chuàng)建嗎?要知道類加載器,并不是一次性將目錄下全部的類都加載到虛擬機(jī),而是在需要使用某個類的時(shí)候才去加載,而且加載后會緩存起來,在下載執(zhí)行加載的時(shí)候會先去緩存查看是不是已經(jīng)加載過,如果加載過就直接返回而不是再次加載。這個在 loadClass 方法分析的時(shí)候已經(jīng)明確了。

          那么,我們可以對自定義的類加載器進(jìn)行優(yōu)化,使其成為一個單例。如下

          public class CustomClassLoader extends URLClassLoader {
          private static volatile CustomClassLoader customClassLoader = null;
          // 私有化構(gòu)造器,禁止在外部進(jìn)行創(chuàng)建 private CustomClassLoader(URL[] urls){ super(urls,null); };
          public static CustomClassLoader getInstance(URL[] urls){ if (customClassLoader == null){ synchronized (CustomClassLoader.class){ if (customClassLoader == null){ customClassLoader = new CustomClassLoader(urls); } } } return customClassLoader; }}


          01e0d8eb490d2240defa8399a16bc27a.webp

          小結(jié)

          以上,對于理解類加載器和雙親委派模型還是很有幫助的。但雖然實(shí)現(xiàn)了自定義類加載器從指定目錄加載 jar,但也是很有局限性的,想一下,如果自定目錄下的 jar 還需要調(diào)用 ClassPath 目錄下的類,以上實(shí)現(xiàn)還可以正常運(yùn)行嗎?換句話說,就是自定義類加載器需要用到另外一個類加載器加載的類對象,該怎么辦?

          其實(shí)整體梳理下來,類加載器其實(shí)并不是太難,實(shí)現(xiàn)邏輯也很清晰。事實(shí)上,應(yīng)該實(shí)現(xiàn)一個比較健壯、支持更多場景的自定義類加載器,起碼滿足我上面的那個問題,可能比較復(fù)雜,但是可以支持更多的場景。

          關(guān)注下,期待下一篇吧~


          瀏覽 146
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青操青青操逼网 | 欧洲AV久久无码秘 蜜桃 | 色哟哟一区二区三区 | 亚洲免费看片 | 亚洲永久精品视频 |