jvm(1)類的加載(二)(自定義類加載器)


【深入Java虛擬機】之四:類加載機制

1,從Java虛擬機的角度,只存在兩種不同的類加載器:

1,啟動類加載器:它使用C++實現(這里僅限於Hotspot,也就是JDK1.5之后默認的虛擬機,有其他的虛擬機是用Java語言實現的),是虛擬機自身的一部分。
2,其他的類加載器:這些類加載器都由Java語言實現,獨立於虛擬機之外,並且全部繼承自抽象類java.lang.ClassLoader,
這些類加載器需要由啟動類加載器加載到內存中之后才能去加載其他的類。

2,從Java開發人員的角度來看,類加載器可以大致划分為以下三類:

1,啟動類加載器:Bootstrap ClassLoader,跟上面相同。
它負責加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,
並且能被虛擬機識別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載)。
啟動類加載器是無法被Java程序直接引用的。
2,擴展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現,
它負責加載JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴展類加載器。
3,應用程序類加載器:Application ClassLoader,該類加載器由sun.misc.Launcher$AppClassLoader來實現,
它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器,
如果應用程序中沒有自定義過自己的類加載器,
一般情況下這個就是程序中默認的類加載器

3,如果編寫了自己的ClassLoader,需要做到如下幾點:

JVM自帶的ClassLoader只是懂得從本地文件系統加載標准的java class文件(不包括URLClassLoader)

 1)在執行非置信代碼之前,自動驗證數字簽名2)動態地創建符合用戶特定需要的定制化構建類3)從特定的場所取得java class,例如數據庫中和網絡中。

事實上當使用Applet的時候,就用到了特定的ClassLoader,因為這時需要從網絡上加載java class,並且要檢查相關的安全信息,

應用服務器也大都使用了自定義的ClassLoader技術。

4,類加載,虛擬機需要完成以下三件事情:

1、通過一個類的全限定名來獲取其定義的二進制字節流。
2、將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。(二進制字節流按照特定虛擬機的格式存儲在方法區之中) 3、在Java堆中生成一個代表這個類的java.lang.Class對象,作為對方法區中這些數據的訪問入口。

二進制字節流並不只是單純地從Class文件中獲取,比如它還可以從Jar包中獲取、從網絡中獲取(最典型的應用便是Applet)、由其他文件生成(JSP應用)等。

5,類的唯一性確認方式:

對於任意一個類,都需要由它的類加載器和這個類本身一同確定其在就Java虛擬機中的唯一性。

即使兩個類來源於同一個.class文件,只要加載它們的類加載器不同,那這兩個類就必定不相等。

影響到的比較方法有:

1,Class對象的比較:equals()、isAssignableFrom()、isInstance()等方法的返回結果。
2,使用instanceof對對象所屬關系的判定結果。

instanceof,isinstance,isAssignableFrom,asSubclass的區別

6,類加載器的雙親委派模型

我們把上層的類加載器叫做當前層類加載器的父加載器,但父子關系並不是通過繼承關系來實現的,而是使用組合關系來復用父加載器中的代碼。

該模型在JDK1.2期間被引入並廣泛應用於之后幾乎所有的Java程序中,但它並不是一個強制性的約束模型,而是Java設計者們推薦給開發者的一種類的加載器實現方式。

雙親委派模型的工作流程是:

如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委托給父加載器去完成,依次向上,
因此,所有的類加載請求最終都應該被傳遞到頂層的啟動類加載器中,
只有當父加載器在它的搜索范圍中沒有找到所需的類時,
即無法完成該加載,子加載器才會嘗試自己去加載該類。

7, 使用雙親委派模型來組織類加載器之間的關系的好處

讓Java類也具備了一種帶有優先級的層次關系:
1,防止重復加載。
(例如,類java.lang.Object類存放在JDK\jre\lib下的rt.jar之中,因此無論是哪個類加載器要加載此類,最終都會委派給啟動類加載器進行加載)
2,可以利於保證類來源的安全性。

8,對於同名的類(包名和類名是一樣)為什么不會重復加載。

1,當啟動虛擬機,引導類加載器會加載jre/lib下的系統類
2,擴展類加載器會加載jre/lib/ext下的類
3,執行public類的main的方法,應用類加載器嘗試去加載。

當加載類時候,是采用雙親加載模式:

1,首先應用加載判斷自己是否已經加載,如果沒有加載,就交給父加載器(擴展類加載器)加載。
   補充:應用類加載器並不是繼承擴展類加載器,而是結構層次上一級。

2,擴展類加載判斷這個是否已經被自己加載,如果沒有,查看引導類加載器是否已經加載了。
   補充引導類加載是本地方法實現的,只能調用查詢的接口,沒有調用讓其去加載類的接口。

3,如果引導類加載器沒有加載,那么擴展類加載器才會嘗試去加載。

4,擴展類加載加載的時候判斷這個類是否已經在ext/lib的目錄下,在的話,嘗試加載。如果不在或者加載失敗,那么交給應用類加載器。

5,應用類加載器首先判斷是否在classpath下,如果類確實存在,應用類加載器才真正去加載該類。

9,如果要加載同名的l類。需要繞開雙親的加載,自己定義類加載器。

1,自定義類首先繼承類加載器(因為需要用到其中的真正加載類的方法)。
2,重寫查找類的方法。

10,關於不同加載器加載同一個字節碼,被認為是兩個不同的類,並且兩個類的屬性值不可以互相賦值。

補充1

JVM在運行時會對這兩個類的加載器進行驗證,JVM規范中要求這兩個加載器必須要一致,
否則將報類驗證錯誤,即VerifyError的錯誤,這是為了防止不正常的類冒充正確的類進行類型欺騙。

補充2

一種熱替換類的方式,用自定義類加載器加類,將需要的類替換掉(或者替換掉類的內容),需要注意同名類不同加載器賦值問題。
以及之前對該類的調用方式(是否指定了類加載器,或者被記錄了加載器和類的關聯)。

11,類加載器的源碼分析:

java.lang.ClassLoader抽象類中重要的幾個方法:

//加載指定名稱(包括包名)的二進制類型,供用戶調用的接口  
public Class<?> loadClass(String name) throws ClassNotFoundException{ … }  
  
//加載指定名稱(包括包名)的二進制類型,同時指定是否解析(但是這里的resolve參數不一定真正能達到解析的效果),供繼承用  
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ … }  
  
//findClass方法一般被loadClass方法調用去加載指定名稱類,供繼承用  
protected Class<?> findClass(String name) throws ClassNotFoundException { … }  
  
//定義類型,一般在findClass方法中讀取到對應字節碼后調用,可以看出不可繼承  
//(說明:JVM已經實現了對應的具體功能,解析對應的字節碼,產生對應的內部數據結構放置到方法區,所以無需覆寫,直接調用就可以了)  
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }  

ClassLoader詳細源碼

public abstract class ClassLoader {  
  
    // 父ClassLoader  
    private ClassLoader parent;  
  
    // 被此classLoader加載過的Class對象  
    private Vector classes = new Vector();  
      
    // The packages defined in this class loader.  Each package name is mapped  
    // to its corresponding Package object.  
    private HashMap packages = new HashMap();  
  
    // 由虛擬機調用  
    void addClass(Class c) {  
        classes.addElement(c);  
    }  
  
    // The packages defined in this class loader. Each package name is mapped  
    // to its corresponding Package object.  
    private final HashMap<String, Package> packages = new HashMap<String, Package>();  
  
    // 指明parent  
    protected ClassLoader(ClassLoader parent) {  
        this(checkCreateClassLoader(), parent);  
    }  
  
    // 不指名parent時使用SystemClassLoader  
    protected ClassLoader() {  
        this(checkCreateClassLoader(), getSystemClassLoader());  
    }  
  
    // 默認resolve=false  
    public Class<?> loadClass(String name) throws ClassNotFoundException {  
        return loadClass(name, false);  
    }  
  
    protected synchronized Class<?> loadClass(String name, boolean resolve)  
            throws ClassNotFoundException {  
        // 1 檢查此class是否被此classloader加載過,  
        // 最終是有native方法返回,native方法會使用到classes集合  
        Class c = findLoadedClass(name);  
        // 1.1 未被加載  
        if (c == null) {  
            try {  
                // 1.1.1 此classloader有parent,委托parent去load  
                if (parent != null) {  
                    c = parent.loadClass(name, false);  
                } else {// 1.1.2 此classloader無parent = 啟動類裝載器去加載  
                    c = findBootstrapClassOrNull(name);  
                }  
            } catch (ClassNotFoundException e) {  
            }  
            // 如果沒有找到class,自己去加載試試  
            if (c == null) {  
                c = findClass(name);  
            }  
        }  
        // 找到的Class對象是否需要連接操作  
        if (resolve) {  
            resolveClass(c);  
        }  
        // 1.2 被加載過,直接返回  
        return c;  
    }  
  
    protected final Class<?> findLoadedClass(String name) {  
        if (!checkName(name))  
            return null;  
        return findLoadedClass0(name);  
    }  
  
    // 如果name里包含/,或者虛擬機不支持class數組你使用了數組的時候返回false,其它情況返回true,包括空的name  
    // eg com.jyz.component.core.collection.Tuple return true  
    private boolean checkName(String name) {  
        if ((name == null) || (name.length() == 0))  
            return true;  
        if ((name.indexOf('/') != -1)  
                || (!VM.allowArraySyntax() && (name.charAt(0) == '[')))  
            return false;  
        return true;  
    }  
  
    // 檢查package是否可訪問  
    private void checkPackageAccess(Class cls, ProtectionDomain pd) {  
        final SecurityManager sm = System.getSecurityManager();  
        if (sm != null) {  
            // ...  
        }  
        domains.add(pd);  
    }  
  
    // 自定義classloader時重寫此方法  
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        throw new ClassNotFoundException(name);  
    }  
  
    // 將byte數組轉換成一個Class對象,最終有native方法實現  
    // 同一個byte數組,被不同的classloader加載,產生兩個不同的Class對象  
    protected final Class<?> defineClass(String name, byte[] b, int off, int len)  
            throws ClassFormatError {  
        return defineClass(name, b, off, len, null);  
    }  
  
    /* 
     * Determine protection domain, and check that: - not define java.* class, - 
     * signer of this class matches signers for the rest of the classes in 
     * package. 
     */  
    //native的defineClass時會調用此方法檢查name是否合法  
    //首先checkName,然后還需要!name.startsWith("java.")  
    //所以我們定義了java.mypackage包,都將異常  
    //java.lang.SecurityException: Prohibited package name: java.mypackage  
    private ProtectionDomain preDefineClass(String name,  
            ProtectionDomain protectionDomain) {  
        if (!checkName(name))  
            throw new NoClassDefFoundError("IllegalName: " + name);  
        if ((name != null) && name.startsWith("java.")) {  
            throw new SecurityException("Prohibited package name: "  
                    + name.substring(0, name.lastIndexOf('.')));  
        }  
        //...  
    }  
  
    // protected的resolveClass方法,可以在自定義的classloader調用  
    protected final void resolveClass(Class<?> c) {  
        resolveClass0(c);  
    }  
      
    //獲得appClassLoader,實際調用Launcher完成  
    public static ClassLoader getSystemClassLoader() {  
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();  
        return l.getClassLoader();  
    }  
  
}  
ClassLoader類

java.lang.ClassLoader抽象類中默認實現的兩個構造函數:

protected ClassLoader() {  
    SecurityManager security = System.getSecurityManager();  
    if (security != null) {  
        security.checkCreateClassLoader();  
    }  
    //默認將父類加載器設置為系統類加載器,getSystemClassLoader()獲取系統類加載器  
    this.parent = getSystemClassLoader();  
    initialized = true;  
}  
  
protected ClassLoader(ClassLoader parent) {  
    SecurityManager security = System.getSecurityManager();  
    if (security != null) {  
        security.checkCreateClassLoader();  
    }  
    //強制設置父類加載器  
    this.parent = parent;  
    initialized = true;  
}  

java.lang.ClassLoader中的loadClass(String name)方法的代碼就可以分析出虛擬機默認采用的雙親委派機制到底是什么模樣:

public Class<?> loadClass(String name) throws ClassNotFoundException {  
    return loadClass(name, false);  
}  
  
protected synchronized Class<?> loadClass(String name, boolean resolve)  
        throws ClassNotFoundException {  
  
    // 首先判斷該類型是否已經被加載  
    Class c = findLoadedClass(name);  
    if (c == null) {  
        //如果沒有被加載,就委托給父類加載或者委派給啟動類加載器加載  
        try {  
            if (parent != null) {  
                //如果存在父類加載器,就委派給父類加載器加載  
                c = parent.loadClass(name, false);  
            } else {  
                //如果不存在父類加載器,就檢查是否是由啟動類加載器加載的類,  
                //通過調用本地方法native findBootstrapClass0(String name)  
                c = findBootstrapClass0(name);  
            }  
        } catch (ClassNotFoundException e) {  
            // 如果父類加載器和啟動類加載器都不能完成加載任務,才調用自身的加載功能  
            c = findClass(name);  
        }  
    }  
    if (resolve) {  
        resolveClass(c);  
    }  
    return c;  
}  

系統類加載器和擴展類加載器是在Launcher類的內部類:

public class Launcher {  
    private static URLStreamHandlerFactory factory = new Factory();  
    private static Launcher launcher = new Launcher();  
  
    public static Launcher getLauncher() {  
        return launcher;  
    }  
  
    private ClassLoader loader;  
      
    //ClassLoader.getSystemClassLoader會調用此方法  
    public ClassLoader getClassLoader() {  
        return loader;  
    }  
  
    public Launcher() {  
        // 1. 創建ExtClassLoader   
        ClassLoader extcl;  
        try {  
            extcl = ExtClassLoader.getExtClassLoader();  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create extension class loader");  
        }  
  
        // 2. 用ExtClassLoader作為parent去創建AppClassLoader   
        try {  
            loader = AppClassLoader.getAppClassLoader(extcl);  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create application class loader");  
        }  
  
        // 3. 設置AppClassLoader為ContextClassLoader  
        Thread.currentThread().setContextClassLoader(loader);  
        //...  
    }  
  
    static class ExtClassLoader extends URLClassLoader {  
        private File[] dirs;  
  
        public static ExtClassLoader getExtClassLoader() throws IOException  
        {  
            final File[] dirs = getExtDirs();  
            return new ExtClassLoader(dirs);  
        }  
  
        public ExtClassLoader(File[] dirs) throws IOException {  
            super(getExtURLs(dirs), null, factory);  
            this.dirs = dirs;  
        }  
  
        private static File[] getExtDirs() {  
            String s = System.getProperty("java.ext.dirs");  
            File[] dirs;  
            //...  
            return dirs;  
        }  
    }  
  
    /** 
     * The class loader used for loading from java.class.path. 
     * runs in a restricted security context. 
     */  
    static class AppClassLoader extends URLClassLoader {  
  
        public static ClassLoader getAppClassLoader(final ClassLoader extcl)  
            throws IOException  
        {  
            final String s = System.getProperty("java.class.path");  
            final File[] path = (s == null) ? new File[0] : getClassPath(s);  
  
            URL[] urls = (s == null) ? new URL[0] : pathToURLs(path);  
            return new AppClassLoader(urls, extcl);  
        }  
  
        AppClassLoader(URL[] urls, ClassLoader parent) {  
            super(urls, parent, factory);  
        }  
          
        /** 
         * Override loadClass so we can checkPackageAccess. 
         * 這個方法似乎沒什么必要,因為super.loadClass(name, resolve)時也會checkPackageAccess 
         */  
        public synchronized Class loadClass(String name, boolean resolve)  
            throws ClassNotFoundException  
        {  
            int i = name.lastIndexOf('.');  
            if (i != -1) {  
                SecurityManager sm = System.getSecurityManager();  
                if (sm != null) {  
                    //  
                    sm.checkPackageAccess(name.substring(0, i));  
                }  
            }  
            return (super.loadClass(name, resolve));  
        }  
  
    }  
}  

ClassLoader.loadClass()的最后一步是調用findClass(),這個方法在ClassLoader中並未實現,由其子類負責實現。

findClass()的功能是找到class文件並把字節碼加載到內存中。(自定義的ClassLoader一般覆蓋這個方法。——以便使用不同的加載路徑。)

URLClassLoader.findClass()如下:

 /* The search path for classes and resources */
    URLClassPath ucp;
    /* The context to be used when loading classes and resources */
    private AccessControlContext acc;
 
   /**
     * Finds and loads the class with the specified name from the URL search
     * path. Any URLs referring to JAR files are loaded and opened as needed
     * until the class is found.
     *
     * @param name the name of the class
     * @return the resulting class
     * @exception ClassNotFoundException if the class could not be found
     */
    protected Class<?> findClass(final String name)
     throws ClassNotFoundException
    {
    try {
        return (Class)
        AccessController.doPrivileged(new PrivilegedExceptionAction() {
            public Object run() throws ClassNotFoundException {
            String path = name.replace('.', '/').concat(".class");
            // 1. URLClassPath ucp,幫助獲取class文件字節流
            //    URLClassPath會用FileLoader或者JarLoader去加載字節碼 
            Resource res = ucp.getResource(path, false); 
            if (res != null) {
                try {
                // 2. defineClass,創建類對象,將字節流解析成JVM能夠識別的Class對象。
                return defineClass(name, res, true);
                } catch (IOException e) {
                throw new ClassNotFoundException(name, e);
                }
            } else {
                throw new ClassNotFoundException(name);
            }
            }
        }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    }

ClassLoader.resolveClass()源碼如下:

加載完字節碼后,會根據需要進行驗證、解析。

protected final void resolveClass(Class<?> c) {
    resolveClass0(c);
    }
private native void resolveClass0(Class c);

由此可以知道:

1,雙親加載模式的具體實現
2,安全檢測的代碼開始執行時機(security.checkCreateClassLoader())(補充:SecurityManager入門
3,驗證和解析的代碼開始時機(resolveClass(c))

根據繼承關系:

和方法實現:

可以看出:只要繼承任意子類都還是雙親加載模式,除非重寫loadClass().

標准擴展類加載器和系統類加載器及其父類(java.NET.URLClassLoader和java.security.SecureClassLoader)
都沒有覆寫java.lang.ClassLoader中默認的加載委派規則---loadClass(…)方法。

12,java程序動態擴展方式

允許用戶運行時擴展引用程序:
1,既可以通過當前虛擬機中預定義的加載器加載編譯時已知的類或者接口,
2,又允許用戶自行定義類裝載器,在運行時動態擴展用戶的程序。
  你的程序可以裝載在編譯時並不知道或者尚未存在的類或者接口,並動態連接它們並進行有選擇的解析。

運行時動態擴展java應用程序有如下兩個途徑:

1 調用java.lang.Class.forName(…)加載類

這里主要說明的是多參數版本的forName(…)方法:

public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException 

這里的initialize參數是很重要的。

它表示在加載同時是否完成初始化的工作(說明:單參數版本的forName方法默認是完成初始化的)。

有些場景下需要將initialize設置為true來強制加載同時完成初始化。

例如典型的就是利用DriverManager進行JDBC驅動程序類注冊的問題。

因為每一個JDBC驅動程序類的靜態初始化方法都用DriverManager注冊驅動程序,這樣才能被應用程序使用。
這就要求驅動程序類必須被初始化,而不單單被加載。Class.forName的一個很常見的用法就是在加載數據庫驅動的時候。

如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()用來加載 Apache Derby 數據庫的驅動。

 

如果loader的參數設置為null那么會用啟動類加載。

常用無參方法默認的加載器是應用類加載器,如下情況

Class.forName("Foo")
is equivalent to:
Class.forName("Foo", true, this.getClass().getClassLoader())

2 用戶自定義類加載器

 findClass()定義加載路徑:

findClass()的功能是找到class文件並把字節碼加載到內存中。
自定義的ClassLoader一般覆蓋這個方法。(以便使用不同的加載路徑。)
在其中調用defineClass()解析字節碼。

loadClass()定義加載機制:

1,自定義的加載器可以覆蓋該方法loadClass(),以便定義不同的加載機制。
例如:
Servlet中的WebappClassLoader覆蓋了該方法,在WEB
-INFO/classes目錄下查找類文件;在加載時,如果成功,則緩存到ResourceEntry對象。(不同的加載機制)。
2,AppClassLoader覆蓋了loadClass()方法。
  如果自定義的加載器僅覆蓋了findClass,而未覆蓋loadClass(即加載規則一樣,但加載路徑不同);
則調用getClass().getClassLoader()返回的仍然是AppClassLoader!因為真正load類的,還是AppClassLoader。

實現類的熱部署:

JVM默認不能熱部署類,因為加載類時會去調用findLoadedClass(),如果類已被加載,就不會再次加載。

JVM判斷類是否被加載有兩個條件:完整類名是否一樣、ClassLoader是否是同一個。

所以要實現熱部署的話,只需要使用ClassLoader的不同實例來加載。

MyClassLoader cl1 = new MyClassLoader();
Class c1 = cl1.findClass("Test.class");
c1.newInstance();

MyClassLoader cl2 = new MyClassLoader();
Class c2 = cl2.findClass("Test.class");
c2.newInstance();

上例中的c1和c2就是兩個不同的實例。

如果用同一個ClassLoader實例來加載,則會拋LinkageError。

13,區別不同類加載器:

真正完成類的加載工作的類加載器和啟動這個加載過程的類加載器,有可能不是同一個。
1,真正完成類的加載工作是通過調用defineClass來實現的(稱為一個類的定義加載器(defining loader);
2,而啟動類的加載過程是通過調用loadClass來實現的。稱為初始加載器(initiating loader)。

在Java虛擬機判斷兩個類是否相同的時候,使用的是類的定義加載器。

也就是說,哪個類加載器啟動類的加載過程並不重要,重要的是最終定義這個類的加載器。

兩種類加載器的關聯之處在於:

一個類的定義加載器是它引用的其它類的初始加載器。

如類 com.example.Outer引用了類 com.example.Inner,則由類 com.example.Outer的定義加載器負責啟動類 com.example.Inner的加載過程。

14,文件系統類加載器

package classloader;  
  
import java.io.ByteArrayOutputStream;  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
  
// 文件系統類加載器  
public class FileSystemClassLoader extends ClassLoader {  
  
    private String rootDir;  
  
    public FileSystemClassLoader(String rootDir) {  
        this.rootDir = rootDir;  
    }  
  
    // 獲取類的字節碼  
    @Override  
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        byte[] classData = getClassData(name);  // 獲取類的字節數組  
        if (classData == null) {  
            throw new ClassNotFoundException();  
        } else {  
            return defineClass(name, classData, 0, classData.length);  
        }  
    }  
  
    private byte[] getClassData(String className) {  
        // 讀取類文件的字節  
        String path = classNameToPath(className);  
        try {  
            InputStream ins = new FileInputStream(path);  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            int bufferSize = 4096;  
            byte[] buffer = new byte[bufferSize];  
            int bytesNumRead = 0;  
            // 讀取類文件的字節碼  
            while ((bytesNumRead = ins.read(buffer)) != -1) {  
                baos.write(buffer, 0, bytesNumRead);  
            }  
            return baos.toByteArray();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
    private String classNameToPath(String className) {  
        // 得到類文件的完全路徑  
        return rootDir + File.separatorChar  
                + className.replace('.', File.separatorChar) + ".class";  
    }  }  

為了保證類加載器都正確實現代理模式,在開發自己的類加載器時,最好不要覆寫 loadClass()方法,而是覆寫 findClass()方法。

package com.example;   
public class Sample {  
    private Sample instance;  
    public void setSample(Object instance) {  
        System.out.println(instance.toString());  
        this.instance = (Sample) instance;  
    }  
}  
package classloader;  
  
import java.lang.reflect.Method;  
  
public class ClassIdentity {  
  
    public static void main(String[] args) {  
        new ClassIdentity().testClassIdentity();  
    }  
  
    public void testClassIdentity() {  
        String classDataRootPath = "C:\\Users\\JackZhou\\Documents\\NetBeansProjects\\classloader\\build\\classes";  
        FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);  
        FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);  
        String className = "com.example.Sample";  
        try {  
            Class<?> class1 = fscl1.loadClass(className);  // 加載Sample類  
            Object obj1 = class1.newInstance();  // 創建對象  
            Class<?> class2 = fscl2.loadClass(className);  
            Object obj2 = class2.newInstance();  
            Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);  
            setSampleMethod.invoke(obj1, obj2);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }   }  }  

運行輸出:com.example.Sample@7852e922

15,網絡類加載器

通過類加載器來實現組件的動態更新。

場景是:

Java 字節代碼(.class)文件存放在服務器上,客戶端通過網絡的方式獲取字節代碼並執行。
當有版本更新的時候,只需要替換掉服務器上保存的文件即可。

類 NetworkClassLoader負責通過網絡下載Java類字節代碼並定義出Java類。 

package classloader;  
  
import java.io.ByteArrayOutputStream;  
import java.io.InputStream;  
import java.net.URL;  
  
public class NetworkClassLoader extends ClassLoader {  
  
    private String rootUrl;  
  
    public NetworkClassLoader(String rootUrl) {  
        // 指定URL  
        this.rootUrl = rootUrl;  
    }  
  
    // 獲取類的字節碼  
    @Override  
    protected Class<?> findClass(String name) throws ClassNotFoundException {  
        byte[] classData = getClassData(name);  
        if (classData == null) {  
            throw new ClassNotFoundException();  
        } else {  
            return defineClass(name, classData, 0, classData.length);  
        }  
    }  
  
    private byte[] getClassData(String className) {  
        // 從網絡上讀取的類的字節  
        String path = classNameToPath(className);  
        try {  
            URL url = new URL(path);  
            InputStream ins = url.openStream();  
            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            int bufferSize = 4096;  
            byte[] buffer = new byte[bufferSize];  
            int bytesNumRead = 0;  
            // 讀取類文件的字節  
            while ((bytesNumRead = ins.read(buffer)) != -1) {  
                baos.write(buffer, 0, bytesNumRead);  
            }  
            return baos.toByteArray();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return null;  
    }  
  
    private String classNameToPath(String className) {  
        // 得到類文件的URL  
        return rootUrl + "/"  
                + className.replace('.', '/') + ".class";  
    }  
} 

在通過NetworkClassLoader加載了某個版本的類之后,一般有兩種做法來使用它。

第一種做法是使用Java反射API。
另外一種做法是使用接口。

需要注意的是,並不能直接在客戶端代碼中引用從服務器上下載的類,因為客戶端代碼的類加載器找不到這些類。

使用Java反射API可以直接調用Java類的方法。

而使用接口的做法則是把接口的類放在客戶端中,從服務器上加載實現此接口的不同版本的類。

在客戶端通過相同的接口來使用這些實現類。我們使用接口的方式。示例如下:

//客戶端接口
package classloader;    
public interface Versioned {  
  
    String getVersion();  
}  
package classloader;  
  
public interface ICalculator extends Versioned {  
  
    String calculate(String expression);  
}  

網絡上的不同版本的類:

package com.example;  
  
import classloader.ICalculator;  
  
public class CalculatorBasic implements ICalculator {  
  
    @Override  
    public String calculate(String expression) {  
        return expression;  
    }  
  
    @Override  
    public String getVersion() {  
        return "1.0";  
    }  
  
}  
package com.example;  
  
import classloader.ICalculator;  
  
public class CalculatorAdvanced implements ICalculator {  
  
    @Override  
    public String calculate(String expression) {  
        return "Result is " + expression;  
    }  
  
    @Override  
    public String getVersion() {  
        return "2.0";  
    }  
  
}  

在客戶端加載網絡上的類的過程:

package classloader;  
  
public class CalculatorTest {  
  
    public static void main(String[] args) {  
        String url = "http://localhost:8080/ClassloaderTest/classes";  
        NetworkClassLoader ncl = new NetworkClassLoader(url);  
        String basicClassName = "com.example.CalculatorBasic";  
        String advancedClassName = "com.example.CalculatorAdvanced";  
        try {  
            Class<?> clazz = ncl.loadClass(basicClassName);  // 加載一個版本的類  
            ICalculator calculator = (ICalculator) clazz.newInstance();  // 創建對象  
            System.out.println(calculator.getVersion());  
            clazz = ncl.loadClass(advancedClassName);  // 加載另一個版本的類  
            calculator = (ICalculator) clazz.newInstance();  
            System.out.println(calculator.getVersion());  
        } catch (Exception e) {  
            e.printStackTrace();  
        }      }    }  

 

16,類加載器加載路徑

java中獲取類加載路徑和項目根路徑的5種方法

Java類加載器(classloader)及類加載路徑簡介

類加載器獲取資源路徑

 

總結:加載過程中,主要加載器方法的執行步驟。

//其實就是不同實例的方法
1,loadClass
2,findClass
3,difineClass
3,resovleClass

 

來源:

深入理解Java類加載器(1):Java類加載原理解析

http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html

http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

 http://zy19982004.iteye.com/blog/1983236

http://zy19982004.iteye.com/blog/1983240

http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html

http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM