JVM进阶之自定义类加载器

Posted ProChick

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM进阶之自定义类加载器相关的知识,希望对你有一定的参考价值。

自定义类加载器

1.作用

  • 隔离加载类

    在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境。

  • 修改类加载的方式

    类的加载模型并非强制的,应该根据实际情况在某个时间点按需进行动态加载。

  • 扩展加载源

    可以从数据库、网络、甚至是电视机机顶盒进行加载。

  • 防止源码泄露

    可以对Java源码进行编译时的加密,还原时的解密。

2.场景

  • 当实现类似进程内隔离效果时,类加载器可用作不同的命名空间,以提供类似容器、模块化的效果。
  • 当应用需要从不同的数据源获取类定义信息时,比如:网络数据源、操纵字节码来动态修改或者生成类。

3.注意

  • 在一般情况下,使用不同的类加载器去加载不同的功能模块,会提高应用程序的安全性。
  • 但如果涉及Java类型之间的相互转换,那么只有两个类型都是由同一个类加载器所加载的,才能进行类型转换,否则转换时会发生异常。

4.实现

  • 常见的有两种做法:重写loadClass方法、重写findClass方法。
  • 这两种方法本质上差不多,毕竟在loadClass方法中也会调用findClass方法。但是从逻辑上讲,我们最好不要直接修改loadClass方法里面的内部逻辑, 因为这个方法是实现双亲委派模型逻辑的地方,擅自修改这个方法会导致模型被破坏,容易造成问题,所以建议使用第二种做法。

代码示例

public class MyClassLoader extends ClassLoader {
    private String byteCodePath;

    public MyClassLoader(String byteCodePath) {
        this.byteCodePath = byteCodePath;
    }

    public MyClassLoader(ClassLoader parent, String byteCodePath) {
        super(parent);
        this.byteCodePath = byteCodePath;
    }

    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        BufferedInputStream bis = null;
        ByteArrayOutputStream baos = null;
        
        try {
            // 获取字节码文件所在路径
            String fileName = byteCodePath + className + ".class";
            
            // 获取文件输入流
            bis = new BufferedInputStream(new FileInputStream(fileName));
            
            // 获取字节输出流
            baos = new ByteArrayOutputStream();
            
            // 将字节码文件写入到输出流当中
            int len;
            byte[] data = new byte[1024];
            while ((len = bis.read(data)) != -1) {
                baos.write(data, 0, len);
            }
            
            // 将字节输出流转化为字节数组
            byte[] byteCodes = baos.toByteArray();
			
            // 调用defineClass方法完成类实例的创建
            Class clazz = defineClass(null, byteCodes, 0, byteCodes.length);
            
            return clazz;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (bis != null) bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }
}
public class MyClassLoaderTest {
    public static void main(String[] args) {
        MyClassLoader loader = new MyClassLoader("字节码文件所在根路径");

        try {
            Class clazz = loader.loadClass("Demo");
            
            System.out.println("加载此类的类加载器为:" + 
                               clazz.getClassLoader().getClass().getName());

            System.out.println("加载此类的类加载器的父加载器为:" + 
                               clazz.getClassLoader().getParent().getClass().getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

以上是关于JVM进阶之自定义类加载器的主要内容,如果未能解决你的问题,请参考以下文章

JVM进阶之类加载器详解

JVM进阶之类加载器详解

JVM进阶之路十四:类加载器和类加载机制

JVM进阶之路十四:类加载器和类加载机制

JVM进阶之路十四:类加载器和类加载机制

JVM进阶之双亲委派机制