动态字节码

简介: 在Java中其字节码以二进制的形式存储在class文件中,每一个class文件都包含一个java类或接口,我们可以通过一些动态字节码技术去实现动态创建类、添加类的属性和方法、设置类的父类,以及修改类的方法等操作。常用的动态字节码类库有Javassist、ASM等。而Javassist相比ASM其不需要接触JVM底层的指令,只需要使用Javassist提供API接口就可以实现动态字节码编程,Mybatis动态是实现Dao接口底层也是使用到了Javassist技术,所以这篇文章主要介绍一下Javassist的使用。

在Java中其字节码以二进制的形式存储在class文件中,每一个class文件都包含一个java类或接口,我们可以通过一些动态字节码技术去实现动态创建类、添加类的属性和方法、设置类的父类,以及修改类的方法等操作。常用的动态字节码类库有Javassist、ASM等。而Javassist相比ASM其不需要接触JVM底层的指令,只需要使用Javassist提供API接口就可以实现动态字节码编程,Mybatis动态是实现Dao接口底层也是使用到了Javassist技术,所以这篇文章主要介绍一下Javassist的使用。


Javassist依赖导入


使用Javassist之前需要导入依赖,在Maven的pom添加如下依赖


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


Javassist常用类


ClassPool:ClassPool 类可以控制的类的字节码,例如创建一个类或加载一个类,与 JVM 类装载器类似
CtClass: CtClass提供了类的操作,如在类中动态添加新字段、方法和构造函数、以及改变类、父类和接口的方法
CtField:类的属性,通过它可以给类创建新的属性,还可以修改已有的属性的类型,访问修饰符等
CtMethod:表示类中的方法,通过它可以给类创建新的方法,还可以修改返回类型,访问修饰符等, 甚至还可以修改方法体内容代码
CtConstructor:用于访问类的构造,与CtMethod类的作用类似


Javassist使用


添加方法


看看如下一段代码


package ysoserial;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class Test {
    public static void main(String[] args) throws Exception {
        //获取类池
        ClassPool pool = ClassPool.getDefault();
        // 利用类池创建类
        CtClass ctClass = pool.makeClass("ysoserial.JavassistTest");
        // 创建方法
        // 1.返回值类型 2.方法名 3.形式参数列表 4.所属类
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, ctClass);
        // 设置方法的修饰符列表
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 设置方法体
        ctMethod.setBody("{System.out.println(\"hello world\");}");
        // 给类添加方法
        ctClass.addMethod(ctMethod);
        // 调用方法
        Class<?> aClass = ctClass.toClass();
        Object o = aClass.newInstance();
        Method method = aClass.getDeclaredMethod("execute");
        method.invoke(o);
    }
}


首先在代码的12行-14创建了一个类池,然后利用makeClass去创建一个类


当一个类已存在的时候,我们还可以利用getCtClass去获取这个类,然后对这个类进行动态修改


CtClass ctClass1 = classPool.getCtClass("ysoserial.JavassistTest");


在创建或获取一个类之后,我们通过会把这个对象赋值给一个ctClass句柄,然后通过ctClass句柄去操作这个类


代码的第17行利用CtMethod类添加一个execute方法,在new CtMethod里面依次有四个参数,分别为


CtClass.voidType  //返回值的类型
"execute"  //添加的方法名
new CtClass[]{}  //形参列表
ctClass  //所属类,也就是我们利用类池创建出来的那个类


返回的参数类型,可以在CtClass这类的源码里面可以清晰查看



代码的第19行设置我们之前定义的execute这个方法的访问修饰符


ctMethod.setModifiers(Modifier.PUBLIC);


代码的第19行设置我们之前定义的execute这个方法的访问修饰符


ctMethod.setBody("{System.out.println(\"hello world\");}");


代码的第21行设置我们之前定义的execute这个方法体里面的内容


ctMethod.setBody("{System.out.println(\"hello world\");}");


代码的第23行,把之前我们在方法里面设置好的东西全部添加到类当中去


ctClass.addMethod(ctMethod);


代码的第25-26行转化为class类然后进行实例化


Class<?> aClass = ctClass.toClass();
Object o = aClass.newInstance();


代码的27-28行获取方法并执行


Method method = aClass.getDeclaredMethod("execute");
method.invoke(o);


执行代码可以看到输出hello world



添加成员属性


package ysoserial;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import java.lang.reflect.Modifier;
public class Test {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.makeClass("ysoserial.JavassistTest");
        //添加一个int类型的age变量
        CtField ctField = new CtField(CtClass.intType,"age",ctClass);
        ctField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(ctField);
        //添加一个String类型的name变量
        CtField ctField2 = new CtField(classPool.getCtClass("java.lang.String"), "name" , ctClass);
        ctField2.setModifiers(Modifier.PUBLIC);
        ctClass.addField(ctField2);
        Class aClass = ctClass.toClass();
        ctClass.writeFile();
        Object obj = aClass.newInstance();
    }
}


添加构造方法


package ysoserial;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import java.lang.reflect.Modifier;
public class Test {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.makeClass("ysoserial.JavassistTest");
        //添加一个int类型的age变量
        CtField ctField = new CtField(CtClass.intType,"age",ctClass);
        ctField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(ctField);
        //添加一个String类型的name变量
        CtField ctField2 = new CtField(classPool.getCtClass("java.lang.String"), "name" , ctClass);
        ctField2.setModifiers(Modifier.PUBLIC);
        ctClass.addField(ctField2);
        //添加有参构造
        CtConstructor ctConstructor1 = new CtConstructor(new CtClass[]{ CtClass.intType, classPool.getCtClass("java.lang.String")},ctClass);
        ctConstructor1.setModifiers(Modifier.PUBLIC);
        ctConstructor1.setBody("{$0.age = $1;$0.name = $2;}");
        ctClass.addConstructor(ctConstructor1);
        Class aClass = ctClass.toClass();
    }
}
目录
相关文章
|
6月前
|
安全 Java API
JDK 11中的动态类文件常量:探索Java字节码的灵活性与动态性
在JDK 11中,Java语言引入了一个新的特性,允许在运行时动态地修改类文件常量。这一特性为Java开发者提供了更大的灵活性,使他们能够根据需要在运行时更改类文件中的常量值。本文将深入探讨动态类文件常量的工作原理、优点、限制以及在实际项目中的应用。
141 11
|
6月前
|
Java 索引
【JVM】字节码文件的组成部分
【JVM】字节码文件的组成部分
52 1
|
6月前
|
存储 XML 监控
JVM工作原理与实战(三):字节码文件的组成
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了字节码文件的基础信息、常量池、方法、字段、属性等内容。
91 6
|
6月前
|
存储 Java 编译器
深入理解JVM - 字节码指令
深入理解JVM - 字节码指令
102 0
|
XML 设计模式 JavaScript
JVM第三讲:JVM 基础-字节码的增强技术详解
JVM第三讲:JVM 基础-字节码的增强技术详解
162 0
JVM第三讲:JVM 基础-字节码的增强技术详解
|
Java
有几种方式获取字节码文件对象?
有几种方式获取字节码文件对象?
114 0
|
存储 人工智能 Java
通过字节码分析i++ 与 ++i
通过字节码分析i++ 与 ++i
|
存储 JavaScript 安全
jvm(16) -- 虚拟机字节码执行引擎(运行时栈帧结构)
jvm(16) -- 虚拟机字节码执行引擎(运行时栈帧结构)
166 0
jvm(16) -- 虚拟机字节码执行引擎(运行时栈帧结构)
|
前端开发 IDE Java
Java虚拟机学习:方法调用的字节码指令
通过一个实例将JVM方法调用的字节码指令逐个列出,一起来熟悉以下它们使用的场景
297 1
Java虚拟机学习:方法调用的字节码指令
|
存储 监控 前端开发
JVM字节码文件概述
字节码文件概述
127 0
JVM字节码文件概述