上次我们说到Java的双亲委托机制,导致我们不能加载到WangHouse里的Socker类。今天我们来说说怎么在不改变目录结构的情况下加载Socker。
自定义ClassLoader可能比较陌生。既然我们知道AppClassLoader是对应当前的classpath,而ClassLoader会根据class path来加载类,那么我们可以再自定义一个ClassLoader,将它的路径指定到WangHouse。 自定义ClassLoader分两步 · 继承 java.lang.ClassLoader · 重写 findClass方法
这里需要说一下为什么只重写findClass方法。JDK在搜索类的时候,会先调用 loadClass,在找不到的时候会调用 findClass,虽然重写loadClass()方法也可以,但是这样会改写原有的正常查找逻辑,因此我们只需要重写findClass()就可以,这是Java预留给开发者的可重载方法。
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";
}
}
}
简单的解释代码的逻辑是在传进去的路径下查找对应的class文件,把它转换为文件流,通过ClassLoader的 defineClass方法获得Class对象。
现在我们有可以找class的工具了,目标的类Socker在隔壁WangHouse,调用的代码这么写
public class Ming {
public static void main(String[] args) {
System.out.println("Ming looking for socker");
findSocker();
}
private static void findSocker() {
DiskClassLoader loader = new DiskClassLoader("../WangHouse/");
try {
Class b = loader.loadClass("Socker");
if(b != null){
try {
Object object = b.newInstance();
Method method = b.getDeclaredMethod("callSockerMethod", null);
method.invoke(object, null);
} catch(Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果
$ java Ming Ming looking for socker Socker method invoke
看到这里肯定会有疑问,为什么要用反射。我们接着说。
为什么要用反射来获取Socker对象?既然都能找到类,也已经加载进来了,为何不直接 new 就完了?
其实很简单,因为Ming和Socker并不在同一个ClassLoader里,Ming在AppClassLoader中,而Socker我们加载到了DiskClassLoader里。子Loader加载类的时候可以去父Loader里找到,就像我们调用System,而父Loader用子Loader里的类的时候是不行的,只能用反射。
…可能有人已经意识到,Android的热修复是不是可以用自定义ClassLoader来做? 没错!有些热修复方案就是基于ClassLoader做的。 明天我们接着讲开发中时常遇到的ClassCastException。在我们准备好相关的点之后,就可以自己来写一个热修复框架了。