Java的字节码操作框架ASM详解
文章目录
一、ASM介绍
1. 简介
ASM是一个用于在Java字节码级别进行操作的框架,它运行你在不破坏原有逻辑的情况下修改、生成和转换字节码。ASM提供类一种灵活高效的方式,用于在编译器运行时动态生成字节码,这对于许多Java框架和工具来说是非常有用的。
2. 基本概念
字节码:字节码是Java源代码编译后的中间表达形式,它由字节码指令组成,这些指令被Java虚拟机执行。
访问者模式:ASM使用访问者模式,它通过访问者来访问和操作字节码。在ASM中,你可以实现自定义的访问者来处理不同类型的字节码元素。
类适配器和方法适配器:ASM提供了类适配器和方法适配器,用于修改类和方法的字节码。类适配器可以用于全局类操作,而方法适配器则可以用于单个方法的操作。
3. Java ASM的应用场景
- 代码分析和优化:通过ASM,我们可以对字节码进行深入分析,以找出潜在的性能瓶颈或者执行特定的优化
- 动态代理和AOP(面向切面编程):ASM可以用于创建动态代理或实现AOP框架,从而实现运行时的行为修改
- 自定义类加载器:通过ASM,我们可以实现自定义类加载器,以支持独特的类加载策略和实现热加载等功能
- 安全审计:ASM可以用于分析潜在的安全风险,例如检测恶意代码或验证第三方库的安全性
二、ASM基本使用
1. 基本使用
添加ASM依赖
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version> <!-- 版本号可能有更新 -->
</dependency>
生成一个类
public class HelloWorldGenerator {
public static byte[] generateHelloWorldClass() {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// Define the class
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "HelloWorld", null, "java/lang/Object", null);
// Define the constructor
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// Define the main method
mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello, ASM!");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
}
使用生成的类:
public class HelloWorldRunner {
public static void main(String[] args) {
byte[] classData = HelloWorldGenerator.generateHelloWorldClass();
CustomClassLoader loader = new CustomClassLoader();
Class<?> helloWorldClass = loader.defineClass("HelloWorld", classData);
try {
// Invoke the main method
helloWorldClass.getMethod("main", String[].class).invoke(null, (Object) args);
} catch (Exception e) {
e.printStackTrace();
}
}
// Custom class loader to define a class from byte array
static class CustomClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
}
2. Java ASM主要组件
JavaASM提供了的主要组件包括ClassReader
、ClassWriter
、ClassVisitor
、MethodVIsitor
。
(1)ClassReader(类读取器)
ClassReader用于读取已经存在的类文件的字节码,并以一种结构化的方式提供对类结构的访问。它能够解析类的结构,包括类的字段、方法和注解信息。
accept(ClassVisitor classVisitor,int flags)
//将类的结构传递给给定的ClassVisitor,flags参数用于指定读取选项
(2)ClassWriter(类写入器)
ClassWriter
用于生产新的类文件的字节码。你可以通过添加新的字段、方法等,最终生成一个新的类字节码
visit(int version, int access, String name, String signature, String superName, String[] interfaces)
//开始访问一个类,指定类的版本、访问标志、类名、泛型签名、父类和实现的接口。
visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)
//开始访问一个方法,指定方法的访问标志、方法名、描述符、泛型签名和可能抛出的异常。
(3)ClassVisitor(类访问器):
ClassVisitor是一个接口,用于遍历和修改类的结构。你可以自定义一个ClassVisitor,重写其方法,在访问类、方法、字段等时执行你的逻辑。
visit(int version, int access, String name, String signature, String superName, String[] interfaces)
//开始访问一个类。
visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)
//开始访问一个方法。
visitField(int access, String name, String descriptor, String signature, Object value)
//访问类的字段。
(4)MethodVisitor(方法访问器)
MethodVisitor 类似于 ClassVisitor,但专注于方法级别的访问和修改。你可以在访问方法、局部变量、指令等时插入你的操作。
visitCode()
//访问方法的代码开始部分。
visitInsn(int opcode)
//访问一个无操作指令。
visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
//访问方法调用指令
这些组件共同构成了ASM的核心,使得开发人员能够以精细的方式操作Java字节码。通过创建自定义的 ClassVisitor 和 MethodVisitor,你可以在字节码层面执行各种操作,包括字节码增强、代码注入、AOP等。
3. Java ASM使用组件准备
创建一个简单的Java类
public class MyClass {
public void myMethod() {
System.out.println("Original method");
}
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.myMethod();
}
}
创建ClassReader
要读取字节码,我们首先要创建一个ClassReader实例。ClassReader可以接收一个字节数组,表示已编译的Java类文件。
//从文件系统中加载字节码
byte[] bytecode= Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
//创建ClassReader实例
ClassReader classReader=new ClassReader(bytecode);
使用ClassVisitor解析字节码
要解析字节码,我们需要创建一个自定义的ClassVisitor实现。以下是一个简单的案例:
public class MyClassVisitor extends ClassVisitor {
//使用ASM,指定ASM的版本为5
public MyClassVisitor(){
//调用父类的构造函数,指定ASM的版本为5
super(Opcodes.ASM5);
}
@Override
public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) {
//打印类名
System.out.println("Class:"+s);
//调用父类的visit方法,以便继续处理类信息
super.visit(version, access, name, signature, supername, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
//打印方法名
System.out.println("Method:"+name);
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
Visit()方法参数说明:
version
:类文件的版本号,表示类文件的JDK版本。例如,JDK1.8的版本是52(0x34),JDK 11 对应的版本为55(0x37)
access
:类访问标志,表示类的访问权限和属性
name
:类的内部名称,如我们这个类就是com/jack/redis/pojo/MyClass
sinature
:类的范型签名,如果没有范型信息,则为null
superName
:父类的内部名。这里为java.lang.Object
interfaces
:类实现的接口的内部名称数组
visitMethod()方法参数说明:
access
:方法访问标志
name
:放啊法的名称
descriptor
:方法的描述符,表示方法的参数类型和返回值类型
signature
:放啊法的范型信息
exceptions
:方法抛出的异常的内部名称数组
public class LogInjector {
public static void main(String[] args) throws IOException {
//从文件系统中加载字节码
byte[] bytecode= Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
//创建ClassReader实例
ClassReader classReader=new ClassReader(bytecode);
MyClassVisitor classVisitor=new MyClassVisitor();
//使用ClassReader的accept方法,将MyClassVisitor传递给ClassReader进行字节码解析
classReader.accept(classVisitor,0);
}
}
上面解析字节码需要的基本组件都已经初始化完毕,下面开始真正解析字节码:
4. 修改字节码
- 添加、修改和删除字节码
首先修改一下MyClass类
public class MyClass {
private String name;
private String type;
public void myMethod() {
System.out.println("Original method");
}
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.myMethod();
}
}
要添加、修改或删除字节码,我们需要扩展ClassVisitor类并重写visitField方法。
public class MyFieldClassVisitor extends ClassVisitor {
public MyFieldClassVisitor(ClassVisitor classVisitor){
super(Opcodes.ASM5,classVisitor);
}
//visitField在访问类的字段时被调用,用于对字段进行修改
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
//删除MyClass类的name字段
if("name".equals(s)){
//返回null表示删除字段
return null;
}
return super.visitField(access, name,descriptor, signature, value);
}
//visitEnd方法在访问类结尾时被调用,用于在类结尾添加新的字段
@Override
public void visitEnd() {
//添加名为"newField"的字段
FieldVisitor fieldVisitor=super.visitField(Opcodes.ACC_PRIVATE,"newField","Ljava/lang/String;",null,null);
if(fieldVisitor!=null){
fieldVisitor.visitEnd();
}
super.visitEnd();
}
}
测试代码如下:
public class LogInjector {
public static void main(String[] args) {
try {
// 读取原始类的字节码
byte[] originalClassBytes = readClassBytes("MyClass.class");
// 使用 MyFieldClassVisitor 修改类
byte[] modifiedClassBytes = modifyClass(originalClassBytes);
// 将修改后的字节码保存到文件
saveToFile("MyModifiedClass.class", modifiedClassBytes);
// 运行修改后的类
runModifiedClass();
} catch (IOException e) {
e.printStackTrace();
}
}
private static byte[] readClassBytes(String className) throws IOException {
// 从类文件读取字节码
// 这里假设 MyClass.class 文件位于当前工程目录下
// 实际应用中,你可能需要根据你的工程结构调整路径
return Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
}
private static byte[] modifyClass(byte[] originalClassBytes) {
// 使用 MyFieldClassVisitor 修改类字节码
ClassReader classReader = new ClassReader(originalClassBytes);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MyFieldClassVisitor myFieldClassVisitor = new MyFieldClassVisitor(classWriter);
classReader.accept(myFieldClassVisitor, ClassReader.SKIP_DEBUG);
// 返回修改后的字节码
return classWriter.toByteArray();
}
private static void saveToFile(String fileName, byte[] data) throws IOException {
// 将字节码保存到文件
try (FileOutputStream fos = new FileOutputStream(fileName)) {
fos.write(data);
}
}
private static void runModifiedClass() {
// 运行修改后的类
MyClass modifiedClass = new MyClass();
System.out.println(modifiedClass);
}
}
可以看到修改后的class文件
- 添加、修改和删除方法
要添加、修改和删除方法,我们需要扩展ClassVisitor类并重写visitMethod方法。
public class MyMethodClassVisitor extends ClassVisitor {
public MyMethodClassVisitor(ClassVisitor classVisitor){
super(Opcodes.ASM5,classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
//删除myMethod方法
if("myMethod".equals(name)){
return null;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
@Override
public void visitEnd() {
//添加名为"newMethod"方法
MethodVisitor methodVisitor=super.visitMethod(Opcodes.ACC_PUBLIC,"newMethod","()V",null,null);
if(methodVisitor!=null){
//开始访问方法的字节码
methodVisitor.visitCode();
//向方法字节码中添加Returen指令,表示方法返回
methodVisitor.visitInsn(Opcodes.RETURN);
//设置方法的最大操作树栈深度和局部变量大小,这里我们是一个空方法
methodVisitor.visitMaxs(0,0);
methodVisitor.visitEnd();
}
super.visitEnd();
}
}
下面我们来测试一下生成的字节码是怎么样子的
public class LogInjector {
public static void main(String[] args) {
try {
// 读取原始类的字节码
byte[] originalClassBytes = readClassBytes("MyClass.class");
// 使用 MyFieldClassVisitor 修改类
byte[] modifiedClassBytes = modifyClass(originalClassBytes);
// 将修改后的字节码保存到文件
saveToFile("MyModifiedClass.class", modifiedClassBytes);
// 运行修改后的类
runModifiedClass();
} catch (IOException e) {
e.printStackTrace();
}
}
private static byte[] readClassBytes(String className) throws IOException {
// 从类文件读取字节码
// 这里假设 MyClass.class 文件位于当前工程目录下
// 实际应用中,你可能需要根据你的工程结构调整路径
return Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
}
private static byte[] modifyClass(byte[] originalClassBytes) {
// 使用 MyFieldClassVisitor 修改类字节码
ClassReader classReader = new ClassReader(originalClassBytes);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MyMethodClassVisitor myMethodClassVisitor = new MyMethodClassVisitor(classWriter);
classReader.accept(myMethodClassVisitor, ClassReader.SKIP_DEBUG);
// 返回修改后的字节码
return classWriter.toByteArray();
}
private static void saveToFile(String fileName, byte[] data) throws IOException {
// 将字节码保存到文件
try (FileOutputStream fos = new FileOutputStream(fileName)) {
fos.write(data);
}
}
private static void runModifiedClass() {
// 运行修改后的类
MyClass modifiedClass = new MyClass();
System.out.println(modifiedClass);
}
}
成功删除了原来的myMethod方法,并添加了新的方法
- 修改的方法中的指令
要修改方法中的指令,我们需要扩展MethodVisitor类并重写相应的visit方法。下面这个示例表示在每个方法调用前添加一条打印日志的指令:
public class MyMethodAdapter extends MethodVisitor {
public MyMethodAdapter(MethodVisitor methodVisitor){
super(Opcodes.ASM5,methodVisitor);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
//在方法调用前添加打印日志
mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream");
mv.visitLdcInsn("Enterning method:"+name);
//熟悉字节码结构的都知道,这是在向字节码文件中插入内容
mv.visitMethodInsn(Opcodes.INVOKESPECIAL,"java/io/PringStream","println","(Ljava/lang/String;)V",false);
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
要应用这个方法适配器,我们需要在自定义的ClassVisitor实现中重写visitMethod方法
public class MyMethodLoggerClassVisitor extends ClassVisitor {
public MyMethodLoggerClassVisitor(ClassVisitor classVisitor){
super(Opcodes.ASM5,classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor=super.visitMethod(access,name,descriptor,signature,exceptions);
return new MyMethodAdapter(methodVisitor);
}
}
测试方法如下:
public class LogInjector {
public static void main(String[] args) {
try {
// 读取原始类的字节码
byte[] originalClassBytes = readClassBytes("MyClass.class");
// 使用 MyFieldClassVisitor 修改类
byte[] modifiedClassBytes = modifyClass(originalClassBytes);
// 将修改后的字节码保存到文件
saveToFile("MyModifiedClass.class", modifiedClassBytes);
// 运行修改后的类
runModifiedClass();
} catch (IOException e) {
e.printStackTrace();
}
}
private static byte[] readClassBytes(String className) throws IOException {
// 从类文件读取字节码
// 这里假设 MyClass.class 文件位于当前工程目录下
// 实际应用中,你可能需要根据你的工程结构调整路径
return Files.readAllBytes(Paths.get("/Users/jackchai/Desktop/Self-study-notes/Redis/redis_project/redis_test/target/classes/com/jack/redis/pojo/MyClass.class"));
}
private static byte[] modifyClass(byte[] originalClassBytes) {
// 使用 MyFieldClassVisitor 修改类字节码
ClassReader classReader = new ClassReader(originalClassBytes);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MyMethodLoggerClassVisitor myMethodClassVisitor = new MyMethodLoggerClassVisitor(classWriter);
classReader.accept(myMethodClassVisitor, ClassReader.SKIP_DEBUG);
// 返回修改后的字节码
return classWriter.toByteArray();
}
private static void saveToFile(String fileName, byte[] data) throws IOException {
// 将字节码保存到文件
try (FileOutputStream fos = new FileOutputStream(fileName)) {
fos.write(data);
}
}
private static void runModifiedClass() {
// 运行修改后的类
MyClass modifiedClass = new MyClass();
System.out.println(modifiedClass);
}
}
可以看出日志添加成功
- 生成新的字节码
在修改字节码后,我们需要使用ClassWriter (它是ClassVisitor的子类)生成新的字节码。上面提供的几个测试代码都用到了ClassWriter
private static byte[] modifyClass(byte[] originalClassBytes) {
// 使用 MyFieldClassVisitor 修改类字节码
ClassReader classReader = new ClassReader(originalClassBytes);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MyMethodLoggerClassVisitor myMethodClassVisitor = new MyMethodLoggerClassVisitor(classWriter);
classReader.accept(myMethodClassVisitor, ClassReader.SKIP_DEBUG);
// 返回修改后的字节码
return classWriter.toByteArray();
}
三、Java ASM高级技巧
1. 自定义类加载器
要实现自定义类加载器,我们需要扩展Java标准裤的ClassLoader,并重写findClass方法。以下展示了如何实现一个简单的自定义类加载器,它使用Java ASM修改类字节码后加载类:
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 加载原始字节码
String resourceName = name.replace('.', '/').concat(".class");
InputStream is = getClass().getClassLoader().getResourceAsStream(resourceName);
byte[] originalBytecode = is.readAllBytes();
// 使用 Java ASM 修改字节码
byte[] modifiedBytecode = modifyBytecode(originalBytecode);
// 使用修改后的字节码定义类
ByteBuffer byteBuffer = ByteBuffer.wrap(modifiedBytecode);
return defineClass(name, byteBuffer, null);
} catch (IOException e) {
throw new ClassNotFoundException("Failed to load class " + name, e);
}
}
private byte[] modifyBytecode(byte[] originalBytecode) {
// 创建 ClassReader 和 ClassWriter
ClassReader classReader = new ClassReader(originalBytecode);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
// 创建自定义的 ClassVisitor(执行切入动作)
MyMethodLoggerClassVisitor myMethodLoggerClassVisitor = new MyMethodLoggerClassVisitor(classWriter);
// 使用 ClassReader 遍历字节码,应用自定义的 ClassVisitor
classReader.accept(myMethodLoggerClassVisitor, ClassReader.EXPAND_FRAMES);
// 返回修改后的字节码
return classWriter.toByteArray();
}
}
2. 动态代理
动态代理是一种常用的设计模式,可以在运行时动态地为对象生成代理。使用 Java ASM,我们可以生成字节码来实现动态代理。以下是一个简单的动态代理示例,它实现了一个基于接口的代理:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
public class MyDynamicProxy {
public static <T> T createProxy(Class<T> interfaceClass, InvocationHandler handler) {
// 创建 ClassWriter
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
// 定义代理类,实现指定的接口
String proxyClassName = interfaceClass.getName() + "$Proxy";
String proxyClassInternalName = proxyClassName.replace('.', '/');
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, proxyClassInternalName, null, "java/lang/Object", new String[]{Type.getInternalName(interfaceClass)});
// 实现代理类的构造方法
MethodVisitor constructorVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructorVisitor.visitCode();
constructorVisitor.visitVarInsn(Opcodes.ALOAD, 0);
constructorVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructorVisitor.visitInsn(Opcodes.RETURN);
constructorVisitor.visitMaxs(1, 1);
constructorVisitor.visitEnd();
// 实现接口的所有方法
for (Method method : interfaceClass.getDeclaredMethods()) {
String methodName = method.getName();
String methodDescriptor = Type.getMethodDescriptor(method);
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor, null, null);
// 在代理方法中调用 InvocationHandler 的 invoke 方法
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, proxyClassInternalName, "handler", "Ljava/lang/reflect/InvocationHandler;");
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitLdcInsn(Type.getType(interfaceClass));
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/reflect/Method", "valueOf", "(Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(Opcodes.ARETURN);
methodVisitor.visitMaxs(4, 2);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
// 使用自定义类加载器加载代理类
byte[] proxyClassBytecode = classWriter.toByteArray();
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> proxyClass = myClassLoader.defineClass(proxyClassName, ByteBuffer.wrap(proxyClassBytecode), null);
// 创建代理类实例,并设置 InvocationHandler
try {
T proxyInstance = (T) proxyClass.getConstructor().newInstance();
proxyClass.getField("handler").set(proxyInstance, handler);
return proxyInstance;
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to create proxy instance", e);
}
}
}
可以使用MyDynamicProxy为接口创建动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
public class MyDynamicProxy {
public static <T> T createProxy(Class<T> interfaceClass, InvocationHandler handler) {
// 创建 ClassWriter
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
// 定义代理类,实现指定的接口
String proxyClassName = interfaceClass.getName() + "$Proxy";
String proxyClassInternalName = proxyClassName.replace('.', '/');
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, proxyClassInternalName, null, "java/lang/Object", new String[]{Type.getInternalName(interfaceClass)});
// 实现代理类的构造方法
MethodVisitor constructorVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructorVisitor.visitCode();
constructorVisitor.visitVarInsn(Opcodes.ALOAD, 0);
constructorVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructorVisitor.visitInsn(Opcodes.RETURN);
constructorVisitor.visitMaxs(1, 1);
constructorVisitor.visitEnd();
// 实现接口的所有方法
for (Method method : interfaceClass.getDeclaredMethods()) {
String methodName = method.getName();
String methodDescriptor = Type.getMethodDescriptor(method);
MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor, null, null);
// 在代理方法中调用 InvocationHandler 的 invoke 方法
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, proxyClassInternalName, "handler", "Ljava/lang/reflect/InvocationHandler;");
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitLdcInsn(Type.getType(interfaceClass));
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/reflect/Method", "valueOf", "(Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(Opcodes.ARETURN);
methodVisitor.visitMaxs(4, 2);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
// 使用自定义类加载器加载代理类
byte[] proxyClassBytecode = classWriter.toByteArray();
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> proxyClass = myClassLoader.defineClass(proxyClassName, ByteBuffer.wrap(proxyClassBytecode), null);
// 创建代理类实例,并设置 InvocationHandler
try {
T proxyInstance = (T) proxyClass.getConstructor().newInstance();
proxyClass.getField("handler").set(proxyInstance, handler);
return proxyInstance;
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to create proxy instance", e);
}
}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!