javaassist操作字节码

java动态性常见的两种实现方式

  • 字节码操作

  • 反射

运行时操作字节码可以让我们实现如下功能

  • 动态生成新的类

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

优势

  • 比反射开销小

  • JAVAasist性能高于反射,低于ASM

常见的字节码操作类库

  • BCEL

  • ASM

  • CGLIB

  • Javaassist

学习

  • javaassist的最外层API跟JAVA反射包中的API颇为相似

  • 它主要由CtClass、CtMethod以及CtField几个类组成。用以执行和JDK反射API中java.lang.Class、java.lang.reflect.Method、java.lang.reflect.Method.Field相同的操作

利用javaassist生成一个类

public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {
    // 获取javaassist类池
    ClassPool pool = ClassPool.getDefault();
    CtClass ctClass = pool.makeClass("com.eric.bean.Emp");
    // 创建属性
    CtField f1 = CtField.make("private int empno;", ctClass);
    CtField f2 = CtField.make("private String empname;", 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);
    CtMethod m3 = CtMethod.make("public String getEmpname() {return empname;}", ctClass);
    CtMethod m4 = CtMethod.make("public void setEmpname(String empname) {this.empname = empname;}", ctClass);
    ctClass.addMethod(m1);
    ctClass.addMethod(m2);
    ctClass.addMethod(m3);
    ctClass.addMethod(m4);

    // 添加构造器
    CtConstructor ctConstructor = new CtConstructor(new CtClass[]{CtClass.intType, pool.get("java.lang.String")}, ctClass);
    ctConstructor.setBody("{this.empno = empno; this.empname = empname;}");
    ctClass.addConstructor(ctConstructor);

    // 将上面构造好的类写入d:/myjava中
    ctClass.writeFile("d:/myjava");
    System.out.println("生成类成功!");
}

javaassist详细操作

对类的基本操作

ClassPool pool = ClassPool.getDefault();
// 获取类
CtClass emp = pool.get("com.eric.test.Emp");
// 打印字节码信息
byte[] bytes = emp.toBytecode();
System.out.println(Arrays.toString(bytes));

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

对方法的操作

javaassist操作方法需要注意几个占位符

  • $0:表示this关键字

  • $1:表示第一个形参

  • $2:表示第二个形参,$3、$4以此类推

  • $args:所有参数的数组形式

  • $$:所有参数的简写,即move($$)等价于move($1, $2),方便调用

  • $cflow:一个方法调用的深度,例如在递归调用中显示

  • $r:方法的返回值类型

  • $_:方法的返回值,在修改方法体时不支持

  • addCatch():增加try catch块,$e代表异常对象

  • $class:this的类型,也就是$0的类型

  • $sig:方法参数类型的数组

为一个类生成新的方法

public static void testMethod () throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass ctClass = pool.get("com.eric.test.Emp");

    // 声明一个方法
    // 方式一
    CtMethod ctMethod = CtMethod.make("public int minus(int a, int b) {return a - b;}", ctClass);
    ctClass.addMethod(ctMethod);
    // 方式二
    CtMethod m = new CtMethod(CtClass.intType, "add", new CtClass[]{CtClass.intType, CtClass.intType}, ctClass);
    m.setModifiers(Modifier.PUBLIC);   // 声明修饰符
    m.setBody("{return $1 + $2;}");
    ctClass.addMethod(m);

    // 通过反射调用新生成的方法
    Class<?> aClass = ctClass.toClass();
    Object o = aClass.newInstance();  // 通过调用emp的无参构造器生成对象
    Method add = aClass.getDeclaredMethod("add", int.class, int.class);
    Object result = add.invoke(o, 200, 300);
    System.out.println(result);
}

获取已有的方法,在方法指定位置添加代码

public static void testMethod1 () throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass ctClass = pool.get("com.eric.test.Emp");

    CtMethod sayHello = ctClass.getDeclaredMethod("sayHello", new CtClass[]{CtClass.intType});
    // 在方法前面添加一些代码
    sayHello.insertBefore("System.out.println($1); System.out.println(\"start\");");
    // 在方法指定行添加一些代码
    sayHello.insertAfter("System.out.println(\"end\");");
    // 在方法后面添加一些代码
    sayHello.insertAt(44, "System.out.println(\"insertAt\");");

    // 反射调用
    Class<?> aClass = ctClass.toClass();
    Object o = aClass.newInstance();
    Method sayHello1 = aClass.getDeclaredMethod("sayHello", int.class);
    sayHello1.invoke(o, 100);
}

对属性的操作

public static void testField() throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass ctClass = pool.get("com.eric.test.Emp");

    // 创建属性
    // 方式一
    CtField f1 = CtField.make("private int empId;", ctClass);
    ctClass.addField(f1);

    // 方式二
    CtField f2 = new CtField(CtClass.intType, "salary", ctClass);
    f1.setModifiers(Modifier.PRIVATE);
    ctClass.addField(f2, "100");  // 传入默认值


    // 获取指定属性
    //        ctClass.getDeclaredField("empname");
    // 生成setter跟getter方法
    ctClass.addMethod(CtNewMethod.getter("getSalary", f2));
    ctClass.addMethod(CtNewMethod.setter("setSalary", f2));
}

构造器操作

public static void testConstructor() throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass ctClass = pool.get("com.eric.test.Emp");

    CtConstructor[] cs = ctClass.getDeclaredConstructors();
    for (CtConstructor c: cs) {
        System.out.println(c.getName());
        c.insertBefore("");   // 在构造器前面加代码 以及后面等等 同方法操作
    }
}

注解操作

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

  

public static void testAnnotation() throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass ctClass = pool.get("com.eric.test.Emp");

    Object[] annotations = ctClass.getAnnotations();
    Author author = (Author) annotations[0];
    System.out.println(author.name());
    System.out.println(author.year());
}

局限性

  • JDK1.5新语法不支持(包括泛型,枚举),不支持注解的修改

  • 不支持数组的初始刷,如String[]{"1", "2"},除非只有容量为1的数组

  • 不支持内部类和匿名类

  • 不支持continue和break表达式

  • 对于复杂的继承关系不支持

 

posted @ 2019-10-20 11:14  Jin同学  阅读(1144)  评论(1)    收藏  举报