自定義類加載器

(問:自定義類加載器怎麼實現,其中哪一個方法走雙親委派模型,(實現findclass方法,通常用defineclass加載外部類),如何才能不走雙親委派。(重寫loadclass方法))java

三個重要函數:loadClass,findClass,defineClass算法

loadClass:調用父類加載器的loadClass,加載失敗則調用本身的findClass方法數據庫

findClass:根據名稱讀取文件存入字節數組數組

defineClass:把一個字節數組轉爲Class對象安全

 

 

0. 爲何須要自定義類加載器  網絡

網上的大部分自定義類加載器文章,幾乎都是貼一段實現代碼,而後分析一兩句自定義ClassLoader的原理。可是我以爲首先得把爲何須要自定義加載器這個問題搞清楚,由於若是不明白它的做用的狀況下,還要去學習它顯然是很讓人困惑的。函數

首先介紹自定義類的應用場景學習

(1)加密:Java代碼能夠輕易的被反編譯,若是你須要把本身的代碼進行加密以防止反編譯,能夠先將編譯後的代碼用某種加密算法加密,類加密後就不能再用Java的ClassLoader去加載類了,這時就須要自定義ClassLoader在加載類的時候先解密類,而後再加載。this

(2)從非標準的來源加載代碼:若是你的字節碼是放在數據庫、甚至是在雲端,就能夠自定義類加載器,從指定的來源加載類。加密

(3)以上兩種狀況在實際中的綜合運用:好比你的應用須要經過網絡來傳輸 Java 類的字節碼,爲了安全性,這些字節碼通過了加密處理。這個時候你就須要自定義類加載器來從某個網絡地址上讀取加密後的字節代碼,接着進行解密和驗證,最後定義出在Java虛擬機中運行的類。

 


1. 雙親委派模型

在實現本身的ClassLoader以前,咱們先了解一下系統是如何加載類的,那麼就不得不介紹雙親委派模型的實現過程。

//雙親委派模型的工做過程源碼
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded
Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader //父類加載器沒法完成類加載請求
} if (c == null) { // If still not found, then invoke findClass in order to find the class //子加載器進行類加載 
c = findClass(name); } } if (resolve) {//判斷是否須要連接過程,參數傳入
resolveClass(c); } return c; }

雙親委派模型的工做過程以下:

(1)當前類加載器從本身已經加載的類中查詢是否此類已經加載,若是已經加載則直接返回原來已經加載的類。

(2)若是沒有找到,就去委託父類加載器去加載(如代碼c = parent.loadClass(name, false)所示)。父類加載器也會採用一樣的策略,查看本身已經加載過的類中是否包含這個類,有就返回,沒有就委託父類的父類去加載,一直到啓動類加載器。由於若是父加載器爲空了,就表明使用啓動類加載器做爲父加載器去加載。

(3)若是啓動類加載器加載失敗(例如在$JAVA_HOME/jre/lib裏未查找到該class),會使用拓展類加載器來嘗試加載繼續失敗則會使用AppClassLoader來加載繼續失敗則會拋出一個異常ClassNotFoundException,而後再調用當前加載器的findClass()方法進行加載

 好比要加載本身寫的String類,自定義一個String類放在某路徑下,自定義一個類加載器繼承ClassLoader類,並實現findClass方法(在本身的路徑下去取String類)。重寫loadClass方法讓它不走雙親委派,這樣他就會直接調用findClass加載本身的String類了。

雙親委派模型的好處:

(1)主要是爲了安全性,避免用戶本身編寫的類動態替換 Java的一些核心類,好比 String。

(2)同時也避免了類的重複加載,由於 JVM中區分不一樣類,不單單是根據類名,相同的 class文件被不一樣的 ClassLoader加載就是不一樣的兩個類。

 


2. 自定義類加載器

(1)從上面源碼看出,調用loadClass時會先根據委派模型在父加載器中加載,若是加載失敗,則會調用當前加載器的findClass來完成加載。

(2)所以咱們自定義的類加載器只須要繼承ClassLoader,並覆蓋findClass方法,下面是一個實際例子,在該例中咱們用自定義的類加載器去加載咱們事先準備好的class文件。

 

2.1 自定義一個People.java類作例子,.java編譯後生成.class,即二進制字節流文件

public class People { //該類寫在記事本里,在用javac命令行編譯成class文件,放在d盤根目錄下
    private String name; public People() {} public People(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return "I am a people, my name is " + name; } }

2.2 自定義類加載器

自定義一個類加載器,須要繼承ClassLoader類,並實現findClass方法。其中defineClass方法能夠把二進制流字節組成的文件轉換爲一個java.lang.Class(只要二進制字節流的內容符合Class文件規範)。

import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; public class MyClassLoader extends ClassLoader { public MyClassLoader() { } public MyClassLoader(ClassLoader parent) { super(parent); } protected Class<?> findClass(String name) throws ClassNotFoundException { File file = new File("D:/People.class"); try{ byte[] bytes = getClassBytes(file);
//defineClass方法能夠把二進制流字節組成的文件轉換爲一個java.lang.Class Class<?> c = this.defineClass(name, bytes, 0, bytes.length); return c; } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } private byte[] getClassBytes(File file) throws Exception { // 這裏要讀入.class的字節,所以要使用字節流 FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel wbc = Channels.newChannel(baos); ByteBuffer by = ByteBuffer.allocate(1024); while (true){ int i = fc.read(by); if (i == 0 || i == -1) break; by.flip(); wbc.write(by); by.clear(); } fis.close(); return baos.toByteArray(); } }

2.3 在主函數裏使用

MyClassLoader mcl = new MyClassLoader(); Class<?> clazz = Class.forName("People", true, mcl); Object obj = clazz.newInstance(); System.out.println(obj); System.out.println(obj.getClass().getClassLoader());//打印出咱們的自定義類加載器

 

2.4 運行結果


ClassLoader中的defineClass方法:
//Converts an array of bytes into an instance of class <tt>Class</tt>.
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null); }

 

另外一個自定義類加載器示例

首先,咱們定義一個待加載的普通Java類:Test.java。放在com.huachao.cl包下:

複製代碼
package com.huachao.cl;

public class Test {
    public void hello() {
        System.out.println("恩,是的,我是由 " + getClass().getClassLoader().getClass()
                + " 加載進來的");
    }
}
複製代碼

注意:

若是你是直接在當前項目裏面建立,待Test.java編譯後,請把Test.class文件拷貝走,再將Test.java刪除。由於若是Test.class存放在當前項目中,根據雙親委派模型可知,會經過sun.misc.Launcher$AppClassLoader 類加載器加載。爲了讓咱們自定義的類加載器加載,咱們把Test.class文件放入到其餘目錄。

在本例中,咱們Test.class文件存放的目錄以下:


class文件目錄

接下來就是自定義咱們的類加載器:

複製代碼
import java.io.FileInputStream;
import java.lang.reflect.Method;

public class Main {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                    + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;

        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

    };

    public static void main(String args[]) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("D:/test");
        Class clazz = classLoader.loadClass("com.huachao.cl.Test");
        Object obj = clazz.newInstance();
        Method helloMethod = clazz.getDeclaredMethod("hello", null);
        helloMethod.invoke(obj, null);
    }
}

 

轉載請註明出處:http://blog.csdn.net/seu_calvin/article/details/52315125

相關文章
相關標籤/搜索