Java的高级知识中ClassLoader是很重要的一环。面试中有很多关于ClassLoader的问题,今天分析一道例子。
场景:假设有三个类 A B C,B和C跟A类不在一个路径下。A使用B类,但没办法通过直接引用的方法使用它。B类对C有引用。 问题1:A如何访问B类? 问题2:B和C类的ClassLoader是谁?
我们分两篇说明这两个问题。今天先分析问题1。
类的加载是通过ClassLoader去做的,当A类要使用B类的时候,A的ClassLoader首先会从最根的ClassLoader去寻找类B,然后依次往下找,最终如果A的ClassLoader也找不到的话,会报ClassNotFoundException。这就是双亲委派的简单原理。
A类的ClassLoader找不到B类的原因是,A的ClassLoader只会去找同个路径下的class文件,而B不在这个路径下。
接下来回答怎么加载B类的问题
类的加载是通过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是谁?