对于Java程序员来说,工作中经常会遇到这样一些问题,比如引用了些第三方提供的非开源jar包,这个时候我们需要用它,甚至需要改它的内容。怎么办?下面看看开发中会遇到哪些问题?

  1. 某个类的里面的字段默认值不符合需求要改掉.
  2. 某个方法里面有很多校验,想直接return ;绕过校验。
  3. 某个方法里面的计算方法太复杂,想用自己的计算方式。替换方法体。
  4. 首先声明一点,如果是收费的,切勿参加商业性质。

       往往这个时候我们就会很棘手,因为确实不好操作,不好处理。现在我给大家介绍两种方式,解决一些比较常见且简单的问题。



1、 反编译class文件,推荐Java Decompiler 



       这个工具几乎能正确的反编译出源代码。如果反编译出的源码,没有出现很明显的错误。或者说很难修改的错误时。通常这个时候我们直接修改该类已达到我们的需求就可以了,把修改后的class文件覆盖jar里的class文件,即可。



不过,有时候第三方提供的jar里面,某些类写的特别复杂,反编译后一堆错误,真的没法修改。这个时候可以试试第二种方法。



 



2、利用工具直接修改class字节码,推荐Javassist



       先给大家介绍一下Javassist,Javassist是一个开源的分析、编辑和创建Java字节码的类库,它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。



 



       关于java字节码的处理,目前有很多工具,如bcel,asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。



1、我们可以用Javassist实现什么。



  • 添加修改父类
  • 添加修改方法
  • 添加修改字段
  • 创建类

2、准备工作



 



        2.1首先下载Javassist,jar文件



       2.2获得ClassPool对象,他控制者class字节码的修改。



      



        2.3. 如果你准备修改的class文件在某个路径或者jar文件里或者网络的某个路径。需要设置路径



1



2



3



4



5



6



7


pool.insertClassPath( "E:\\test" );或者提供一个jar文件



pool.insertClassPath( "E:\\test.jar" );或者提供一个网络路径



ClassPath cp = new URLClassPath( "www.javassist.org" , 80 , "/java/" , "org.javassist." );



pool.insertClassPath(cp);或者直接提供 byte 字节码



byte [] b = a byte array;



String name = class name;



pool.insertClassPath( new ByteArrayClassPath(name, b));




 



        2.4. 下面我们就要处理class文件了,每个class文件对应一个CtClass对象,而CtClass是从对象ClassPool对象里得到。需要完整的包名+类名,不需要.class



1


CtClass cc = pool.get( "com.my.TestMain" );




        2.5. 完整代码



1



2



3



4


ClassPool pool = ClassPool.getDefault();



//pool.insertClassPath("E:\\test.jar");



pool.insertClassPath( "E:\\test" );



CtClass cc = pool.get( "com.my.TestMain" );




3、添加修改父类



        3.1. 添加接口



      



        3.2. 添加抽象类



1



2


cc.setSuperclass(pool.get( "com.my.TestMain1" ));



cc.writeFile();




        如果父类已经存在就用现在的类替换了。



       



4、添加修改方法



        4.1. 添加新的方法 



1



2



3



4


CtClass cc = pool.get( "sample.TestMain" );



CtMethod cm = CtNewMethod.make( "public void toInt(int i){i++;}" , cc);



cc.addMethod(cm);



cc.writeFile()




        4.2. 修改方法



1



2



3


CtClass cc = pool.get( "sample.TestMain" );



CtMethod cm = cc.getDeclaredMethod( "toInt" , new CtClass[]{ CtClass.intType });



cc.writeFile();




        getDeclaredMethod的第一个参数是方法名,第二个参数是方法的参数。



        获得方法对象后,以下三种方法是最常用的。



  • cm.setBody("");重新设置方法体
  • cm.insertBefore("");在原来方法的最前面插入代码
  • cm.insertAfter("");在原来方法的后面插入代码

        4.3. 删除方法



1


cc.removeMethod()




5. 添加修改字段



        5.1. 添加字段



1



2



3



4



5


//方式1



CtClass cc = pool.get( "sample.TestMain" );



CtField f = new CtField(CtClass.intType, "t1" , cc);



cc.addField(f, "2" );    // initial value is 0.



cc.writeFile();




     //方式2



1



2



3



4


CtClass cc = pool.get( "sample.TestMain " );



CtField f = CtField.make( "public int z = 0;" , point);



cc.addField(f);



cc.writeFile();




        5.2. 修改字段,方法里没有提供直接修改,我们需要删删除在修改



1



2



3



4



5


CtClass cc = pool.get( "sample.TestMain" );



CtField f = cc.getDeclaredField( "t" );



cc.removeField(f);



cc.addField(CtField.make( "public int t=3;" , cc));



cc.writeFile();




6. 创建新类



1



2



3


ClassPool pool = ClassPool.getDefault();



CtClass cc = pool.makeClass( "Point" );



cc.writeFile();




7. Import



1



2



3



4


ClassPool pool = ClassPool.getDefault();



CtClass cc = pool.makeClass( "sample.TestMain" );



pool.importPackage( "java.awt" );



cc.writeFile();