gpt4 book ai didi

java - asm如何生成子类

转载 作者:行者123 更新时间:2023-11-29 08:27:32 27 4
gpt4 key购买 nike

我在Google上搜索了如何通过asm生成子类,似乎很少有人关心这个问题。这个需求本身不合适吗?也许 asm 做的最常见的事情是通过 AdviceAdapter 在方法前后添加额外的代码。我觉得生成子类也是一个很常见的需求。其实要做到这一点并不容易。如何使所有公共(public)或 protected 方法自动覆盖父类的方法,就像 HttpServletRequest 的子类 HttpServletRequestWrapper 所做的那样。

我使用org.ow2.asm:asm:6.2实现如下:

public class InheritMethodVisitor extends ClassVisitor {

/** the return opcode for different type */
public static final Map<Type, Integer> RETURN_OPCODES = new HashMap<>();
/** the load opcode for different type */
public static final Map<Type, Integer> LOAD_OPCODES = new HashMap<>();

static {
RETURN_OPCODES.put(Type.VOID_TYPE, Opcodes.RETURN);
RETURN_OPCODES.put(Type.BOOLEAN_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.BYTE_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.SHORT_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.INT_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.LONG_TYPE, Opcodes.LRETURN);
RETURN_OPCODES.put(Type.FLOAT_TYPE, Opcodes.FRETURN);
RETURN_OPCODES.put(Type.DOUBLE_TYPE, Opcodes.DRETURN);

LOAD_OPCODES.put(Type.BOOLEAN_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.BYTE_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.SHORT_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.INT_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.LONG_TYPE, Opcodes.LLOAD);
LOAD_OPCODES.put(Type.FLOAT_TYPE, Opcodes.FLOAD);
LOAD_OPCODES.put(Type.DOUBLE_TYPE, Opcodes.DLOAD);
}


private Class<?> superClass;
private ClassVisitor cv;

public InheritMethodVisitor(int api, ClassVisitor classVisitor, Class<?> superClass) {
super(api);
this.cv = classVisitor;
this.superClass = Objects.requireNonNull(superClass);
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
//Inherit all public or protect methods
if (Modifier.isStatic(access) || Modifier.isPrivate(access)) return null;
if (name.equals("<init>") || Modifier.isProtected(access)) access = Opcodes.ACC_PUBLIC;

MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);

Type methodType = Type.getMethodType(descriptor);
Type[] argumentTypes = methodType.getArgumentTypes();

if (!name.equals("<init>")) {
//TODO Customize what you want to do
//System.out.println(name)
mv.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(System.class), "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(name);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), "println", "(Ljava/lang/String;)V", false);
}

//load this
mv.visitVarInsn(Opcodes.ALOAD, 0);
//load arguments
IntStream.range(0, argumentTypes.length).forEach(value ->
mv.visitVarInsn(LOAD_OPCODES.getOrDefault(argumentTypes[value], Opcodes.ALOAD), value + 1)
);

//invoke super.{method}()
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
Type.getInternalName(superClass),
name,
descriptor,
false);

//handle return
mv.visitInsn(RETURN_OPCODES.getOrDefault(methodType.getReturnType(), Opcodes.ALOAD));
//for ClassWriter.COMPUTE_FRAMES the max*s not required correct
int maxLocals = argumentTypes.length + 1;
mv.visitMaxs(maxLocals + 2, maxLocals);
mv.visitEnd();
return null;
}}

@Test
public void create() throws Exception {
//generate a subclass of AdviceAdapter to add logger info
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//generate class name
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/github/Generated",
null, Type.getInternalName(AdviceAdapter.class), null);
//generate overwrite methods
new ClassReader(AdviceAdapter.class.getName())
.accept(
new InheritMethodVisitor(Opcodes.ASM6, cw, AdviceAdapter.class),
ClassReader.EXPAND_FRAMES
);
//TODO AdviceAdapter.class.getSuperclass() not supported
//end
cw.visitEnd();
//save to file
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(getClass().getResource("/com/github").getPath(), "Generated.class"), bytes);
//show use of it
ClassReader classReader = new ClassReader(AdviceAdapter.class.getName());
classReader.accept(
new ClassVisitor(Opcodes.ASM6, new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES)) {
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
try {
Class<?> aClass = Class.forName("com.github.Generated");
return (MethodVisitor) aClass.getConstructors()[0].newInstance(Opcodes.ASM6, methodVisitor, access, name, descriptor);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
},
ClassReader.EXPAND_FRAMES
);
}

它可以工作,但不是很容易。事实上,我认为 TraceClassVisitor 工作起来很简单。

最佳答案

您真的应该看看 ByteBuddy 库 - 因为它更适合您的目标,所以没有理由为此类通用任务使用如此低级的库。

在 ASM 中你需要一个人做这一切——你不能只告诉它为你生成和实现方法,ASM 库只是修改原始字节码,所以你需要阅读我生成的父类(super class)的所有方法他们每个人的字节码。您可以使用 asm 模块为您打印 asm 代码:​​https://static.javadoc.io/org.ow2.asm/asm/5.2/org/objectweb/asm/util/ASMifier.html .或者从命令行:

java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Runnable

这将创建工作的 ASM 代码来生成给定的类,如下所示:

package asm.java.lang;
import org.objectweb.asm.*;

public class RunnableDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "run", "()V",
null, null);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}

(也有Intellij IDEA的插件可以让你看到字节码和ASMifed代码,只要在插件中寻找ASM)

要创建一个子类,您需要做的就是在 ASM 中生成类时传递该子类的名称,如上例所示:

cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);

java/lang/Object 是您要扩展的父类(super class),完成。
但是对于方法,你需要手动遍历所有方法并生成你想要的代码。
除非你想创建一些不太典型的东西或更通用的东西,比如自己的库来生成像 ByteBuddy 这样的代理类——那么最好使用一些已经存在的解决方案:ByteBuddy、Javassist、CGLib。 (他们也都使用ASM生成字节码)

关于java - asm如何生成子类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51399073/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com