一、简介

通常情况下,我们都是直接使用系统类加载器,但是有些时候,由于某种特殊需求,我们也需要自定义类加载器。比如,应用程序是根据网络来传输字节码文件信息, 为了保证在网络传输过程中字节码文件的安全,通常都会进行加密,这样我们在加载类的时候,就需要进行解密,这种需求使用系统提供的类加载器是实现不了的,这就需要我们自己定义加密解密类加载器。自定义类加载器一般都是继承ClassLoader类。

二、自定义类加载器流程

【a】继承java.lang.ClassLoader类

【b】首先检查请求的类型是否已经被这个类加载器加载到命名空间中,如果已经装载则直接返回

【c】委托类加载请求给父类加载器,如果父类加载器能够完成加载工作,则返回父类加载器加载的Class实例

【d】调用本类的findClass()方法,试图获取对应的字节码,如果获取的到,则调用defineClass()导入类型到方法区,如果获取不到,返回异常给loadClass()方法,loadClass()转抛异常,终止加载过程。

  • 注意:被两个类加载器加载的同一个类,JVM并不认为是相同的类。

三、自定义类加载器示例

下面我们自定义一个文件系统类加载器,实现传入类的全限定名,然后根据IO流读取.class字节码信息。

首先在d:java下面新建一个Test.java:

package com.wsh;public class Test {public static void main(String[] args) {System.out.println("test");}
}

接着使用命令行工具编译Test.java,生成Test.class字节码文件:

这样在d:/java/com/wsh/路径下生成了Test.class字节码文件:

/*** @Description: 自定义文件系统类加载器* @author: weishihuai* @Date: 2019/1/16 16:32*/
public class FileSystemClassLoader extends ClassLoader {/*** 根目录路径*/private String rootDir;public FileSystemClassLoader(String rootDir) {this.rootDir = rootDir;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {//1. 查找该类加载器是否已经装载这个类,如果已经装载,则直接返回该Class对象Class<?> loadedClass = findLoadedClass(name);if (null == loadedClass) {//如果未装载,依据双亲委托机制,寻找父类加载器进行加载ClassLoader parent = this.getParent();try {loadedClass = parent.loadClass(name);} catch (Exception e) {
//                e.printStackTrace();}//如果父类加载器加载成功,则返回父类加载器加载的Class对象if (null != loadedClass) {return loadedClass;} else {//文件流读取返回字节数组byte[] classData = getClassData(name);//如果自己都加载失败的话直接抛出ClassNotFoundException异常if (null == classData) {throw new ClassNotFoundException();} else {//使用defineClass()加载类loadedClass = defineClass(name, classData, 0, classData.length);}}} else {return loadedClass;}return loadedClass;}/*** 根据路径名称获取.class字节数组信息** @param name 路径* @return*/private byte[] getClassData(String name) {  //com.wsh.Test  d:/java/com/wsh/Test.classStringBuilder path = new StringBuilder(rootDir).append(File.separator).append(name.replace(".", File.separator)).append(".class");ByteArrayOutputStream byteArrayOutputStream = null;InputStream inputStream = null;try {inputStream = new FileInputStream(path.toString());byteArrayOutputStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;while ((len = inputStream.read(buffer)) != -1) {byteArrayOutputStream.write(buffer, 0, len);}return byteArrayOutputStream.toByteArray();} catch (IOException e) {e.printStackTrace();} finally {if (null != byteArrayOutputStream) {try {byteArrayOutputStream.close();} catch (IOException e) {e.printStackTrace();}}if (null != inputStream) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}return null;}
}

测试类:

public class TestFileSystemClassLoader {public static void main(String[] args) {FileSystemClassLoader fileSystemClassLoader = new FileSystemClassLoader("d:/java");try {//加载d:/java/com/wsh/Test.class字节码信息Class<?> loaderClass = fileSystemClassLoader.loadClass("com.wsh.Test");//class com.wsh.TestSystem.out.println(loaderClass);//com.wsh.jvm.classloader.FileSystemClassLoader@677327b6System.out.println(loaderClass.getClassLoader());Object object = loaderClass.newInstance();//com.wsh.jvm.classloader.FileSystemClassLoader@677327b6System.out.println(object.getClass().getClassLoader());Class<?> loaderClass2 = fileSystemClassLoader.loadClass("com.wsh.jvm.classloader.Test");//由于双亲委托机制,在classpath下的类默认都会由AppClassLoader类加载器加载//sun.misc.Launcher$AppClassLoader@18b4aac2System.out.println(loaderClass2.getClassLoader());Class<?> loaderClass3 = fileSystemClassLoader.loadClass("java.lang.String");//因为Java核心类库是由引导类加载器BootStrapClassLoader进行加载,所以返回为null//nullSystem.out.println(loaderClass3.getClassLoader());} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {e.printStackTrace();}}
}
  • 注意点:因为类Test 本身可以被 AppClassLoader 类加载,因此我们不能把 Test.class 放在类路径下。否则,由于双亲委托机制的存在,会导致该类由AppClassLoader 加载,而不会通过我们自定义类加载器来加载。

四、自定义类加载器示例二

通过上面的示例,对自定义类加载器的流程已有初步的了解,接下来,我们再通过一个示例【自定义加密解密类加载器】加深对自定义类加载器的理解。

因为.class字节码文件是二进制文件,所以简单起见,实现对字节码取反的方式进行加密,然后使用自定义解密类加载器加载该类。

关于取反,我们可以通过异或的方式进行  xxxx ^ 0xff ,通过下图了解取反怎么取:

【第一步】编写加密方法:

    /*** 加密方法** @param src  源文件路径* @param dest 目标文件路径*/public static void encryptClass(File src, File dest) {InputStream fileInputStream = null;OutputStream fileOutputStream = null;try {fileInputStream = new FileInputStream(src);fileOutputStream = new FileOutputStream(dest);//接收长度int len;while ((len = fileInputStream.read()) != -1) {//通过异或操作对读取的输入流进行取反fileOutputStream.write(len ^ 0xff);}} catch (IOException e) {e.printStackTrace();} finally {if (null != fileOutputStream) {try {fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}if (null != fileInputStream) {try {fileInputStream.close();} catch (IOException e) {e.printStackTrace();}}}}

【第二步】对.class二进制字节码文件进行取反加密:

public class Test{ public static void main(String[] args) {encryptClass(new File("d:/java/com/wsh/Test.class"), new File("d:/java/temp/com/wsh/Test.class"));}
}

执行完之后,在temp/com/wsh目录下就可以看到加密之后的字节码文件,下面我们就需要自定义解密类加载器去读取这个字节码文件:

【第三步】自定义解密类加载器

/*** @Description: 解密类加载器* @Author: weishihuai* @Date: 2019/1/16 21:36*/
public class DecipherClassLoader extends ClassLoader {/*** 根目录路径*/private String rootDir;public DecipherClassLoader(String rootDir) {this.rootDir = rootDir;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {//1. 查找该类加载器是否已经装载这个类,如果已经装载,则直接返回该Class对象Class<?> loadedClass = findLoadedClass(name);if (null == loadedClass) {//如果未装载,依据双亲委托机制,寻找父类加载器进行加载ClassLoader parent = this.getParent();//这里加try-catch的原因是怕父类加载器加载失败之后,报错就不会执行下面的代码.try {loadedClass = parent.loadClass(name);} catch (Exception e) {
//                e.printStackTrace();}//如果父类加载器加载成功,则返回父类加载器加载的Class对象if (null != loadedClass) {return loadedClass;} else {//文件流读取返回字节数组byte[] classData = getClassData(name);//如果自己都加载失败的话直接抛出ClassNotFoundException异常if (null == classData) {throw new ClassNotFoundException();} else {//使用defineClass()加载类loadedClass = defineClass(name, classData, 0, classData.length);}}} else {return loadedClass;}return loadedClass;}/*** 根据路径名称获取.class字节数组信息** @param name 路径* @return*/private byte[] getClassData(String name) {StringBuilder path = new StringBuilder(rootDir).append(File.separator).append(name.replace(".", File.separator)).append(".class");ByteArrayOutputStream byteArrayOutputStream = null;InputStream inputStream = null;byte[] data = null;try {inputStream = new FileInputStream(path.toString());byteArrayOutputStream = new ByteArrayOutputStream();int len;while ((len = inputStream.read()) != -1) {//对加密后的二进制文件再次取反就是解密byteArrayOutputStream.write(len ^ 0xff);}data = byteArrayOutputStream.toByteArray();} catch (IOException e) {e.printStackTrace();} finally {if (null != byteArrayOutputStream) {try {byteArrayOutputStream.close();} catch (IOException e) {e.printStackTrace();}}if (null != inputStream) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}return data;}
}

【第四步】测试解密类加载器

如果我们使用之前的FileSystemClassLoader来加载这个加密后的在字节码文件的话,会直接报错:

public class TestDecrptClassLoader {public static void main(String[] args) {FileSystemClassLoader fileSystemClassLoader = new FileSystemClassLoader("d:/java/temp");try {Class<?> clazz = fileSystemClassLoader.loadClass("com.wsh.Test");System.out.println(clazz);} catch (ClassNotFoundException e) {e.printStackTrace();}}
}

下面我们使用DecipherClassLoader来加载该类:

public class TestDecrptClassLoader {public static void main(String[] args) {DecipherClassLoader decipherClassLoader = new DecipherClassLoader("d:/java/temp");try {Class<?> clazz2 = decipherClassLoader.loadClass("com.wsh.Test");System.out.println(clazz2);System.out.println(clazz2.getClassLoader());} catch (ClassNotFoundException e) {e.printStackTrace();}}
}

由上图可见,已经成功通过自定义的解密类加载器加载了Test类。

五、总结

通过两个自定义类加载器,对自定义类加载器的流程有了进一步的认识,通常情况下,我们使用系统默认的类加载器即能满足大部分的需求,对于一些特殊的需求,那么我们可以通过自定义类加载器来满足特定的需求。本文是笔者对自定义类加载器的一些见解和认识,仅供大家学习参考,不对之处,希望大家多多指点。

JVM初识之自定义类加载器相关推荐

  1. Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

    Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...

  2. jvm十二:自定义类加载器

    package com.atChina.jvm;import java.io.*;public class Test16 extends ClassLoader{private String clas ...

  3. 面试-JVM-类加载-类加载器--自定义类加载器-JVM调优

    文章目录 ==类加载== 谈谈你对类文件结构的理解?有哪些部分组成? 谈谈你对类加载机制的了解? 编写java代码是如何运行起来的? 类加载机制 类加载各阶段的作用分别是什么? 有哪些类加载器?分别有 ...

  4. JVM自定义类加载器在代码扩展性的实践

    一.背景 名单管理系统是手机上各个模块将需要管控的应用配置到文件中,然后下发到手机上进行应用管控的系统,比如各个应用的耗电量管控:各个模块的管控应用文件考虑到安全问题,有自己的不同的加密方式,按照以往 ...

  5. JVM——自定义类加载器

    0. 为什么需要自定义类加载器   网上的大部分自定义类加载器文章,几乎都是贴一段实现代码,然后分析一两句自定义ClassLoader的原理.但是我觉得首先得把为什么需要自定义加载器这个问题搞清楚,因 ...

  6. JVM类加载机制、双亲委派机制、自定义类加载器、打破双亲委派机制

    1.类加载器 站在Java虚拟机的角度看,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机.JDK8中), ...

  7. Java的自定义类加载器及JVM自带的类加载器之间的交互关系

    JVM自带的类加载器: 其关系如下: 其中,类加载器在加载类的时候是使用了所谓的"父委托"机制.其中,除了根类加载器以外,其他的类加载器都有且只有一个父类加载器. 关于父委托机制的 ...

  8. 自定义类加载器以及打破双亲委派

    0x01 自定义类加载器 自定义类加载器加载一个类需要:继承ClassLoader,重写findClass,如果不想打破双亲委派模型,那么只需要重写findClass:如果想打破双亲委派模型,那么就重 ...

  9. java简单通讯录的实现02person类_Java自定义类加载器实现不同版本的类加载

    一 什么是类隔离技术 只要你 Java 代码写的足够多,就一定会出现这种情况:系统新引入了一个中间件的 jar 包,编译的时候一切正常,一运行就报错:java.lang.NoSuchMethodErr ...

  10. 自定义类加载器在复杂类加载情况下的运行分析

    在之前咱们都在研究自定义类加载器的一些东东,不过接一来的学习还会依托于之前咱们写的MyTest16这个自定义类加载器,这里先再回顾一下: public class MyTest16 extends C ...

最新文章

  1. 阿里云K8S容器服务的使用
  2. response.end
  3. 单(liu_runda学长的神题)
  4. 使用组件构建Android应用程序
  5. labelimg选中高亮
  6. HDU1010 Tempter of the Bone DFS+剪枝
  7. wxWidgets:wxScopedArray< T >类模板的用法
  8. 特征向量按照特征值大小进行排序
  9. 菜鸟的MySQL学习笔记(三)
  10. java pdf添加图片_java pdf指定位置插入图片?
  11. python爬取招聘网信息并保存为csv文件
  12. arn : 无法加载文件 C:\Users\zky\AppData\Roaming\npm\yarn.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.
  13. 上下协同,用友IPD的研发管理之道(下)
  14. SSL证书的几个误解,正确认识SSL证书
  15. 推荐一款清爽的实时监控大屏附安装教程
  16. 【T+】畅捷通T+选项设置界面没有“数据精度”调整项
  17. huge形式_big的最高级形式
  18. python爬虫实例——中国电影票房(续)
  19. 专利申请需要哪些资料
  20. 数组的操作-集大成篇

热门文章

  1. Tensorflow 2.0 : FCNN
  2. C/C++[codeup 2088]排名
  3. StarUML接口视图修改为类的形式
  4. 算法:回溯二 生成有效括号对Generate Parentheses
  5. 数据集:不同地区居民消费数据
  6. 什么叫做形态学图像处理_【视觉】机器视觉技术和无人天车有什么关系?
  7. html5的file api,HTML5 File API
  8. mysql backup user_mysql备份常见命令
  9. 搜狐校园“情感分析×推荐排序“算法大赛 AutoX方案 转载poteman
  10. 468.验证IP地址