Java应用程序的动态加载与字节码文件解密过程的讨论
2012-07-19#############2012-07-19######2#0#12-07-19########Java应用程序的动态加载与字节码文件解密过程的讨论
周天宏 曹大有
( )郧阳师范高等专科学校 计算机科学系 丹江口 442700
摘要 :通过对 J ava应用程序的执行过程和 J ava类加载器的加载机制的
,探讨了如何在 J ava类的显示加载和通过自定义类 加载器进行加载两种方式下 ,让一个 J ava应用程序通过动态加载并执行另一个 J ava应用程序的具体过程 ,并对之进行了深入 的讨论 。
关键词 :应用程序 字节码 加载器 虚拟机
The Ja va A pp l ie s the D iscu ss ion tha t the D ynam ic S ta te of the Procedure
A dd s to Ca rry w ith the Perform an ce C ircum stan ce
ZHOU Tianhong, CAO D ayou
( )Yunyang Teache r’s Co llege, D an jiangkou 442700, Ch ina
A b stra c t: Th is text add s the ana lysis of ca rry the m echan ism th rough p e rfo rm ance p roce ss and J ava typ e s tha t app lie s the p rocedu re to the J ava, inqu iring in to how to add to ca rry in the J ava m an ife sta tion and add to ca rry the m ach ine to ca rry on add to ca rry from the def2 in ition typ e unde r two k ind s of m e thod s, le t a J ava app lica tion p rocedu re add to ca rry and ca rry ou t ano the r one conc re te p roce ss of the J ava app lica tion p rocedu re th rough a dynam ic sta te, and ca rried on the tho rough d iscu ssion to it.
Keyword s: App lica tion, B yte code, Loade r V irtua l, M ach ine
我们通常运行 J ava应用程序时 ,一般是在命令行窗口中 ,通过输入命令行参数 : java App lica tion _nam e, 来完成对指定的应用程序的加载和解释执行的 。但是我们能否让一个 Java应用程序来动态加载并执行其 他 J ava应用程序呢 ? 为了解决这一问题 ,我们必须首先对 Java应用程序的执行过程和 Java的类加载机制做 一简单的分析 。
J ava编程语言编译器能够将 J ava源程序转换为某种假想机器的机器语言 ,这种假想机器称为 J ava虚拟机 ,虚拟机代码存储在扩展名为 . c la ss的类文件中 ,类文件包含一个类的全部方法的代码 ,这些类文件必须 由一个翻译器进行解释 ,该翻译器能够将虚拟机的指令集翻译成目标机器的机器语言 ,这个翻译器称为 Java
虚拟机解释器 。
虚拟机解释器通过 Java的类加载器只加载程序运行所需要的类文件 ,即该类的主类文件和该主类所依
( )赖的其它类的类文件以及 m a in 方法所调用的更多的类 。然而 Java的类加载机制并非只使用单个的类加 载器 。每个 J ava应用程序至少要有下面 3个类加载器 :
引导类加载器 :负责加载系统类 ,通常从 rt. ja r文件那里进行加载 ;
扩展类加载器 :用于从 jre / lib / ext目录加载一个
的扩展名 ;
系统类加载器:负责加载应用程序类。 2012-07-19#############2012-07-19######2#0#12-07-19########
隐式加载 :即当一个类被解析时 ,该类所依赖的那些类被加载时所采用的加载方式 ;
( )显示加载 :它没有命名的类加载器 ,而是通过调用 C la ss. fo rN am e c la ssN am e方法进行类的加载 ;
( )自定义类加载器 :即通过调用抽象类 C la ssLoade r的 loade r. loadC la ss c la ssN am e方法进行加载 。 根据以
上的分析 ,我们如果要想在一个 J ava程序中动态加载并执行其他的 Java 应用程序 ,必须进行对
指定类的加载 ,那么所使用的类加载机制只能是显示加载和通过自定义类加载器进行加载这两种方式 。1 通过类的显示加载方式来运行其他应用程序
若字节码文件没有被用作其他处理 ,如没有进行加密处理 ,那么我们可以通过类的显示加载方式来动态
( )( ) 加载指定名的字节码文件 ,即通过调用 C la ss. fo rN am e c la ssN am e方法来进行类的动态加载 。而 fo rN am e 是 C la ss类中的一个静态方法 ,它的方法原型为 :
( ) p ub lic sta tic C la ss fo rN am e String c la ssN am eth row s C la ssNo tFoundExcep tion
它可以用来为一个已知命名了的类获得 C la ss对象 。而此时所需的类加载器是 Java默认的类加载器 , 按照 J ava默认的类加载器加载类的机制 ,若 c la ssN am e拥有另一个类型的类的实例变量或超类 ,那么这些类 所在的字节码文件也会被 J ava默认的类加载器进行加载 。由于每个 J ava 应用程序都是以主类中的主方法
( )( )m a in 为执行起点的 ,所以我们只要从主类中通过 J ava 提供的反射功能 ,寻找并执行主类的 m a in 方法 , 从而就可以让整个应用程序执行起来 。具体过程要涉及到以下方法 :
( )首先是 C la ss类中的 ge tM e thod 方法 ,它的方法原型为 :
( )p ub lic M e thod ge tM e thod String nam e, C la ss[ ] p a ram e te rTyp e s
th row s NoSuchM e thodExcep tion, Secu rityExcep tion
该方法从指定的类中寻找并返回由 nam e命名并具有指定参数 p a ram e te rTyp e s的方法对象 ,当然我们这
( ) 里寻找的 nam e为 " m a in" ,而 p a ram e te rTyp e s参数对象为 : new C la ss[ ] { a rgs. ge tC la ss }。其次是 M e thod
( )类中的 invoke 方法 ,它的方法原型为 :
()p ub lic O b jec t invoke O b jec t ob j, O b jec t[ ] a rgs
th row s Illega lA cce ssExcep tion, Illega lA rgum en tExcep tion, Invoca tionTa rge tExcep tion
该方法将执行 M e thod对象所代
的方法 ,其中 : a rgs是该方法需要的参数 ,而 ob j一般可以为 nu ll。实
( ) 际用法由下面 runC la ss String nam e方法所示 。
( ) ( ) p ub lic vo id runC la ss String nam e{ m. invoke nu ll, new O b jec t[ ] { a rgs } ; / /执行 m 所代表的方
法 try{
( ) } ca tch Th rowab le e{ ( ) C la ss c = C la ss. fo rN am e nam e; / /加载指定的类 nam e
/ /构造命令行参数 ( ) JOp tionPane. showM e ssageD ia log th is, e; String[ ] a rgs = new String[ ] { } ;
} ( M e thod m = c. ge tM e thod " m a in" ,
} ( ) ) new C la ss[ ] { a rgs. ge tC la ss } ; / /获取 m a in方法对象 m
我们通过在 C la ssLoade rTe st1. java程序的运行窗口中 ,在由 C la ss所标识的文本域中分别输入 : Ca lcu la to r
和 M yJavaApp lica tion,运行 Ca lcu la to r. java程序和 M yJavaApp lica tion. java 程序 。当然程序 Ca lcu la to r. java 和
M yJavaApp lica tion. java事先必须被分别编译成字节码文件并且没有被加密 。
2 通过自定义类加载器来加载并运行经过处理了的其他应用程序
若对类的字节码文件进行了其他处理 ,如进行加密处理 。加密的目的是为了安全 。首先我们要对类的 字节码文件进行解密处理 ,当然我们也可以先解密 ,然后再应用上述过程进行动态加载执行 。但由于我们要 将解密和动态加载合为一个过程 ,所以我们可以通过定制一个类加载器来完成上述过程 。
( 若要定制自己的类加载器 , 只需要扩展 Java 提供 的 C la ssLoade r抽 象 类 , 并重 载方 法 : findC la ss String
)( )c la ssN am e即可完成 。 findC la ss String c la ssN am e的方法原型为 :
( )
( )超类 C la ssLoade r的 loadC la ss 方法用于将类的加载操作委托给它的父类去进行 ,只有当该类尚未被加
( )载 ,并且它的父类加载器也无法加载该类时 ,才调用 findC la ss 方法来加载指定的类 。经过特殊处理的类文
( )件 ,如经过了加密处理的字节码文件 ,一般要通过重载 findC la ss String c la ssN am e方法来完成加载过程 。
( ) 如果要实现 findC la ss 方法 ,我们必须完成两件事 : 一是从本地文件系统或从其他来源为类加载字节
( )码 ;二是调用超类 C la ssLoade r的 defineC la ss 方法 ,给虚拟机提供当前字节码 。
( ) 对第一项任务 ,我们通过自定义方法 : p riva te byte [ ] loadC la ssB yte s String nam eth row s IO Excep tion完成 该任务 。该方法从加密了的字节码文件中读入信息并解密 ,然后将信息转化为字节数组提供给 defineC la ss ( ) 方法使用 。具体实现过程如下所示 :
( ) ( ( ( ) ) ) p riva te byte [ ] loadC la ssB yte s String nam eth row s IO Excep wh ile ch = in. read ! = - 1{ / /读取内容
tion{ ( ) ( ) byte b = bytech - key; / /解密
()( ) String cnam e = nam e. rep lace .’ ’, ’/ ’ 向内存流写入内容 buffe r. w rite b; / /+ ". cae sa r" ;
/ /加密文件名 } F ile Inp u tStream in = nu ll; / /定义输入流对象 ( ) 关闭输入流对象 / /in. c lo se ; try{ ( ) re tu rn buffe r. toB yteA rray ; / /将内容转换为数组 ( ) in = new F ile Inp u tStream cnam e; / /获取输入流 } fina lly{ B yteA rrayO u tp u tStream buffe r = new B yteA rrayO u tp u tStream ( ) ( ) if in ! = nu llin. c lo se ; ( ) ; / /定义内存流
} in t ch; }
( ) ( ) 方法 defineC la ss 的原型为 : p ro tec ted fina l C la ss defineC la ss String nam e, byte [ ] b, in t off, in t len , th row s C la ssFo rm a tE rro r
它需要四个参数 : ?需要加载的类名 , ?存放该类字节码信息的数组名 , ?偏移量 , ?数组的长度 。它的功 能是将存放在数组 b中的字节码信息转换成 C la ss对象的一个实例 ,但是所得到的类在没有通过方法 findC la ss ( )调用之前是不能使用的 ,若数组 b中的内容不能被译解 ,则它将抛出一个 C la ssFo rm a tE rro r异常信息 。
( ) 有了以上分析 ,我们对 findC la ss 方法的重载过程就为 :
( ) p ro tec ted C la ss findC la ss String nam e th row s C la ssNo tFoundEx }
( ) cep tion{ C la ss c l = defineC la ss nam e, c la ssB yte s, 0, c la ssB yte s. length;
byte [ ] c la ssB yte s = nu ll; / /定义字节数组对象 生成类实例 / /
( ) try{ c la ssB yte s = loadC la ssB yte s nam e; / /将文件内容装 ( ) ( ) if c l == nu llth row new C la ssNo tFoundExcep tion nam e; 入字节数组中 re tu rn c l; / /返回所生成的类
( ) } ca tch IO Excep tion excep tion{ }
( ) th row new C la ssNo tFoundExcep tion nam e;
对加密了的字节码文件 ,进行动态加载与执行的过程见程序 C la ssLoade rTe st. java所示 ,该程序中包括了 自定义的类加载器 C ryp toC la ssLoade r的定义过程 。至于字节码文件的加密过程我们在程序 Cae sa r. java中给 出了具体过程 ,它采用历史悠久的恺撒密码对类文件进行加密 ,密钥我们用整数 3。在这里我们给出了 Ca l2
cu la to r. java程序经过编译后的加密文件 ,我们同样是在 C la ssLoade rTe st. java程序的运行窗口中 ,在由 C la ss
标识的文本域中输入 : Ca lcu la to r,就可以运行经过加密了 Ca lcu la to r字节码文件 。
3 讨论
当我们动态加载并执行具有图形用户界面的 J ava应用程序 ,如 Ca lcu la to r. java时 ,但当我们关闭 Ca lcu2 la to r. java应用程序所在的窗口 ,并准备运行下一个 Java 应用程序时 ,我们发现主程序 C la ssLoade rTe st. java 或 C la ssLoade rTe st1. java所在的窗口也随之被关闭 。经过分析发现 ,原因在于关闭 Ca lcu la to r. java 应用程序
( )所在的窗口时 ,我们已经退出了 J ava的虚拟机 ,即在 Ca lcu la to r. java应用程序的 m a in 方法中有 : fram e. se t2
( ) D efau ltC lo seOp e ra tion J F ram e. EX IT_ON _CLO SE 这 一 句 , 它 指 示我 们退 出 J ava 虚 拟机 , 所 以 主程 序 C la ss2 Loade rTe st. java或 C la ssLoade rTe st1. java所在的窗口也随之被关闭 。改进的方法是将被动态加载并执行的
( ) 图形用户 界 面 应 用 程 序 , 如 : Ca lcu la to r. java 应 用 程 序 的 m a in 方 法 中 的 fram e. se tD efau ltC lo seOp e ra tion ( ) J fram e. EX IT_ON _CLO SE语句由以下程序段替换 :
( ( ) ) fram e. addW indowL istene r new W indowA dap te r { re tu rn; } } ;
() p ub lic vo id w indowC lo sing W indowEven t even t{
如 : Ca lcu la to r1. java 所示 ,即使关闭 Ca lcu la to r1. java 所在的窗口 ,主程序 C la ssLoade rTe st. java 或 C la ss2 Loade rTe st1. java所在的窗口将不会被关闭 ,我们仍然可以继续动态加载并运行其他 J ava应用程序 。 如果我
们用 C la ssLoade rTe st. java或 C la ssLoade rTe st1. java程序动态加载并执行具有字符界面的 Java应
( ) 用程序时 ,只要具有字符界面的应用程序不用命令 : System. exit 直接退出 J ava虚拟机 ,我们就可以多次动态加载并执行它们 ,但是要解决的是如何在 C la ssLoade rTe st. java或 C la ssLoade rTe st1. java程序中给某些具有 字符界面的应用程序提供命令行参数 。如 : App lica tion. java应用程序在运行时就可以处理命令行参数 。
解决的办法是在 C la ssLoade rTe st. java或 C la ssLoade rTe st1. java 程序运行的窗口中 ,在由 C la ss所标识的文本域中直接输入被加载执行的应用程序名后 ,再输入各命令行参数串 ,其中应用程序名和各参数串之间以 及各参数串之间要用空格分开 。然后通过构造一个 java. u til. StringToken ize r类 ,并用此类中提供的方法 :
( ) p ub lic in t coun tToken s ;
( ) p ub lic Boo lean ha sMo reToken s ;
( ) p ub lic String nextToken ;
将应用程序名和各命令行参数串解析出来 。具体过程为 :
( ) String cnam e; a rgs[ i + + ] = st. nextToken ;
给命令行参数数组的各元素赋值 / /( ) StringToken ize r st = new StringToken ize r nam e;
/ /构造 StringToken ize r对象 ( ) C la ss c = C la ss. fo rN am e cnam e;
( ) cnam e = st. nextToken ; / /求出应用程序名 / /加载应用程序
( ) ( String[ ] a rgs = new String[ st. coun tToken s ] ; M e thod m = c. ge tM e thod " m a in" , new C la ss[ ] / /构造命令行参数数组 ( ) ) { a rgs. ge tC la ss } ;
in t i = 0; ( ) m. invoke nu ll, new O b jec t[ ] { a rgs } ;
/ /执行应用程序的主方法 ( ( ) )wh ile st. ha sMo reToken s
( )具体程序可参见 C la ssLoade rTe st2. java程序中的 runC la ss String nam e 方法 。当我们在程序 C la ssLoad2 e rTe st2. java所运行的窗口中 ,在由 C la ss所标识的文本域中直接输入 : App lica tion 123 234 345 456 时 ,字符 界面程序 App lica tion. java的运行结果就在控制台窗口中显示出来了 ,这样我们即指定了被动态加载执行的 应用程序又为它指定了运行过程中所需要的各命令行参数 。当然应用程序 App lica tion. java 事先也要被编 译成字节码文件 。
参 考 文 献
() 1 John Zukow sk i 美 著 ,邱仲潘译. Java 2 从入门到精通. 北京 :电子工业出版社 , 20051)(2 Cay S. Ho rstm ann、Ga ry Co rne ll 美 著 ,京京工作室译. Java 2 核心 卷 ?: 基础知识. 北京 : 机械工业出版社 , 20001
( )( )( ) 3 曹大有. Java 2 AW T中 p ain t 和 rep a in t 方法的合理使用. 微计算机应用 , 2005 , 4 .
( ) 4 曹大有. Java 2 数组容量扩充的通用方法. 微计算机应用 , 2005 , 2 :
作者简介( ) 曹大有 ,男 , 1965 - ,郧阳师范高等专科学校计算机科学系副教授 、高级程序员 ,主要从事计算机应用方面的研究 。
Your requestcould not be processed becauseof a configurationerror: "Could not connect to LDAPserver."
For assistance,contact your network support team.
file:///C|/Users/Administrator/Desktop/新建文本文档.txt
涵盖各行业最丰富完备的资料文献,最前瞻权威的行业动态,是专业人士的不二选择。
file:///C|/Users/Administrator/Desktop/新建文本文档.txt2012/8/26 12:19:58