第9章 類加載及執(zhí)行子系統(tǒng)的案例與實(shí)戰(zhàn)

1.概述

在Class文件格式與執(zhí)行引擎這部分,用戶的程序能直接影響的內(nèi)容并不多,以什么格式存儲(chǔ)、類型什么時(shí)候加載、怎樣連接、虛擬機(jī)如何執(zhí)行字節(jié)碼指令等都是由虛擬機(jī)直接控制。用戶程序控制的主要是字節(jié)碼生成與類加載器這兩部分功能。

2.案例分析

下面是關(guān)于類加載器和字節(jié)碼的案例各兩個(gè)。

2.1 Tomcat:正統(tǒng)的類加載器架構(gòu)

主流的Java Web服務(wù)器,都實(shí)現(xiàn)了自己定義的類加載器(一般不止一個(gè))。因?yàn)楣δ芙∪腤eb服務(wù)器需要解決如下的問題:

  • 部署在同一個(gè)服務(wù)器上的兩個(gè)Web應(yīng)用程序使用的Java類庫(kù)可以實(shí)現(xiàn)相互隔離。兩個(gè)應(yīng)用程序可能會(huì)依賴同一個(gè)第三方類庫(kù)。

  • 部署在同一個(gè)服務(wù)器上的兩個(gè)Web應(yīng)用程序所使用的Java類庫(kù)可以互相共享。例如10各Spring應(yīng)用程序部署在同一個(gè)服務(wù)器上,如果類庫(kù)不能共享,類庫(kù)使用時(shí)都被加載到服務(wù)器內(nèi)存,虛擬機(jī)的方法區(qū)很容易出現(xiàn)過度膨脹。

  • 服務(wù)器需要盡可能的保證自身的安全不受部署的Web應(yīng)用程序影響。一般來說,基于安全考慮,服務(wù)器使用的類庫(kù)應(yīng)該與應(yīng)用程序的類庫(kù)互相獨(dú)立。

  • 支持JSP應(yīng)用的Web服務(wù)器,大多數(shù)需要支持HotSwap功能。"主流"的Web服務(wù)器都會(huì)支持JSP生成類的熱替換。

現(xiàn)在,筆者以Tomcat服務(wù)器為例,看一看Tomcat如何規(guī)劃用戶類庫(kù)結(jié)構(gòu)和類加載器的。

Tomcat目錄結(jié)構(gòu)中,有3組目錄("/common/"、"/server/"、"/shared/*")可以存放Java類庫(kù),另外還可以加上Web應(yīng)用程序自身的目錄"/WEB-INF",一共4組。

  • /common目錄下:類庫(kù)可被Tomcat和所有的Web應(yīng)用程序共同使用。

  • /server目錄下:類庫(kù)可被Tomcat使用,對(duì)所有的Web應(yīng)用程序都不可見。

  • /shared目錄:類庫(kù)可被所有的Web應(yīng)用程序共同使用,對(duì)Tomcat不可見。

  • /WEB-INF:類庫(kù)僅僅可以被此Web應(yīng)用使用,對(duì)Tomcat和其他Web應(yīng)用程序不可見。

為此,Tomcat自定義類多個(gè)類加載器。關(guān)系如下圖所示:

Tomcat的類加載架構(gòu)

2.2 OSGi:靈活的類加載器架構(gòu)

OSGi的每個(gè)模塊(Bundle)與普通的Java類庫(kù)區(qū)別不大。一般都以JAR格式進(jìn)行封裝,內(nèi)部存儲(chǔ)的是Java Package和Class。但一個(gè)Bundle可以聲明它所依賴但Java Package(通過Import-Package描述),也可以聲明它允許導(dǎo)出發(fā)布的Java package(通過Export-package描述)。

OSGi中,Bundle之間的依賴關(guān)系從傳統(tǒng)的上層模塊依賴底層模塊轉(zhuǎn)變?yōu)槠郊?jí)模塊之間的依賴,而且類庫(kù)的可見性由很準(zhǔn)確的控制,一個(gè)模塊只有被Export過的Package才能由外界訪問;它還很可能實(shí)現(xiàn)模塊級(jí)的熱插拔功能,當(dāng)程序升級(jí)或調(diào)試時(shí),可以只停用、重新安裝其中一部分。

OSGi之所以可以這樣,是因?yàn)樗`活的類加載器。OSGi的Bundle類加載器之間只有規(guī)則,沒有固定的委派關(guān)系。例如:某個(gè)Bundle聲明了一個(gè)它依賴的Package,如果其他的Bundle聲明發(fā)布了這個(gè)Package,那么所有對(duì)這個(gè)Package對(duì)類加載器動(dòng)作都會(huì)委派給發(fā)布它的Bundle類加載器去完成。

另外,一個(gè)Bundle類加載器為其他Bundle提供服務(wù)時(shí),會(huì)根據(jù)Export-Package列表嚴(yán)格控制訪問范圍。如果一個(gè)類存在于Bundle的類庫(kù)中但沒有被export,那么這個(gè)Bundle的類加載器能找到這個(gè)類,但不會(huì)提供給其他Bundle使用。

觀察下面圖片:

OSGi的類加載器架構(gòu)
  • BundleA:聲明發(fā)布了packageA,依賴java.*的包。

  • BundleB:聲明依賴了packageA,packageC,同時(shí)依賴java.*的包。

  • BundleC:聲明發(fā)布了packageC,依賴packageA。

從圖中可以看出,OSGi里面,加載器之間不再是雙親委派模型的樹形結(jié)構(gòu),而是更復(fù)雜的、運(yùn)行時(shí)才能確定的網(wǎng)狀結(jié)構(gòu)。

2.3 字節(jié)碼生成技術(shù)與動(dòng)態(tài)代理的實(shí)現(xiàn)

使用字節(jié)碼生成的例子很多,如Web服務(wù)器的JSP編譯器,編譯時(shí)植入的AOP框架,動(dòng)態(tài)代理技術(shù)等。

動(dòng)態(tài)代理所謂的"動(dòng)態(tài)",是針對(duì)使用java代碼實(shí)際編寫了代理類的"靜態(tài)"代理而言,它的優(yōu)勢(shì)不在于省去了編寫代理類那一點(diǎn)工作量,而是實(shí)現(xiàn)了可以再原始類和接口還未知的時(shí)候,就確定代理類的行為。

下面看一個(gè)簡(jiǎn)單示例:

public class DynamicProxyTest {

    interface IHello{

        void sayHello();
    }

    static class Hello implements IHello{

        @Override
        public void sayHello() {
            System.out.println("hello world!");
        }
    }

    static class DynamicProxy implements InvocationHandler{

        Object originalObj;

        Object bind(Object originalObj){

            this.originalObj = originalObj;
            return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(),
                    originalObj.getClass().getInterfaces(),this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            System.out.println("dancing????");
            return method.invoke(originalObj,args);
        }
    }

    public static void main(String[] args) {

        IHello hello = (IHello) new DynamicProxy().bind(new Hello());
        hello.sayHello();
    }
}

結(jié)果:

dancing????
hello world!

上述代碼里,特殊之處就是Proxy.newProxyInstance()方法,跟蹤這個(gè)方法的源碼,可以看到程序進(jìn)行了驗(yàn)證、優(yōu)化、緩存、同步、生成字節(jié)碼、顯式類加載等操作,而最后它調(diào)用了sun.misc.ProxyGenerator.generateProxyClass()方法完成生成字節(jié)碼的動(dòng)作。這個(gè)方法可以再運(yùn)行時(shí)產(chǎn)生一個(gè)描述代理類的字節(jié)碼byte[]數(shù)組。

如果想看運(yùn)行時(shí)代理類寫了什么,可以再main()方法中加入下面這句:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

運(yùn)行程序,磁盤中會(huì)產(chǎn)生一個(gè)文件"$Proxy.class"的代理類Class文件,如下:

import DynamicProxyTest.IHello;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0 extends Proxy implements IHello {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("DynamicProxyTest$IHello").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

大致的生成過程是根據(jù)Class文件的格式規(guī)范去拼裝字節(jié)碼。如果對(duì)字節(jié)碼拼裝過程感興趣,可以再OpenJDK的/jdk/src/share/classes/sun/misc目錄下找到sun.misc.ProxyGenerator的源碼。

2.4 Retrotranslator:跨越JDK版本

Java中,每次JDK大版本的發(fā)布,都伴隨著一場(chǎng)大規(guī)模的技術(shù)革新,有時(shí)候會(huì)因?yàn)橐Wo(hù)現(xiàn)有投資、維持程序結(jié)構(gòu)穩(wěn)定,需要使用以前的版本的JDK。于是一種"Java逆向移植"的工具應(yīng)運(yùn)而生,Retrotranslator是最典型的一個(gè)。

舉例來說,它可以將JDK1.5版本編譯出來的Class文件轉(zhuǎn)變?yōu)?.4版本上部署的版本,可以很好的支持自動(dòng)裝箱、泛型、動(dòng)態(tài)注解、枚舉、變長(zhǎng)參數(shù)、遍歷循環(huán)、靜態(tài)導(dǎo)入等,還可以支持1.5中新增的集合改進(jìn)、并發(fā)包以及對(duì)泛型、注解等反射操作。

想知道它如何做到,首先要弄清楚JDK升級(jí)中會(huì)提供哪些功能。JDK每次升級(jí)大致分為4類:

  • 編譯器層面做的改進(jìn)。如自動(dòng)裝箱拆箱,實(shí)際上就是編譯器在程序中使用包裝對(duì)象的地方自動(dòng)插入了Integer.valueOf();變長(zhǎng)參數(shù)在編譯后自動(dòng)轉(zhuǎn)化成一個(gè)數(shù)組完成參數(shù)傳遞;

  • 對(duì)Java API的代碼增強(qiáng)。引入新的包。

  • 在字節(jié)碼中進(jìn)行支持的改動(dòng)。如JDK1.7里新的語(yǔ)法特性:動(dòng)態(tài)語(yǔ)言支持。

  • 虛擬機(jī)內(nèi)部改進(jìn)。

上述4類,Retrotranslator只能模擬前2類,例如JDK1.5引入的java.util.concurrent包,要在舊的JDK中支持這部分功能,以獨(dú)立類庫(kù)的方法可以實(shí)現(xiàn)。Retrotranslator中附帶類"backport-util-concurrent.jar"的類庫(kù)。

至于JDK在編譯階段進(jìn)行處理的改進(jìn),Retrotranslator使用ASM框架直接對(duì)字節(jié)碼進(jìn)行處理。例如枚舉,Retrotranslator對(duì)枚舉所做的主要處理是把枚舉類的父類從"java.lang.Enum"替換為它運(yùn)行時(shí)類庫(kù)中包含的"net.sf.retrotranslator.runtime.java.lang.Enum",然后在類和字段的訪問標(biāo)志中抹去ACC_ENUM標(biāo)志位。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容