web发展这么好,html用的这么多,主要还是在于部署起来方便,更新也快。应用程序的缺点就是更新复杂。通过该文章,完全可以让应用程序和web一样,部署容易。使用方便。

 

上篇提到Java程序运行只依赖于字节码,修改class文件读写方式可以实现代码加密。所谓“字节码”只是一个byte字节序列,并非一定是class文件。也可从远程获取字节码实现类加载,使代码在客户端“不存在”,不仅安全性更高,还有一些额外优势。下面先来看如何实现。

 

本文中通信采用jboss的开源框架Netty。这是一个异步通信框架,严格说不很实现远程类加载,采用Netty只是因为个人比较熟悉,完全可以用其他通信方式替代(如Http方式)。

 

远程加载的逻辑不难:客户端发起加载请求并告知需要加载的全类名,服务端从本地资源中检索到对应class文件的字节码,并将其回传给客户端,接下来的加载就与本地加载一样了。下面是服务端代码(Netty使用参见官网Demo,本文不做过多解释,只给出逻辑处理代码):

客户端请求只包含一个全类名的字符串。cyptdataCache是一个byte[]软引用的Map,用来缓存被加载过的字节码序列,以减少反复从硬盘读文件。红框部分是构建返回值的代码,返回值就是一个字节数组,其结构如下:

字节码文件存放在服务端.\resource\目录下,加密与否皆可(如加密,客户端要有相应的解密器,参见上篇文章)。返回值之所以添加一个全类名的头,是由于Netty的异步通信特性,如果采用Http这样的同步方式,直接返回字节码就可以了。如找不到请求的类,用一个特殊字节0xFF(-1)表示。

 

客户端稍复杂些,先定义一些通信相关的对象:

 

clCh是Netty包装的远程连接对象;ready标志连接是否初始化成功。其他三个是为了应对Netty的异步特征:我们把已发出的类加载请求缓存在remoteTaskList对象,同时对这个对象监听,Server端的返回值也存放在remoteTaskList的value中。

 

连接初始化的代码如下,GJVMClassLoadClient是一个自定义的初始化类,功能就是发起连接。这个类的实现参见Netty的Demo源码

之后是发起加载请求的方法:

clCh.write就是发送远程请求。之后通过一个限时的循环监听,一旦发现有返回值,就从remoteTaskList获取字节码,解密后(如有加密)作为方法返回值。之前提到Netty的异步特征不很适合作为远程类加载。此处的循环是一种异步转同步的“权宜之计”,逻辑上不完全严格,而且可能会造成一些额外延时。

 

请求发出后,返回值的处理代码如下:

先从返回值的头信息中获取全类名,并把后面的字节码存放到remoteTaskList中去。Netty的通信是在自身的通信线程中实现,之前的循环代码其实就是主线程等待通信线程返回。上面代码最后两句是实现一种存储模式,用户可以选择是否将远程字节码存储到本地,下次启动就不必再去远程获取了。

 

至此整个加载过程就基本完整了。为在加载过程中启用远程加载,可以通过改写sun.misc.Lanuncher$ AppClassLoader.loadClass()方法实现。添加的代码如下(参见上篇文章)。

另需注意所有嵌入rt.jar的自定义类都采用系统类的命名空间(sun.misc.*),包括Netty的相关类,也必须把默认的org.jboss.netty.*命名空间改成sun.misc.netty.*(必须将Netty源码重新编译,直接引用Jar包是不行的)。关于JVM系统类的加载策略与命名空间的相关问题,将在后续文章进一步探讨。

 

使用远程类加载后,项目发布时只需打包一段简单的引导代码,逻辑代码则放在服务端的resource目录下,这就实现了一种“轻客户端”,使业务空间几乎不占用空间。项目运行时,直接把字节码从远程加载到本地内存,进一步增强了安全性(通过拦截通信或内存分析来破解显然比分析静态字节码难很多);如果项目有更新,只要替换服务端的字节码,客户端在重启后就能自动更新,使项目版本维护变得简化。

 

之前提到的“存储模式”,还可实现一个额外功能:动态类抽取。开发过程中用到第三方Jar包,有时只需用到其中一部分类,如引用整个Jar包则显得累赘,手动又很难确定究竟哪些类有用。这时用存储模式的远程加载就很合适,用到的类会被自动“抽取”到本地。

 

远程类加载也有一些劣势。最明显的是启动速度变慢(即使忽略异步转同步的影响),这只能通过几种加载模式的配合,在“轻量级”和“加载速度”之间做权衡;另外,java系统类本身是无法被自定义加载的(至少本文的这种方案不行),例如java.awt.*。原因是defineClass方法对全类名的命名空间有过滤,以java.开头的包名会抛出“java.lang.SecurityException”,这是JVM对系统类的一种保护措施。但是rt.jar有40+MB大,单个项目所用的其实也只是一部分,所以对系统类进行上述动态类抽取也是切实的需求。后续文章将探讨java系统类命名过滤的底层实现,以及绕过这种过滤策略的可行性。

 

(对本文内容感兴趣或有疑问,欢迎加群291694807讨论)

posted on 2013-02-01 14:33  编程趋势  阅读(2243)  评论(4编辑  收藏  举报