Java学习之动态编译--字节码操作--javassist类库

时间:2021-10-24 17:09:25

一、字节码操作

1.Java动态性的两种常见实现方式:

  • 字节码操作
  • 反射

2.运行时操作字节码可以实现如下功能:

  • 动态生成新的类
  • 动态改变某个类的结构(添加/删除/修改 新的属性/方法)

3.优势:

  • 比反射开销小,性能高
  • Javaasist性能高于反射,低于ASM

二、常见的字节码操作类库

1.BCEL

Byte Code Engineering Library(BCEL),这是Apache Software Foundation的Jakarta项目的一部分。BCEL是Java classworking 广泛使用的一种框架,它可以让您深入jvm汇编语言进行类库操作的细节。BCEL与javassist有不同的处理字节码方法,BCEL在实际的jvm指令层次上进行操作(BCEL拥有丰富的jvm指令集支持) 而javassist所强调的是源代码级别的工作。

2.ASM

是一个轻量级Java字节码操作框架,直接涉及到JVM底层的操作和指令
高性能,高质量

3.CGLB(code generation library)

  生成类库,基于ASM实现

4.javassist

是一个开源的分析,编辑和创建Java字节码的类库。性能较ASM差,跟cglib差不多,但是使用简单。很多开源框架都在使用它。
主页: http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/

三、javassist库的API详解

  • javassist的最外层的API和java的反射包中的API及其类似
  • 它主要有CtClass, CtMethod,CtField几个类组成,用于执行和JDK反射API中java.lang.Class, java.lang,reflect.Method,java.lang.reflect.Method.Field相同的操作

(1):创建一个类

代码:

public class JavassistTest {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("bean.User");

//创建属性
CtField field01 = CtField.make("private int id;",cc);
CtField field02 = CtField.make("private String name;", cc);
cc.addField(field01);
cc.addField(field02);

//创建方法
CtMethod method01 = CtMethod.make("public String getName(){return name;}", cc);
CtMethod method02 = CtMethod.make("public void setName(String name){this.name = name;}", cc);
cc.addMethod(method01);
cc.addMethod(method02);

//添加有参构造器
CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType,pool.get("java.lang.String")},cc);
constructor.setBody("{this.id=id;this.name=name;}");
cc.addConstructor(constructor);
//无参构造器
CtConstructor cons = new CtConstructor(null,cc);
cons.setBody("{}");
cc.addConstructor(cons);

cc.writeFile("E:/workspace/TestCompiler/src");
}

}

注意:无参构造器和有参构造器


(2):方法操作:

  • 修改已有的方法体(插入代码到已有方法体)
  • 新增方法
  • 删除方法

$0 $1 $2  $0代表是this, $1代表方法参数的第一个参数,$2代表方法参数的第二个参数,以此类推,$N代表方法参数的第N个
$args The type of $args is OBject[]. $args(0)对应的是$1,不是$0
$$ 一个方法调用的深度
$r 方法返回值的类型
$_ 方法返回值。(修改方法体时不支持)
addCatch() 方法中加入try catch块  $e代表 异常对象
$class this的类型(Class)。也就是$0的类型
$sig 方法参数的类型(Class)数组,数组的顺序。

(3):属性操作

(4):构造方法操作

getConstructors()

(5)注解操作

public @interface Author{
String name();
int year();
}

@Author(name="over",year=2012)
public class Point{int x,int y;}

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+":"+year);


四、局限性:

  1. JDK5.0新语法不支持(包括泛型,枚举),不支持注解修改,但可以通过底层的javassist类来解决,具体参考:javassist,bytecode.annotation
  2. 不支持数组的初始化,如String[]{"a","b"},除非只有数组的容量为1
  3. 不支持内部类和匿名类
  4. 不支持continue和break 表达式
  5. 对于继承关系,有些不支持 。例如
class A{}
class B extends A{}
Class C extends B{}


查资料: javassist与反射的性能比较

Demo:

public class Demo01 {
//获取类的简单信息
public static void test01() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("bean.User");

//得到字节码
byte[] bytes = cc.toBytecode();
System.out.println(Arrays.toString(bytes));

System.out.println(cc.getName());//获取类名
System.out.println(cc.getSimpleName());//获取简要类名
System.out.println(cc.getSuperclass());//获取父类
System.out.println(cc.getInterfaces());//获取接口
System.out.println(cc.getMethods());//获取
}

//新生成一个方法
public static void test02() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("bean.User");

//第一种
//CtMethod cm = CtMethod.make("public String getName(){return name;}", cc);
//第二种
//参数:返回值类型,方法名,参数,对象
CtMethod cm = new CtMethod(CtClass.intType,"add",new CtClass[]{CtClass.intType,CtClass.intType},cc);
cm.setModifiers(Modifier.PUBLIC);//访问范围
cm.setBody("{return $1+$2;}");

//cc.removeMethod(m) 删除一个方法
cc.addMethod(cm);
//通过反射调用方法
Class clazz = cc.toClass();
Object obj = clazz.newInstance();//通过调用无参构造器,生成新的对象
Method m = clazz.getDeclaredMethod("add", int.class,int.class);
Object result = m.invoke(obj, 2,3);
System.out.println(result);
}

//修改已有的方法
public static void test03() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("bean.User");

CtMethod cm = cc.getDeclaredMethod("hello",new CtClass[]{pool.get("java.lang.String")});
cm.insertBefore("System.out.println(\"调用前\");");//调用前
cm.insertAt(29, "System.out.println(\"29\");");//行号
cm.insertAfter("System.out.println(\"调用后\");");//调用后

//通过反射调用方法
Class clazz = cc.toClass();
Object obj = clazz.newInstance();
Method m = clazz.getDeclaredMethod("hello", String.class);
Object result = m.invoke(obj, "张三");
System.out.println(result);
}

//修改已有属性
public static void test04() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("bean.User");

//属性
CtField cf = new CtField(CtClass.intType,"age",cc);
cf.setModifiers(Modifier.PRIVATE);
cc.addField(cf);
//增加响应的get set方法
cc.addMethod(CtNewMethod.getter("getAge",cf));
cc.addMethod(CtNewMethod.setter("setAge",cf));

//访问属性
Class clazz = cc.toClass();
Object obj = clazz.newInstance();
Field field = clazz.getDeclaredField("age");
System.out.println(field);
Method m = clazz.getDeclaredMethod("setAge", int.class);
m.invoke(obj, 16);
Method m2 = clazz.getDeclaredMethod("getAge", null);
Object resutl = m2.invoke(obj,null);
System.out.println(resutl);
}

//操作构造方法
public static void test05() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("bean.User");

CtConstructor[] cons = cc.getConstructors();
for(CtConstructor con:cons){
System.out.println(con);
}
}
public static void main(String[] args) throws Exception {
//test01();
//test02();
//test03();
//test04();
test05();
}

}