JAVA字节码操作
-
Java动态性的两种常见实现方式:
- 字节码操作
- 反射
-
运行时操作字节码可以让我们实现如下功能:
- 动态生成新的类
- 动态改变某个类的结构(添加、删除、修改 新的属性、方法)
-
优势:
- 比反射开销小
- Java asist性能高于反射,低于ASM
1. 常见的字节码操作类库
- BCEL
- Byte Code Engineering Library(BCEL),这时 Apache Software Foundation 的 Jakarta 项目的一部分。BCEL是 java classworking 广泛使用的一种框架,它可以让您深入 JVM 汇编语言进行类操作的细节。BCEL 与 Javassist 有不同的处理字节码方法,BCEL 在实际的JVM指令层次进行操作(BCEL拥有丰富的JVM指令级支持)而javassist 所强调的是源代码级别的工作。
- ASM
- 是一个轻量级java字节码操作框架,直接涉及到JVM底层的操作和指令
- CGLIB(Code Generation Library)
- 是一个强大的,高性能,高质量的Code生成类库,基于ASM实现。
- Javassist
- 是一个开源的分析、编辑和创建Java字节码的类库性能较ASM差,跟cglib差不多,但是使用简单。很多开源框架都在使用它。
- 主页:
- http://www.csg.ci.i.u-tokyo.ac.jp/-chiba/javassist/
1.1 JAVAssist库的API详解
- javassit的最外层的API和JAVA的反射包中的API颇为类似。
- 它主要由 CtClass、CtMethod,以及CtField几个类组成。用以执行和JDK反射API中 java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method.Field相同的操作。
1.2 JAVAssist库的简单实用
-
创建一个全新的类
package com.selflearning.test; import javassist.*; public class Demo01 { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("com.selflearning.bean.Emp"); // 创建属性 CtField f1 = CtField.make("private int empno;", ctClass); CtField f2 = CtField.make("private String ename;", ctClass); ctClass.addField(f1); ctClass.addField(f2); // 创建方法 CtMethod m1 = CtMethod.make("public int getEmpno() {return empno;}", ctClass); CtMethod m2 = CtMethod.make("public void setEmpno(int empno) {this.empno = empno;}", ctClass); ctClass.addMethod(m1); ctClass.addMethod(m2); // 添加构造器 CtConstructor constructor = new CtConstructor(new CtClass[] {CtClass.intType, pool.get("java.lang.String")}, ctClass); constructor.setBody("{this.empno = empno; this.ename = ename;}"); ctClass.addConstructor(constructor); ctClass.writeFile("d:/myjava"); // 将上面构造好的写入到D:/myjava中 System.out.println("类生成成功"); } }
-
使用XJAD反编译工具,将生成的class文件反编译成JAVA文件。
1.3 Javassist API详解
-
方法操作
-
修改已有方法的方法体(插入代码到已有方法体)
/** * 修改已有的方法信息,修改方法体的内容 * @throws Exception */ public static void test03() throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.selflearning.test.Emp"); CtMethod cm = cc.getDeclaredMethod("sayHello", new CtClass[] {CtClass.intType}); cm.insertBefore("System.out.println($1);System.out.println(\"start!\");"); // cm.insertAt() // 在某一行前加内容。 cm.insertAfter("System.out.println(\"end!\");"); // 通过反射调用新生成的方法 Class clazz = cc.toClass(); Object obj = clazz.newInstance(); // 通过调用Emp无参构造器,创建新的Emp对象 Method method = clazz.getDeclaredMethod("sayHello", int.class); Object result = method.invoke(obj, 300); System.out.println(result); }
-
新增方法
/** * 测试产生新的方法 * @throws Exception */ public static void test02() throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.selflearning.test.Emp"); // CtMethod m = CtMethod.make("public int add(int a, int b) {return a + b;}", cc); CtMethod m = new CtMethod(CtClass.intType, "add", new CtClass[] {CtClass.intType, CtClass.intType},cc); m.setModifiers(Modifier.PUBLIC); m.setBody("{System.out.println(\"Hello\");return $1+$2;}"); cc.addMethod(m); // 通过反射调用新生成的方法 Class clazz = cc.toClass(); Object obj = clazz.newInstance(); // 通过调用Emp无参构造器,常见新的Emp对象 Method method = clazz.getDeclaredMethod("add", int.class, int.class); Object invoke = method.invoke(obj, 200, 3000); System.out.println(invoke); }
-
其中有一些占位符,需要了解:
占位符 描述 具体的内容 $0, $1, $2 this and actual parameters $0代表的是this指针,$1 代表方法参数的第一个参数,$2 代码方法参数的第二个参数,一次类推。 $args An array of paraeters The type of a r r g s i s O b j e c t [ ] . < b r / > arrgs is Object[].<br /> arrgsisObject[].<br/>arrgs[0]对应的是$1,而不是$0 $$ 所有方法参数的简写,主要用在方法调用上。 move(String a, String b);
move($$) 相当于 move($1, $2)$cflow 一个方法调用的深度 用于递归等。 $r 方法返回值的类型 $_ 方法的返回值(修改方法体时不支持) addCatch() 方法中加入try catch 块 $e 代码异常对象 $class this的类型(class)。 也就是$0的类型 $sig 方法参数的类型(class)数组,数组的顺序为参数的顺序。
-
-
属性的操作
/** * 属性的操作 * @throws Exception */ public static void test04() throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.selflearning.test.Emp"); // CtField f1 = CtField.make("private int empno", cc); CtField f1 = new CtField(CtClass.intType, "salary", cc); f1.setModifiers(Modifier.PRIVATE); cc.addField(f1); // cc.getDeclaredField("empno"); // 获取指定的属性 // 增加响应的set和get方法 cc.addMethod(CtNewMethod.getter("getSalary", f1)); cc.addMethod(CtNewMethod.setter("setSalary", f1)); // 通过反射来进行调用 Class clazz = cc.toClass(); Object o = clazz.newInstance(); Method m = clazz.getDeclaredMethod("setSalary", int.class); Method m_get = clazz.getDeclaredMethod("getSalary"); m.invoke(o, 30000); Object result = m_get.invoke(o); System.out.println(result); }
-
构造方法操作
/** * 构造方法的操作 * * @throws Exception */ public static void test05() throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.selflearning.test.Emp"); CtConstructor[] cs = cc.getConstructors(); for (CtConstructor c: cs ) { System.out.println(c.getLongName()); // c.insertBefore(); // c.insertAfter(); } }
-
注解操作
public @interface Author { String name(); int year(); } @Author(name="Chiba", year = 2005) publkic class Point{int x, y;} public class Test { public void test01() { CtClass cc = ClassPool.getDefault().get("Point"); Object[] all = cc.getAnnotations(); Author a = (Author)all[0]; String name = a.name; int year = a.year(); System.out.println("name: " + name + ", year: " + year) } }
-
局限性:
-
JDK5.0新语法不支持(包括泛型、枚举),不支持注解修改,但是可以通过底层的javassist类来解决,具体参考:javassist.bytecode.annotation
-
不支持数组的初始化,如
String[]{"1", "2"}
,除非只有数组的容量为1. -
不支持内部类的匿名类
-
不支持continue和break表达式。
-
对于继承关系,有些不支持。如
class A{} class B extends A {} class C extends B {}
-