ClassPool CtClass浅析
2016-07-22 16:36
330 查看
最近在看android中的热更新原理,里面有用到javassist来更改.class,因而又恶补了下ClassPool和CtClass的相关使用。虽然android中现在热更新是用 groovy, groovy和java语法很类似,所以先弄java版的~
可以看到相应目录下生成了Test.class文件,然后通过JD-GUI工具打开,如图所示:
可以看到,属性和两个方法,都已经写入到.class文件中,OK啦!
发现报错,log如下所示:
报错位置在
然后我在调用pool.get()之前,先调用代码:
运行代码,结果还是会报错,log如图所示:
所以,如果要阻止对某一个特定的CtClass对象的精简,即需要修改某个.class文件,需要在这个CtClass对象上先调用stopPruing()方法:
参考博客:http://blog.chinaunix.net/uid-21718047-id-3342374.html
什么是javassist
Javassit是一个处理Java字节码的类库。Java字节码存储在名叫class file的二进制文件里。每个class文件包含一个Java类或者接口。Javassit.CtClass是一个class文件的抽象表示。一个CtClass(compile-time class)对象可以用来处理一个class文件。通过javassist生成.class文件
public static void main(String[] args) { //默认的类搜索路径 ClassPool pool = ClassPool.getDefault(); //获取一个ctClass对象 CtClass ctClass = pool.makeClass("com.luoxiaohui.Test"); try { //添加age属性 ctClass.addField(CtField.make("private int age;", ctClass)); //添加setAge方法 ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass)); //添加getAge方法 ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass)); //将ctClass生成字节数组,并写入文件 byte[] byteArray = ctClass.toBytecode(); FileOutputStream output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class"); output.write(byteArray); output.close(); System.out.println("文件写入成功!!!"); } catch (Exception e) { e.printStackTrace(); } }
可以看到相应目录下生成了Test.class文件,然后通过JD-GUI工具打开,如图所示:
可以看到,属性和两个方法,都已经写入到.class文件中,OK啦!
如何修改已经被JVM加载的.class文件
模拟被JVM加载的.class文件代码
ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("com.luoxiaohui.Test"); try { //添加属性 ctClass.addField(CtField.make("private int age;", ctClass)); //添加setAge方法 ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass)); ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass)); byte[] byteArray = ctClass.toBytecode(); FileOutputStream output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class"); output.write(byteArray); output.close(); System.out.println("文件生成成功!!!"); //这里用pool.get()去获取ctClass对象,表示默认JVM已经加载此类. ctClass = pool.get("com.luoxiaohui.Test"); ctClass.addField(CtField.make("private String sex;", ctClass)); ctClass.addField(CtField.make("private String name;", ctClass)); byteArray = ctClass.toBytecode(); output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class"); output.write(byteArray); output.close(); System.out.println("文件修改成功!!!!"); } catch (Exception e) { e.printStackTrace(); }
发现报错,log如下所示:
报错位置在
ctClass.addField(CtField.make("private String sex;", ctClass));
冻结class原因
如果一个CtClass对象通过writeFile(),toClass()或者toBytecode()转换成了class文件,那么Javassist会冻结这个CtClass对象。后面就不能继续修改这个CtClass对象了。这样是为了警告开发者不要修改已经被JVM加载的class文件,因为JVM不允许重新加载一个类。然后我在调用pool.get()之前,先调用代码:
if(ctClass.isFrozen()){ ctClass.defrost(); }
运行代码,结果还是会报错,log如图所示:
被精简原因
如果ClassPool.doPruning被设置成true,那么Javassist会在冻结一个对象的时候对这个对象进行精简。为了减少ClassPool的内存占用,精简的时候会丢弃class中不需要的属性。例如Code_attribute结构(即是方法体)会被丢弃。因此,如果一个CtClass对象被精简了,那么方法的字节码是不能访问的,留下的只有方法名,方法的签名和annotation。被精简的CtClass对象不能够再被defrost。ClassPool.doPruning的默认值是true。所以,如果要阻止对某一个特定的CtClass对象的精简,即需要修改某个.class文件,需要在这个CtClass对象上先调用stopPruing()方法:
ctClass.stopPruning(true);
完整代码
完整代码如下所示:ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("com.luoxiaohui.Test");
ctClass.stopPruning(true);
try {
//添加属性
ctClass.addField(CtField.make("private int age;", ctClass));
//添加setAge方法
ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass));
ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass));
byte[] byteArray = ctClass.toBytecode();
FileOutputStream output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class");
output.write(byteArray);
output.close();
System.out.println("文件写入成功!!!");
if(ctClass.isFrozen()){ ctClass.defrost(); }
ctClass = pool.get("com.luoxiaohui.Test");
ctClass.addField(CtField.make("private String sex;", ctClass));
ctClass.addField(CtField.make("private String name;", ctClass));
byteArray = ctClass.toBytecode();
output = new FileOutputStream("/Users/luoxiaohui/Desktop/test/Test.class");
output.write(byteArray);
output.close();
System.out.println("文件修改成功!!!!");
} catch (Exception e) {
e.printStackTrace();
}
参考博客:http://blog.chinaunix.net/uid-21718047-id-3342374.html
相关文章推荐
- LeetCode 1. Two Sum
- Hibernate中使用原生的sql语句进行查询操作
- Android 接入微信支付
- 禁用cookie后session是如何设置的
- 自定义的Cell的赋值
- 临时起异,要进入C++领域耍一个程序
- Android之NetworkOnMainThreadException异常
- C# 加载Xml文件并解析
- 排序相关的整理
- Part2:Volley请求失败的重试机制
- TCP粘包,拆包及解决方法
- HDU 1009 FatMouse' Trade (贪心)
- ios移动开发的提示弹出框(简单明了方便调试)
- CTeX 中文 article 模板
- ambari 自定义组件安装
- C# Process.Start()方法详解
- position 定位
- docker默认设置下访问私有docker hub遇到的https问题
- Android开发中调试日志的输出方法
- HDU 1427 24点游戏