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); } }
然后查看测试结果
可以看到编写成功了