自定義類加載器

這周在看《深刻理解Java虛擬機 JVM高級特性與最佳實踐(高清完整版)》,就地取材寫寫第7章中提到的類加載器。如下源碼截自java8。java

delegation model

* <p> The <tt>ClassLoader</tt> class uses a delegation model to search for
 * classes and resources.  Each instance of <tt>ClassLoader</tt> has an
 * associated parent class loader.  When requested to find a class or
 * resource, a <tt>ClassLoader</tt> instance will delegate the search for the
 * class or resource to its parent class loader before attempting to find the
 * class or resource itself.  The virtual machine's built-in class loader,
 * called the "bootstrap class loader", does not itself have a parent but may
 * serve as the parent of a <tt>ClassLoader</tt> instance.

截取自源碼開篇註釋。「delegation model」大部分文章譯爲「雙親委派模型」(我的感受不是很貼切,「雙」字很容易產生誤解),闡述了一種類加載順序關係。請求查找類或資源時,ClassLoader實例會先交給父級類加載器處理(組合實現,非繼承),依次類推直到"bootstrap class loader",父級沒法處理(在其範圍內找不到對應類/資源)了再由本身加載。聽說這樣能夠避免同名類引起的安全隱患。類加載順序以下圖。bootstrap

image

loadClass --> findClass

/**
 * Loads the class with the specified <a href="#name">binary name</a>.
 * This method searches for classes in the same manner as the {@link
 * #loadClass(String, boolean)} method.  It is invoked by the Java virtual
 * machine to resolve class references.  Invoking this method is equivalent
 * to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
 * false)</tt>}.
 *
 * @param  name
 *         The <a href="#name">binary name</a> of the class
 *
 * @return  The resulting <tt>Class</tt> object
 *
 * @throws  ClassNotFoundException
 *          If the class was not found
 */
public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

/**
 * Loads the class with the specified <a href="#name">binary name</a>.  The
 * default implementation of this method searches for classes in the
 * following order:
 *
 * <ol>
 *
 *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
 *   has already been loaded.  </p></li>
 *
 *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
 *   on the parent class loader.  If the parent is <tt>null</tt> the class
 *   loader built-in to the virtual machine is used, instead.  </p></li>
 *
 *   <li><p> Invoke the {@link #findClass(String)} method to find the
 *   class.  </p></li>
 *
 * </ol>
 *
 * <p> If the class was found using the above steps, and the
 * <tt>resolve</tt> flag is true, this method will then invoke the {@link
 * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
 *
 * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
 * #findClass(String)}, rather than this method.  </p>
 *
 * <p> Unless overridden, this method synchronizes on the result of
 * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
 * during the entire class loading process.
 *
 * @param  name
 *         The <a href="#name">binary name</a> of the class
 *
 * @param  resolve
 *         If <tt>true</tt> then resolve the class
 *
 * @return  The resulting <tt>Class</tt> object
 *
 * @throws  ClassNotFoundException
 *          If the class could not be found
 */
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        // VM未加載返回null;已加載返回類對象
        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 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.
                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;
    }
}


/**
 * Finds the class with the specified <a href="#name">binary name</a>.
 * This method should be overridden by class loader implementations that
 * follow the delegation model for loading classes, and will be invoked by
 * the {@link #loadClass <tt>loadClass</tt>} method after checking the
 * parent class loader for the requested class.  The default implementation
 * throws a <tt>ClassNotFoundException</tt>.
 *
 * @param  name
 *         The <a href="#name">binary name</a> of the class
 *
 * @return  The resulting <tt>Class</tt> object
 *
 * @throws  ClassNotFoundException
 *          If the class could not be found
 *
 * @since  1.2
 */
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
    // 由自定義類加載器重載
}

體現了以上所述的類加載邏輯。findClass爲自定義類加載器提供了入口。數組

defineClass

/**
 * Converts an array of bytes into an instance of class <tt>Class</tt>,
 * with an optional <tt>ProtectionDomain</tt>.  If the domain is
 * <tt>null</tt>, then a default domain will be assigned to the class as
 * specified in the documentation for {@link #defineClass(String, byte[],
 * int, int)}.  Before the class can be used it must be resolved.
 *
 * <p> The first class defined in a package determines the exact set of
 * certificates that all subsequent classes defined in that package must
 * contain.  The set of certificates for a class is obtained from the
 * {@link java.security.CodeSource <tt>CodeSource</tt>} within the
 * <tt>ProtectionDomain</tt> of the class.  Any classes added to that
 * package must contain the same set of certificates or a
 * <tt>SecurityException</tt> will be thrown.  Note that if
 * <tt>name</tt> is <tt>null</tt>, this check is not performed.
 * You should always pass in the <a href="#name">binary name</a> of the
 * class you are defining as well as the bytes.  This ensures that the
 * class you are defining is indeed the class you think it is.
 *
 * <p> The specified <tt>name</tt> cannot begin with "<tt>java.</tt>", since
 * all classes in the "<tt>java.*</tt> packages can only be defined by the
 * bootstrap class loader.  If <tt>name</tt> is not <tt>null</tt>, it
 * must be equal to the <a href="#name">binary name</a> of the class
 * specified by the byte array "<tt>b</tt>", otherwise a {@link
 * NoClassDefFoundError <tt>NoClassDefFoundError</tt>} will be thrown. </p>
 *
 * @param  name
 *         The expected <a href="#name">binary name</a> of the class, or
 *         <tt>null</tt> if not known
 *
 * @param  b
 *         The bytes that make up the class data. The bytes in positions
 *         <tt>off</tt> through <tt>off+len-1</tt> should have the format
 *         of a valid class file as defined by
 *         <cite>The Java&trade; Virtual Machine Specification</cite>.
 *
 * @param  off
 *         The start offset in <tt>b</tt> of the class data
 *
 * @param  len
 *         The length of the class data
 *
 * @param  protectionDomain
 *         The ProtectionDomain of the class
 *
 * @return  The <tt>Class</tt> object created from the data,
 *          and optional <tt>ProtectionDomain</tt>.
 *
 * @throws  ClassFormatError
 *          If the data did not contain a valid class
 *
 * @throws  NoClassDefFoundError
 *          If <tt>name</tt> is not equal to the <a href="#name">binary
 *          name</a> of the class specified by <tt>b</tt>
 *
 * @throws  IndexOutOfBoundsException
 *          If either <tt>off</tt> or <tt>len</tt> is negative, or if
 *          <tt>off+len</tt> is greater than <tt>b.length</tt>.
 *
 * @throws  SecurityException
 *          If an attempt is made to add this class to a package that
 *          contains classes that were signed by a different set of
 *          certificates than this class, or if <tt>name</tt> begins with
 *          "<tt>java.</tt>".
 */
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

defineClass:接收以字節數組表示的類字節碼,並把它轉換成 Class 實例,該方法轉換一個類的同時,會先要求裝載該類的父類以及實現的接口類。重寫findClass將使用到。安全

public class Test {
    private static String link = "rebey.cn";
    static {
        System.out.println("welcome to: "+link);
    }
    
    public void print() {
        System.out.println(this.getClass().getClassLoader());
    }
}

將這段java代碼編譯成.class文件(可經過javac指令),放在 了E:201706下。同時在個人測試項目下也有一個/201705/src/classLoader/Test.java,代碼相同。區別就是一個有包名一個沒有包名。若是class文件中源碼包含package信息,屆時可能會拋出java.lang.NoClassDefFoundError (wrong name)異常。網絡

package classLoader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

public class CustomClassLoader extends ClassLoader{
    private String basedir; // 須要該類加載器直接加載的類文件的基目錄
    
    public CustomClassLoader(String basedir) {
        super(null);
        this.basedir = basedir;
    } 
    
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            byte[] bytes = loadClassData(name);
            if (bytes == null) {    
                throw new ClassNotFoundException(name);    
            }
            c = defineClass(name, bytes, 0, bytes.length);
        }
        return c; 
    }
    
    // 摘自網絡
    public byte[] loadClassData(String name) {
        try {
            name = name.replace(".", "//");
            FileInputStream is = new FileInputStream(new File(basedir + name + ".class"));
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;
            while ((b = is.read()) != -1) {
                baos.write(b);
            }
            is.close();
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

編寫自定義的類加載器,繼承ClassLoader,重寫了findClass方法,經過defineClass將讀取的byte[]轉爲Class。而後經過如下main函數調用測試:less

package classLoader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Loader {
    public static void main(String[] arg) throws NoSuchMethodException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        // 走自定義加載器
        CustomClassLoader ccl = new CustomClassLoader("E://201706//");
        Class<?> clazz = ccl.findClass("Test");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("print", null);
        method.invoke(obj, null);
        
        System.out.println("--------------我是分割線-------------------");
        
        // 走委派模式
        // 隱式類加載
        Test t1 = new Test();
        t1.print();
        
        System.out.println("--------------我是分割線-------------------");
        
        // 顯式類加載
        Class<?> t2 = Class.forName("classLoader.Test");
        Object obj2 = t2.newInstance();
        Method method2 = t2.getDeclaredMethod("print", null);
        method2.invoke(obj2, null);
        
        System.out.println("--------------我是分割線-------------------");
        
        Class<Test> t3 = Test.class;
        Object obj3 = t3.newInstance();
        Method method3 = t3.getDeclaredMethod("print", null);
        method3.invoke(obj3, null);
    }
}

輸出結果:

welcome to: rebey.cn
classLoader.CustomClassLoader@6d06d69c
--------------我是分割線-------------------
welcome to: rebey.cn
sun.misc.Launcher$AppClassLoader@73d16e93
--------------我是分割線-------------------
sun.misc.Launcher$AppClassLoader@73d16e93
--------------我是分割線-------------------
sun.misc.Launcher$AppClassLoader@73d16e93

靜態代碼塊隨着類加載而執行,並且只會執行一次,因此這裏t二、t3加載完成是並無再輸出。dom

說點什麼

ClassLoader線程安全;ide

同個類加載器加載的.class類實例才相等;函數

Class.forName(xxx.xx.xx) 返回的是一個類, .newInstance() 後才建立實例對象 ;post

Java.lang.Class對象是單實例的;

執行順序:靜態代碼塊 > 構造代碼塊 > 構造函數

一、父類靜態變量和靜態代碼塊(先聲明的先執行);

二、子類靜態變量和靜態代碼塊(先聲明的先執行);

三、父類的變量和代碼塊(先聲明的先執行);

四、父類的構造函數;

五、子類的變量和代碼塊(先聲明的先執行);

六、子類的構造函數。

應用

經過自定義加載類,咱們能夠:

①加載指定路徑的class,甚至是來自網絡(自定義類加載器:從網上加載class到內存、實例化調用其中的方法)、DB(自定義的類裝載器-從DB裝載class(附上對類裝載器的分析));

②給代碼加密;(如何有效防止Java程序源碼被人偷窺?

③裝逼(- -);


更多有意思的內容,歡迎訪問筆者小站: rebey.cn

相關文章
相關標籤/搜索