自定义类加载器的应用场景
加密: 如果你不想自己的代码被反编译的话.(类加密后就不能再用 ClassLoader 进行加载了, 这时需要自定义一个类加载器先对类进行解密, 再加载).
从非标准的来源加载代码: 如果你的字节码存放在数据库甚至是云端, 就需要自定义类加载器, 从指定来源加载类.
双亲委派
我们先看一下 ClassLoader 类默认的 loadClass 方法实现
- protected synchronized Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException {
- // First, check if the class has already been loaded
- Class c = findLoadedClass(name);
- if (c == null) {
- try {
- if (parent != null) {
- c = parent.loadClass(name, false);
- } else {
- c = findBootstrapClassOrNull(name);
- }
- } catch (ClassNotFoundException e) {
- // ClassNotFoundException thrown if class not found
- // from the non-null parent class loader
- // 父类加载器无法完成类加载请求
- }
- if (c == null) {
- // If still not found, then invoke findClass in order to find the class
- // 子加载器进行类加载
- c = findClass(name);
- }
- }
- if (resolve) {
- // 判断是否需要链接过程, 参数传入
- resolveClass(c);
- }
- return c;
- }
双亲委派模型工作过程如下:
(1) 类加载器从已加载的类中查询该类是否已加载, 如果已加载则直接返回.
(2)如果在已加载的类中未找到该类, 则委托给父类加载器去加载 c = parent.loadClass(name, false), 父类也会采用同样的策略查看自己加载的类中是否包含该类, 如果没有则委托给父类, 以此类推一直到启动类加载起.
(3)如果启动类加载器加载失败 (例如在 $JAVA_HOME/jre/lib 里未查找到该 class), 会使用拓展类加载器来尝试加载, 继续失败则会使用 AppClassLoader 来加载, 继续失败则会抛出一个异常 ClassNotFoundException, 然后再调用当前加载器的 findClass() 方法进行加载.
双亲委派的好处:
(1)避免自己编写的类动态替换 java 的核心类, 比如 String.
(2)避免了类的重复加载, 因为 JVM 区分不同类的方式不仅仅根据类名, 相同的 class 文件被不同的类加载器加载产生的是两个不同的类.
正题: 自定义类加载器
从上面的源码可以看出调用 classLoader 时会先根据委派模型在父类加在其中加载, 如果加载失败则会加载当前加载器的 findClass 方法来加载,
因此我们自定义的类加载器只需要继承 ClassLoader, 并覆盖 findClass 方法.
准备一个 class 文件, 编译后放到 D 盘根目录下
- public class People {
- private String name;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
自定义类加载器 MyClassLoader , 继承 ClassLoader 覆盖 findClass 方法(其中 defineClass 方法可以把二进制流字节组成的文件转换为一个 java.lang.Class)
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.nio.ByteBuffer;
- import java.nio.channels.Channels;
- import java.nio.channels.FileChannel;
- import java.nio.channels.WritableByteChannel;
- public class MyClassLoader extends ClassLoader
- {
- public MyClassLoader(){}
- public MyClassLoader(ClassLoader parent)
- {
- super(parent);
- }
- protected Class<?> findClass(String name) throws ClassNotFoundException
- {
- File file = new File("D:/People.class");
- try{
- byte[] bytes = getClassBytes(file);
- //defineClass 方法可以把二进制流字节组成的文件转换为一个 java.lang.Class
- Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
- return c;
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- return super.findClass(name);
- }
- private byte[] getClassBytes(File file) throws Exception
- {
- // 这里要读入. class 的字节, 因此要使用字节流
- FileInputStream fis = new FileInputStream(file);
- FileChannel fc = fis.getChannel();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- WritableByteChannel wbc = Channels.newChannel(baos);
- ByteBuffer by = ByteBuffer.allocate(1024);
- while (true){
- int i = fc.read(by);
- if (i == 0 || i == -1)
- break;
- by.flip();
- wbc.write(by);
- by.clear();
- }
- fis.close();
- return baos.toByteArray();
- }
- }
在主函数中测试一下
- MyClassLoader mcl = new MyClassLoader();
- Class<?> clazz = Class.forName("People", true, mcl);
- Object obj = clazz.newInstance();
- System.out.println(obj);
- // 打印出我们的自定义类加载器
- System.out.println(obj.getClass().getClassLoader());
来源: https://juejin.im/entry/5be1346ee51d4570934d7fc4