Java 字节码操纵框架 ASM

ASM 介绍

ASM 是一个 java 字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。

ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

官网:https://asm.ow2.io/

文档:https://asm.ow2.io/javadoc/overview-summary.html

下载:https://mvnrepository.com/artifact/org.ow2.asm/asm

使用 ASM 框架需要导入 asm 的 jar 包:

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>8.0</version>
</dependency>

ASM 框架中的核心类有以下几个:

  • ClassReader:该类用来解析编译过的 class 字节码文件。
  • ClassWriter:该类用来重新构建编译后的类,比如说修改类名、属性以及方法,甚至可以生成新的类的字节码文件。
  • ClassAdapter:该类也实现了 ClassVisitor 接口,它将对它的方法调用委托给另一个 ClassVisitor 对象。

字节码处理流程

asm 字节码处理流程:

目标类 class bytes -> ClassReader 解析 -> ClassVisitor 增强修改字节码 -> ClassWriter 生成增强后的 class bytes -> 通过 Instrumentation 解析加载为新的 Class。

如下图所示:

1599459538607

ASM 动态操作方法

示例代码 com.demo.Foo

package com.demo;

public class Foo {
	public void execute() {
		System.out.println("test changed method name");
	}

	public void changeMethodContent() {
		System.out.println("test changed method");
	}
}

com.asm.MethodChangeClassAdapter

package com.asm;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MethodChangeClassAdapter extends ClassVisitor implements Opcodes {
	public MethodChangeClassAdapter(ClassVisitor cv) {
		super(Opcodes.ASM4, cv);
	}

	@Override
	public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
		if (cv != null) {
			cv.visit(version, access, name, signature, superName, interfaces);
		}
	}

	@Override
	public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
		// 当方法名为execute时,修改方法名为execute1
		if (cv != null && "execute".equals(name)) {
			return cv.visitMethod(access, name + "1", desc, signature, exceptions);
		}

		// 此处的changeMethodContent即为需要修改的方法 ,修改方法內容
		if ("changeMethodContent".equals(name)) {
			// 先得到原始的方法
			MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
			MethodVisitor newMethod = null;
			// 访问需要修改的方法
			newMethod = new AsmMethodVisit(mv);
			return newMethod;
		}
		if (cv != null) {
			return cv.visitMethod(access, name, desc, signature, exceptions);
		}
		return null;
	}
}

com.asm.AsmMethodVisit

package com.asm;

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class AsmMethodVisit extends MethodVisitor {
	public AsmMethodVisit(MethodVisitor mv) {
		super(Opcodes.ASM4, mv);
	}

	@Override
	public void visitMethodInsn(int opcode, String owner, String name, String desc) {
		super.visitMethodInsn(opcode, owner, name, desc);
	}

	@Override
	public void visitCode() {
		// 此方法在访问方法的头部时被访问到,仅被访问一次
		// 此处可插入新的指令
		super.visitCode();
	}

	@Override
	public void visitInsn(int opcode) {
		// 此方法可以获取方法中每一条指令的操作类型,被访问多次
		// 如应在方法结尾处添加新指令,则应判断:
		if (opcode == Opcodes.RETURN) {
			// pushes the 'out' field (of type PrintStream) of the System class
			mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
			// pushes the "Hello World!" String constant
			mv.visitLdcInsn("this is a modify method!");
			// invokes the 'println' method (defined in the PrintStream class)
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
		}
		super.visitInsn(opcode);
	}
}

测试类 com.asm.AsmExample

package com.asm;

import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import com.demo.Foo;

public class AsmExample extends ClassLoader implements Opcodes {

	public static void main(String args[]) throws IOException, IllegalArgumentException, SecurityException,
			IllegalAccessException, InvocationTargetException, InstantiationException {
		ClassReader cr = new ClassReader(Foo.class.getName());
		ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
		ClassVisitor cv = new MethodChangeClassAdapter(cw);
		cr.accept(cv, Opcodes.ASM4);

		// 新增加一个方法
		MethodVisitor mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "add", "([Ljava/lang/String;)V", null, null);
		mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
		mw.visitLdcInsn("this is add method print!");
		mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
		mw.visitInsn(RETURN);
		// this code uses a maximum of two stack elements and two local
		// variables
		mw.visitMaxs(0, 0);
		mw.visitEnd();

		byte[] code = cw.toByteArray();
		AsmExample loader = new AsmExample();
		Class<?> exampleClass = loader.defineClass(Foo.class.getName(), code, 0, code.length);

		for (Method method : exampleClass.getMethods()) {
			System.out.println(method);
		}

		System.out.println("***************************");

		// uses the dynamically generated class to print 'Helloworld'
		// 調用changeMethodContent,修改方法內容
		exampleClass.getMethods()[1].invoke(exampleClass.newInstance(), null);

		System.out.println("***************************");
		// 調用execute,修改方法名
		exampleClass.getMethods()[2].invoke(exampleClass.newInstance(), null);
		// gets the bytecode of the Example class, and loads it dynamically

		FileOutputStream fos = new FileOutputStream("e:\\Example.class");
		fos.write(code);
		fos.close();
	}

}

输出结果:

public static void com.demo.Foo.add(java.lang.String[])
public void com.demo.Foo.changeMethodContent()
public void com.demo.Foo.execute1()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
***************************
test changed method
this is a modify method!
***************************
test changed method name

通过反编译软件,查看 E 盘生成的 Example.class,结果如下:

package com.demo;

public class Foo {
    public void execute1() {
        System.out.println("test changed method name");
    }

    public void changeMethodContent() {
        System.out.println("test changed method");
        System.out.println("this is a modify method!");
    }

    public static void add(String[] arrstring) {
        System.out.println("this is add method print!");
    }
}