1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 8328字,需瀏覽 17分鐘

         ·

        2021-11-18 21:38

        碎碎念

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

        引言

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

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

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

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

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

        網(wǎng)上先搜搜了解下,但網(wǎng)上那些博客已經(jīng)被資本侵蝕了,有用的不多,沒用的一堆,但可以告訴你這個(gè)東西有人做過,可以試試!試試就試試,正好也好久沒用復(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)類的加載動(dòng)作,通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流。

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

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

        類加載器分類

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

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

        負(fù)責(zé)加載在 /lib 目錄中,或者被 JVM 的 -Xbootclasspath 參數(shù)所指定的路徑中,并且是 JVM 識(shí)別的(JVM 僅按照文件名識(shí)別,比如:rt.jar,名字不符合的類庫即使放在 lib 目錄下也不會(huì)被加載)類庫加載到虛擬機(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ā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序中沒有自定義類加載器,一般情況下,這個(gè)就是應(yīng)用程序中默認(rèn)的類加載器。

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

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

        d4e3ad96202f6cab46baa3afe2cfa5da.webp

        雙親委派模型

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

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

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

        在雙親委派模型下,如果編寫一個(gè)與 rt.jar 類庫中已有類重名的 Java 類,你將會(huì)發(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)使用啟動(dòng)類加載器作為父加載器。如果父類加載失敗,拋出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 {                    // 父類加載器是否為空,如果不為空則委派父加載器加載,否則啟動(dòng)類加載器加載                    if (parent != null) {                        c = parent.loadClass(name, false);                    } else {                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                    // 如果父類加載器拋出 ClassNotFoundException                     // 說明父類加載器無法完成加載請(qǐng)求                }                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)方法說明

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

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

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

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

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

        自定義類加載器

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

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

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

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

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

        經(jīng)過上面對(duì) loadClass 方法的分析可以知道,這種情況下,當(dāng)使用自定義類加載器加載一個(gè)類的時(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); }}
        /** * 測(cè)試類 */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 !

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

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

        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é)

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

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

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


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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            干屄免费视频 | 九色porny丨精品自拍视频 | 欧美毛片在线 | 天天日天天操比 | 豆花视频一区二区三区在线观看 | 啊灬啊灬啊灬快好喷水了视频 | 性爱网站在线 | 大香蕉插插 | 第一页亚洲 | 人妻无码一区二区 |