Java自定义类加载器的编写步骤
两个问题
-
为什么要使用自定义类加载器呢?
Java的class很容易就被反编译,那么我们需要做加密,那么我们加载这个类的时候就需要用到自定义加载器。并且如果需要加载的类不在classPath下,而是在硬盘其他地方或者是网络上,那么同样也需要用到自定也需要用到自定义类加载器
-
什么情况下使用自定义类加载器?
- 加密:Java代码容易被反编译,那么不想要人家看到源代码的时候就需要进行加密。加密之后自带的类加载器不能使用,那么就需要使用到自定义类加载器
- 非标准来源加载代码:比如硬盘其他路径上的class文件,或者网络传过来的class文件
如何自定义类加载器
我们先来看 sun.misc.Launcher
类中的源码,以 AppClassloader
为例,首先人家继承了 java.net.URLClassLoader
并且实现了加载类的 loadClass
方法,如图
然而我们查看 java.net.URLClassLoader
时我们可以看到,最终人家继承自 java.lang.ClassLoader
,如:
public class URLClassLoader extends SecureClassLoader implements Closeable // 继承自 SecureClassLoader
public class SecureClassLoader extends ClassLoader // 然后 SecureClassLoader 继承自 ClassLoader
注意看 AppClassloader
中的 loadClass
人家在调用父类的 loadClass
方法,父类的 loadClass
方法中调用了 findClass
方法
而 findClass
方法会抛出一个没有找到类的异常,并表示需要子类从写这个方法
那么我们根据这个思路新增一个自定义类加载器并继承 java.lang.ClassLoader
。
package com.xiaohh.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* 自定义类加载器
*/
public class XiaoHHClassLoader extends ClassLoader {
/**
* 查找 class
* @param name class 的名字
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// Class 类的存放路径
String resource = "C:/test/";
// 按照路径找到需要加载的 class 文件
File source = new File(resource, name.replace('.', '/').concat(".class"));
// 判断文件是否存在
if (!source.exists()) {
throw new ClassNotFoundException(name + "is not found. ");
}
FileInputStream fis = null;
try {
// 将文件以流的方式加载到内存中
fis = new FileInputStream(source);
// 将文件转换为字节数组
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
// 定义类并返回,此步骤没有抛出异常表示完成加载
return super.defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
// 处理方式可以更加严谨,请自行处理
throw new ClassNotFoundException(name + "is not found. ");
} finally {
// 关闭资源
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这段代码定义了一个名为 XiaoHHClassLoader
的自定义类加载器,我们注意一下这段代码
return super.defineClass(name, bytes, 0, bytes.length);
这段代码调用了 ClassLoader
中的 defineClass
方法,作用是将字节数组解析成一个 Class
实例
到此为止我们的自定义类加载器就编写完成了,我们现在编写一个类来测试一下
package com.xiaohh.customer;
public class Hello {
public void sayHello() {
System.out.println("Hello");
}
}
将这个文件编译之后放在 C:\test\com\xiaohh\customer
的文件夹中
注意在自己的项目中不要出现这个class!!!
然后我们写一个测试方法
package com.xiaohh.test;
import java.lang.reflect.InvocationTargetException;
public class TestMain {
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException, InvocationTargetException, NoSuchMethodException {
// 获取自定的类加载器
ClassLoader loader = new XiaoHHClassLoader();
// 加载硬盘上的 class 文件
Class<?> helloClass = loader.loadClass("com.xiaohh.customer.Hello");
// 获得这个类的对象,因为只有一个默认的无参构造
Object hello = helloClass.getConstructors()[0].newInstance();
// 查看类的信息
System.out.println("Class name: " + hello.getClass().getName());
System.out.println("Class loader: " + hello.getClass().getClassLoader()); // 查看使用的是哪个类加载器
// 调用这个类的方法
// helloClass.getMethods()[0].invoke(hello);
helloClass.getMethod("sayHello").invoke(hello);
}
}
然后查看测试结果
可以看到编写成功了