如何加载一个class文件

LZ说的这个文件,当然不仅仅指的是jar包中的class文件,而是硬盘上任意位置的class文件。
LZ首先想到的是Class的forName方法,该方法传入类的全名(xx.xx.xx.xx),返回该类的Class类的实例。然后再通过newInstance方法创建该类的实例:

Class<Example> clazz = (Class<Example>) Class.forName("pkg.Example");
Example example = clazz.newInstance();

但是,用这种方法加载class文件,该class文件必须在classpath下才可以。

@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

看源码可知,forName是通过ClassLoader来加载的,所以LZ认为其中一定可以有加载任意class文件的方法。

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    throws ClassFormatError
{
    return defineClass(name, b, off, len, null);
}

这个方法通过传入class文件的字节数字,可以返回Class类的实例。但是这个方法是protected的,所以要自己实现一个子类.

public class MyClassLoader extends ClassLoader {

    public Class<?> loadMyClass() {
        Class<?> clazz = null;
        String myClass = "/Users/zhengyi/Desktop/pkg/Example.class";
        File classFile = new File(myClass);
        if (classFile.exists()) {
            try (FileChannel fileChannel = new FileInputStream(classFile).getChannel()) {
                ByteBuffer byteBuffer = ByteBuffer.allocate((int) fileChannel.size());
                while ((fileChannel.read(byteBuffer)) > 0) {
                }
                byte[] b = byteBuffer.array();
                clazz = defineClass("Example", b, 0, b.length);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return clazz;
    }
}

Example.java的内容:

public class Example {
    public void sayHello() {
        System.out.println("Hello, My ClassLoader");
    }
}

然后就可以用自定义的这个加载器加载这个类了。

MyClassLoader loader = new MyClassLoader();
Class<?> example = loader.loadMyClass();
Method sayHello = example.getDeclaredMethod("sayHello");
sayHello.invoke(example.newInstance());

事实上,不用自己定义一个新方法,只要重写findClass方法就可以。

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clazz = null;
        String myClass = "/Users/zhengyi/Desktop/pkg/" + name + ".class";
        File classFile = new File(myClass);
        if (classFile.exists()) {
            try (FileChannel fileChannel = new FileInputStream(classFile).getChannel()) {
                ByteBuffer byteBuffer = ByteBuffer.allocate((int) fileChannel.size());
                while ((fileChannel.read(byteBuffer)) > 0) {
                }
                byte[] b = byteBuffer.array();
                clazz = defineClass(name, b, 0, b.length);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return clazz;
    }
}

加载时候使用loadClass方法来加载类。

MyClassLoader loader = new MyClassLoader();
Class<?> example = loader.loadClass("Example");
Method sayHello = example.getDeclaredMethod("sayHello");
sayHello.invoke(example.newInstance());

使用loadClass方法的好处是,它会帮你自动完成双亲委派的操作。

发表评论