Java的高級知識中ClassLoader是很重要的一環。面試中有很多關於ClassLoader的問題,今天分析一道例子。
問題
場景:假設有三個類 A B C,B和C跟A類不在一個路徑下。A使用B類,但沒辦法通過直接引用的方法使用它。B類對C有引用。 問題1:A如何訪問B類? 問題2:B和C類的ClassLoader是誰?
我們分兩篇說明這兩個問題。今天先分析問題1。
爲什麼A不能直接引用B類
類的加載是通過ClassLoader去做的,當A類要使用B類的時候,A的ClassLoader首先會從最根的ClassLoader去尋找類B,然後依次往下找,最終如果A的ClassLoader也找不到的話,會報ClassNotFoundException。這就是雙親委派的簡單原理。
A類的ClassLoader找不到B類的原因是,A的ClassLoader只會去找同個路徑下的class文件,而B不在這個路徑下。
接下來回答怎麼加載B類的問題
自定義ClassLoader
類的加載是通過ClassLoader去加載class文件。對於ClassLoader,其實我們可以隨心所欲的自定義它,只要重載findClass()方法就可以。
這裏實現了一個可以根據路徑加載class的ClassLoader,
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; /** * Created by phoenix on 2018/3/12. */ public class DiskClassLoader extends ClassLoader { private String mFilePath; public DiskClassLoader(String mFilePath) { this.mFilePath = mFilePath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String fileName = getFileName(name); File file = new File(mFilePath, fileName); try { FileInputStream ins = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; try { while((len = ins.read()) != -1) { bos.write(len); } } catch (IOException ioe) { ioe.printStackTrace(); } byte[] data = bos.toByteArray(); ins.close(); bos.close(); return defineClass(name, data, 0, data.length); } catch (IOException exp) { exp.printStackTrace(); } return super.findClass(name); } private String getFileName(String name){ int index = name.lastIndexOf('.'); if(index == -1) { return name + ".class"; } else { return name.substring(index) + ".class"; } } }
它的構造方法接受一個String作爲路徑,重載的findClass方法可以加載指定的class文件。 其實邏輯很簡單,不過二十多行代碼,這裏就不解釋了。 下面看如何使用它。
A的代碼像下面這樣,
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class A { private static void println(Object msg) { System.out.println("A " + msg); } public static void main(String[] args) { // System.out.println("Ming looking for socker"); println(A.class.getClassLoader()); checkClassCast(); } private static void checkClassCast() { try { DiskClassLoader loader1 = new DiskClassLoader("../BDirectory"); Class class1 = loader1.findClass("B"); Object classB = class1.newInstance(); Method method = class1.getDeclaredMethod("loaderTest", (Class<?>[]) null);//<--B類的方法 method.invoke(classB, (Object[])null); } catch(Exception e){ e.printStackTrace(); } } }
代碼註釋中 loaderTest()是B類裏的方法,通過自定義ClassLoader,我們也能調用上在另外路徑下的類B了。
下次我們會再聊聊如何回答第二個問題, B和C的ClassLoader是誰?