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是谁?