Java基础-字节码操作

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, $2this and actual parameters$0代表的是this指针,$1 代表方法参数的第一个参数,$2 代码方法参数的第二个参数,一次类推。
      $argsAn array of paraetersThe 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 代码异常对象
      $classthis的类型(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 {}
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值