多说两句关于ClassLoader的面试题

1,157 阅读4分钟
原文链接: mp.weixin.qq.com

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