专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

字节码动态生成库-Javassist

地址:www.tianshouzhi.com/api/tutoria…

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态”AOP”框架。

通过Javassist,我们可以:

  • 动态创建新类或新接口的二进制字节码;
  • 动态扩展现有类或接口的二进制字节码(AOP);

1. 动态创建新类或新接口的二进制字节码

假设我们需要生成一个Student类,代码如下:

public class Student {

    private String name;

    private Integer age;

    public Student() {

    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String display(String desc) {
        return this.name + "," + this.age + "," + desc + ".";
    }
}

javassist创建代码如下:

String clazzName = "Student";

ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass(clazzName);

// member field,setter and getter
CtField nameField = new CtField(classPool.get(String.class.getName()), "name", ctClass);
nameField.setModifiers(Modifier.PRIVATE);

CtField ageField = new CtField(classPool.get(Integer.class.getName()), "age", ctClass);
ageField.setModifiers(Modifier.PRIVATE);

ctClass.addMethod(CtNewMethod.setter("setName", nameField));
ctClass.addMethod(CtNewMethod.setter("getName", nameField));
ctClass.addField(nameField);

ctClass.addMethod(CtNewMethod.setter("setAge", ageField));
ctClass.addMethod(CtNewMethod.setter("getAge", ageField));
ctClass.addField(ageField);

// constructor
CtConstructor constructor = new CtConstructor(
                null,
                ctClass);

constructor.setBody("{}");
ctClass.addConstructor(constructor);

constructor = new CtConstructor(
        new CtClass[]{
                classPool.get(String.class.getName()),
                classPool.get(Integer.class.getName())},
        ctClass);

constructor.setBody("{ $0.name = $1; $0.age = $2; }");
ctClass.addConstructor(constructor);

// display method
CtMethod displayCtMethod = new CtMethod(
        classPool.get(String.class.getName()),
        "display",
        new CtClass[]{
                classPool.get(String.class.getName())
        },
        ctClass
);
displayCtMethod.setModifiers(Modifier.PUBLIC);
String displayMethodBody = "{ return $0.name+\"(\"+$0.age +\"), \"+$1+\".\";}";
displayCtMethod.setBody(displayMethodBody);
ctClass.addMethod(displayCtMethod);

// generator class
Class<?> c = ctClass.toClass();

Object student = c.getConstructor(String.class, Integer.class)
        .newInstance("niumi", 20);

Method method = student.getClass().getMethod("display", String.class);
String result = (String) method.invoke(student, "is a good boy");
System.out.println(result);

运行程序后输出:

niumi(20), is a good boy.

2. 动态扩展现有类或接口的二进制字节码(AOP)

想统计Studentdisplay方法的耗时时间。最简单的思路是使用Student修改display方法的源码:

public String display(String desc) {
    //记录方法调用的开始时间
    long start = System.currentTimeMillis();
    try {
            Thread.sleep(2000l);
            String result = this.name + "," + this.age + "," + desc + ".";
            //方法结束时打印耗时
            System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
            return result;
        } catch (InterruptedException e) {
            return "";
        }
}

javassist的CtClass方法提供的insertBeforeinsertAfter方法,允许我们在一个方法开始和结束添加自己的代码。

String className = "Student";
String methodName = "display";

ClassPool classPool = ClassPool.getDefault();
CtClass clazz = classPool.get(className);
CtMethod displayMethod = clazz.getDeclaredMethod(methodName);
displayMethod.insertBefore("long start=System.currentTimeMillis();");
displayMethod.insertAfter("System.out.println(\"耗时:\"+(System.currentTimeMillis()-start)+\"ms\");");

Student student = (Student) clazz.toClass().newInstance();
student.display("is a good boy");

运行程序会报如下错误:

101_1.png

因为javassist插入的代码片段中,每次插入操作的代码,称之为一个插入代码块,后面的插入块不能使用前面的插入块定义的局部变量,而言且也不能使用方法中的原有局部变量。而上述代码中,我们分表调用了insertBeforeinsertAfter插入了两个代码块,而后面的插入块不能使用前面的插入块定义的局部变量start,因此报错。

如果代码片段都位于一个插入块中,则局部变量是可以引用的。因此考虑使用如下的方法实现: 将原有的display方法名改为display$impl,然后再定义一个display方法,新的display方法内部会调用display$impl,在调用之前和调用之后分别加入上述的代码片段。

String className = "Student";
String methodName = "display";

ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get(className);
CtMethod method = ctClass.getDeclaredMethod(methodName);
String newName = methodName + "$impl";
method.setName(newName);

CtMethod newMethod = CtNewMethod.make("public String " + methodName + "(String desc){" +
        "long start=System.currentTimeMillis();" +
        "String data = " + newName + "(desc);" +
        "System.out.println(\"耗时:\"+(System.currentTimeMillis() - start)+\"ms\");" +
        "return data;" +
        "}", ctClass);
ctClass.addMethod(newMethod);

Constructor<?> constructor = ctClass.toClass()
                                .getDeclaredConstructor(String.class,Integer.class);
Student student = (Student) constructor.newInstance("niumi",20);
student.display("is a good boy");

此外还有一种更加简单的方式:

ProxyFactory factory=new ProxyFactory();
factory.setSuperclass(Student.class);
Class<ProxyObject> proxyClass = factory.createClass();
Student student = (Student) proxyClass.newInstance();
//设置拦截处理
((ProxyObject)student).setHandler(new MethodHandler() {
    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed,
                         Object[] args) throws Throwable {
        long start=System.currentTimeMillis();
        Object result = proceed.invoke(self, args);
        System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
        return result;
    }
});
student.display("is a good boy");

未经允许不得转载:搜云库技术团队 » 字节码动态生成库-Javassist

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们