Matrix42

不饱食以终日,不弃功于寸阴

Java 动态性(4) - 字节码操作

1.字节码操作

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

    字节码操作

    反射

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

    动态生成新的类

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

  • 优势

    比反射开销小,性能高

    JAVAasist性能高于反射,低于asm

2.常见的字节码操作类库

  • BCEL

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

  • ASM

    是一个轻量级ava字节码操作框架,直接涉及量到VM底层的操作和指令

  • CGLIB(Code Generation Library)

    是一个强大的,高性能,高质量的Code生成类库,基于ASM实现

  • Javassist

    是一个开源的分析、编辑和创建Jaw字节码的类库.性能较ASM差,跟cglib差不多,但是使用简单.很多开源框架都在使用它

    主页:http://jboss-javassist.github.io/javassist/

3.JAVAssist库

  • Javassist(Java Programming Assistant)makes java bytecode manipulation simple.

  • It is a class library for editing bytecodes in Java;it enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it.

  • Unlike other similar bytecode editors,Javassist provides two levels of API:source level and bytecode level.

    • If the users use the source-level API,they can edit a class file without knowledge of the specifications of the Java bytecode.The whole API is designed with only the vocabulary of the java language.You can even specify inserted bytecode-level API allows the users to directly edit a class file as other editors.
  • Aspect Oriented Programming(AOP面向切面编程):Javassist can be a good tool for adding new methods into a class and for inserting before/after/around advice at the both caller and callee sides.

  • Reflection:Ones of applications of Javassist is runtime reflection;Javassist enables Java programs to use a metaobject that controls method calls on base-level objects.No specialized complier or virtual machine are needed.

4.JAVAssist库的API简介

  • javaassist的最外层的API和JAVA的反射包中的API颇为类似

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

5.JAVAssist库的简单使用

  • 创建一个全新的类

  • 使用XJAD反编译工具,将生成的class文件反编译成JAVA文件

使用前先导入javassist的jar包
Demo:

/**
 * 使用javassist生成一个新的类
 * @author Matrix42
 *
 */
public class Demo01 {

    public static void main(String[] args) throws Exception{

        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("com.lorinda.bean.Emp");

        //创建属性
        CtField f1 = CtField.make("private int empno;", cc);
        CtField f2 = CtField.make("private String ename;", cc);
        cc.addField(f1);
        cc.addField(f2);

        //创建方法
        CtMethod m1 = CtMethod.make("public int getEmpno(){return empno;}", cc);
        CtMethod m2 = CtMethod.make("public void setEmpno(int empno){this.empno = empno;}", cc);
        cc.addMethod(m1);
        cc.addMethod(m2);
        CtMethod m3 = CtMethod.make("public String getEname(){return ename;}", cc);
        CtMethod m4 = CtMethod.make("public void setEname(String empno){this.ename = ename;}", cc);
        cc.addMethod(m3);
        cc.addMethod(m4);

        //添加构造器
        CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType,pool.get("java.lang.String")}, cc);
        constructor.setBody("{this.empno=$1;this.ename=$2;}");
        cc.addConstructor(constructor);

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

    }

}

创建完成后使用XJAD反编译就可以看到源码了

反编译源码:

package com.lorinda.bean;

public class Emp
{
    private int empno;
    private String ename;

    public int getEmpno()
    {
        return empno;
    }

    public void setEmpno(int i)
    {
        empno = i;
    }

    public String getEname()
    {
        return ename;
    }

    public void setEname(String s)
    {
        ename = ename;
    }

    public Emp(int i, String s)
    {
        empno = i;
        ename = s;
    }
}

6.JAVAssist库的API详解

  • 方法操作

    修改已有方法的方法体(插入代码到已有方法体)

    新增方法

    删除方法

  • 属性操作

    修改已有方法的方法体(插入代码到已有方法体)

    新增方法

    删除方法

Demo:

import java.awt.color.CMMException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;

public class Demo02 {

    public static void test01() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");

        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()); //获得接口
    }

    public static void test02()throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.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(\"Ha Ha\");return $1+$2;}");

        cc.addMethod(m);

        //通过反射调用新生产的方法
        Class<?> clazz = cc.toClass();
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("add", int.class,int.class);
        Object result = method.invoke(obj, 200,300);
        System.out.println(result);
    }

    public static void test03()throws Exception{

        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");

        CtMethod cm = cc.getDeclaredMethod("sayHello", new CtClass[]{CtClass.intType});
        cm.insertBefore("System.out.println($1);");

        Class<?> clazz = cc.toClass();
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sayHello", int.class);
        method.invoke(obj, 90);

    }

    public static void test04() throws Exception{

        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");

        //CtField f1 = CtField.make("private int empno", cc);
        CtField f1 = new CtField(CtClass.intType,"salary",cc);
        f1.setModifiers(Modifier.PRIVATE);
        cc.addField(f1,"1000");//1000位默认值

       // cc.getDeclaredField("ename"); //获取指定属性

        cc.addMethod(CtNewMethod.getter("salary",f1));
        cc.addMethod(CtNewMethod.setter("salary", f1));
    }

    public static void test05()throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");

        CtConstructor[] cs = cc.getConstructors();
        for (CtConstructor ctConstructor : cs) {
            System.out.println(ctConstructor.getLongName());
        }
    }

    public static void main(String[] args) throws Exception {
        //test01();
        //test02();
       // test03();
       // test04();
        test05();

    }

}
  • 构造方法操作

    getConstructors()

  • 注解操作

代码片段:

public @interface Author {
String name();
int year();
}
@Author(name="Chiba",year=2005)
public class Point{
    int x,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:"+name+",year:"+year);

当调用了writeFile(),toClass(),toBytecode(),Javassist会把那个CtClass对象冻结,如果想使用冻结的对象可以调用.defrose()方法

  • 局限性

    JDK5.0新语法不支持(包括泛型,枚举),不支持注解修改,单可以的通过底层javasist类来解决,具体参考:javassist.bytecode.annotation

    不支持数组的初始化,如String[]{“1″,”2”},除非只有数组容量为1

    不支持内部类盒匿名类

    不支持continue盒break表达式

    对于继承关系,有些语法不支持,如:

    class A{}

    class B extends A{}

    class C extends B{}

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注

5 − 2 =