這周在看《深刻理解Java虛擬機 JVM高級特性與最佳實踐(高清完整版)》,就地取材寫寫第7章中提到的類加載器。如下源碼截自java8。java
* <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
/** * 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爲自定義類加載器提供了入口。數組
/** * 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™ 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