一文读懂Classloader(一)
附赠JavaAssist和CGlib的部分用法
一、 略过的部分
这个主题源自一个朋友的问题:如何在区块链上安全地完成热更新。自定义Classloader是一个比较成熟的方式,但是需要解决几个问题:
1、 生成.class的安全问题
2、 如何避免.class在JVM中加载的是旧版本的代码
3、 如何动态生成.class文件
按照惯例,我们依然省略一些基础但是需要掌握的问题,需要的可自行百度:
1、 系统的Classload有哪些?
2、 Classloader的双亲委派原则
3、 Class相关的反射属性与方法
二、 Classloader与自定义Classloader
2.1 Classloader的加载原理
我们先看Classloader的加载函数,如图1所示:
图一 Classloader的加载类方法
在这个方法中,大部分教程都说明了,加载类,先从parent去加载,然后在当前loader实例加载。除此以外,我们还需要看下findLoadedClass这个方法,如图二所示。
图二 findLoadedClass
这是一个Native方法,也就是从JVM查找已经加载的类。遗憾的是,JVM并未提供卸载类。
那么如何进行热卸载,我们在下个章节再详述。在这个方法中,我们还看到了另外一个方法:
findClass。我们就是通过重载这个方法完成自定义的Classloader来加载.class文件,这个很简单,如图三所示
图三 重载findClass方法
2.2 Class的安全问题
看图三,我们对返回值做了一个Decode的操作,这个解码我们对文件做了一个简单的定义,读者可根据自己的需要进行加密和解密。笔者采用的加密方式如下:
//简单加密的文件格式大致如下
//文件头:文件类型(DCLASS:6字节)+ 加密Key方式(1字节:0:约定加密;1:自带加密key)
//如果是约定加密,则后面为正文密文;如果是自带加密key则格式为:加密key长度(1字节)+加密key(n字节)
//加密采用XOR方式
根据这个加密规则,我们来看解密的代码,如图四所示
图四 基于异或加密的解密方式
现在出现了下一个问题,如何进行加密呢?可能可以按照这样的方式
Javac -d xxx.java
读取class文件,然后进行加密
笔者觉得这种方法不够方便,因此采用两种方式进行加密:
1、 对已存在的Class进行加密
2、 动态创建class,并进行加密
动态创建class参考了dubbo的代码ClassGenerator,这个类主要是使用了JavaAssist这个工具,而这个工具相当接近我们创建class的思维,依次结构为:
CtClass:对应class
CtField:对应成员变量Field
CtConstructor:对应构造函数
CtMethod:对应方法
AnnotationsAttribute:注解属性
我们看ClassGenerator的转换成CtClass的方法toClass,如图五所示
图五 动态生成类核心代码
而加载已有的class代码也是使用JavaAssist,如图六所示
图六 加载已有的class
这里有个问题,如果class是动态热加载的,pool.get(“xxxx”)的时候会提示抛出classNotFoundException,我们只需要按照箭头部分添加classPath即可。我们最后来看测试用例的代码,如图七所示。
图七 加密Classloader的测试用例
其中文件的内容如下,是我们定义的加密文件