欢迎关注微信公众号:互联网全栈架构

Javaassist(JAVA programming ASSISTant),是一个可以操控字节码的类库,能够在不修改源代码的情况下,在运行时动态地对类的结构和方法进行拓展(当然也可以新增类)。它提供了丰富的API,让开发人员可以方便地对字节码进行操控。下面我们用实例来展示它的使用。创作不易,辛苦在文末点个赞,谢谢!

首先初步认识一下字节码,我们知道,Java源代码在编译后生成了class文件,也就是字节码文件,JVM加载class文件后再翻译成对应平台的指令。定义一下简单的类,看看生成的字节码文件是什么样子,有一个初步的直观感受:

package com.sample.core.bytecode;
public class Calculator {
    public int sum(int x, int y){
        System.out.println("Sum Operation");
        return x+y;
    }
}

编译后生成了.class文件,使用javap命令查看文件的字节码:

javap -c Calculator.class
public class com.sample.core.bytecode.Calculator {
  public com.sample.core.bytecode.Calculator();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int sum(int, int);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Sum Operation
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: iload_1
       9: iload_2
      10: iadd
      11: ireturn
}

以上就是对应的字节码,而Javaassist提供了丰富的API,可以操控字节码文件。对于这些字节码指令的具体含义,可以参考具体的资料说明,这里不做说明。我们来看看Javaassist的一些主要功能,比如新增一个类、加载已有类并修改、新增字段和方法等。首先在POM文件中引入依赖:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.28.0-GA</version>
</dependency>

一、生成一个新类

Javaassist可以新生成一个类,比如我们想新生成一个类ManuNewClass,它实现接口java.io.Serializable,并且有一个名为id的成员变量,ClassFile用于定义一个新类,FieldInfo用于新增一个成员变量,CtClass是类的抽象表示形式:

// 新增一个类,并实现接口
ClassFile cf = new ClassFile(
        false, "com.sample.core.bytecode.ManuNewClass", null);
cf.setInterfaces(new String[] {"java.io.Serializable"});
// 增加新的成员变量
FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "id");
f.setAccessFlags(AccessFlag.PUBLIC);
cf.addField(f);
// 生成新类
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass(cf);

二、修改现有类

比如我们之前定义的类Calculator,现在修改它的父类为com.sample.core.bytecode.ParentClass:

ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("com.sample.core.bytecode.Calculator");
ctClass.setSuperclass(classPool.get("com.sample.core.bytecode.ParentClass"));

三、新增成员变量

在之前定义的计算器类中新增成员变量id:

ClassPool classPool = ClassPool.getDefault();
ClassFile classFile = classPool.get("com.sample.core.bytecode.Calculator").getClassFile();
FieldInfo fieldInfo = new FieldInfo(classFile.getConstPool(), "id", "id");
fieldInfo.setAccessFlags(AccessFlag.PUBLIC);
classFile.addField(fieldInfo);

四、新增成员方法

比如我们要新增一个test方法,它打印Hello World:

ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("com.sample.core.bytecode.Calculator");
CtMethod ctMethod =CtNewMethod.make("public void test() { System.out.println(\"Hello World\");}", ctClass);
ctClass.addMethod(ctMethod);

五、示例

我们以常见的AOP为例,比如我们要在方法调用之前做一些操作,在方法调用之后再做别的操作,类似于这样的功能,用Javaassist是可以实现的,对于我们在文章开头定义的计算器类,如果我们需要在计算之前打印信息,在计算完成后也要给出提示,在不修改源代码的情况下,实现方法如下:

package com.sample.core.bytecode;

import javassist.*;
public class JavaassistExample {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass targetClass = pool.get("com.sample.core.bytecode.Calculator");
        CtMethod targetMethod = targetClass.getDeclaredMethod("sum");
        // 在方法调用前打印信息
        targetMethod.insertBefore("{ System.out.println(\"Start Calculating:\"); }");
        // 在方法调用后打印信息
        targetMethod.insertAfter("{ System.out.println(\"End Calculating:\"); }");
        Class c = targetClass.toClass();
        Calculator calculator = (Calculator) c.newInstance();
        calculator.sum(5, 7);
    }
}

运行上面的程序,在控制台中打印出如下信息,可以看出,在调用方法sum前后,都打印出了对应的信息:

Start Calculating:
Sum Operation
End Calculating:

六、总结

Javaassist可以对字节码进行操作,所以对于AOP、动态代理、安全检测、链路跟踪等方面都能够广泛应用,相比于ASM,它提供了更为友好和便捷的API,但在性能要求较为苛刻的场合,ASM更胜一筹。

以上就是关于字节码增强的类库Javaassist介绍,谢谢!

都看到这里了,请帮忙一键三连啊,也就是点击文末的在看、点赞、分享,这样会让我的文章让更多人看到,也会大大地激励我进行更多的输出,谢谢!

鸣谢:

https://github.com/jboss-javassist/javassist

https://www.51cto.com/article/750294.html

推荐阅读:

Spring Boot Starter原理及实践

聊聊MySQL中的死锁

越俎代庖:应用广泛的代理模式

MySQL整数类型的长度到底是什么含义?

漫谈MySQL中的事务

臭名昭著,怙恶不悛的OOM,到底是什么?

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐