一、什么是ASM
ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。asm字节码增强技术主要是用来反射的时候提升性能的,如果单纯用jdk的反射调用,性能是非常低下的,而使用字节码增强技术后反射调用的时间已经基本可以与直接调用相当了
二、如何使用ASM
我对ASM实现的理解:
ASM使用了树的结构来对复杂的java字节码进行访问,并结合push模型来进行对字节码的修改。以下我结合ASM中的几个重要数据结构来进行理解:
ClassReader:它将字节数组或者class文件读入到内存当中,并形成内部表示的树结构。
ClassVisitor:ClassReader对象构建好之后,需要调用这个类的accept方法,这个方法接收ClassVisitor类的实例作为参数。框架在遍历树结构的不同节点时会调用ClassVisitor类上不同的visitor方法,遍历并调用ClassVisitor的算法骨架是由ClassReader确定的,用户可以做的是提供自己的ClassVisitor类的实现,从而实现对字节码的修改。ClassVisitor上的一些访问会产生一些子过程,比如visitMethod会产生MethodVisitor的调用,visitField会产生对FieldVisitor的调用,用户也可以对这些Visitor进行自己的实现,从而达到对这些子节点上的字节码访问的修改。
用户还可以提供多中不种操作的ClassVisitor实现,并以职责链的模式提供给ClassReader来使用。对于ClassReader,依然只需要accept一个ClassVisitor类,因为ClassVisitor同时也是职责链上的元素类型。
ClassWriter:生成字节码的工具类,也是ClassVisitor接口的实现,它一般作为职责链上的最后一个节点被执行。即其前面的ClassVisitor链条上的每一个visitor都是致力与对原始字节码做某一项修改,ClassWriter这个visitor的操作则是老实得把每一个节点的字节码输出到指定的文件当中。
ClassAdapter:它实现了ClassVisitor定义的所有接口,并接受一个ClassVisitor对象作为构建一个新的ClassAdapter实例的参数。所以它的实现一般是职责链上的一个节点。
ClassVisitor关注四个方法(常用):visit()修改类的信息,visitField()修改字段信息,visitMethod()修改方法信息,MethodVisitor修改方法体信息。
ASM的大体实现流程是这样的:ClassReader读取字节码,生成用于表示该字节码的内部表示的树;组装一系列ClassVisitor的链条,这些visitor对应与visitor模式中的具体访问者类,一般都完成了字节码进行一项不同的字节码改写的操作,而整个职责链则完成了对字节码的一系列不同的字节码修改工作;然后调用ClassReader的accept方法,传入ClassVisitor的对象,也是一个职责链条,ClassReader使用这个链条上的每个Visitor对已加载进内存的字节码的树结构上的每个节点进行访问,在链条的末端,调用ClassWriter这个visitor进行修改后的字节码的输出工作。
ASM是一个强大的框架,利用它我们可以做到:
1、获得class文件的详细信息,包括类名、父类名、接口、成员名、方法名、方法参数名、局部变量名、元数据等
2、对class文件进行动态修改,如增加、删除、修改类方法、在某个方法中添加指令等
但如果我们想要使用上述api修改类的字节码或者生成新的类,手搓代码是比较困难的,我们很难能记得住这些api的使用顺序,也很容易漏掉某些调用。所以如果哦我们能直接看到我们写出的类生成的字节码,那么我们就可以通过类似调参的方式来修改字节码信息,达到我们的目的。
针对以上问题,有两种常见方式可以解决。第一,ASM可以使用插件打印字节码(看了别人介绍感觉不是很好用,可以参考ASM简介-CSDN博客,所以用了第二种方法);第二,可以使用ASM自带的print方法打印,代码如下:
public static void print(String className) {
int par = ClassReader.SKIP_DEBUG | ClassReader.EXPAND_FRAMES;
Printer printer = new ASMifier();
PrintWriter printWriter = new PrintWriter(System.out,true);
TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, printer, printWriter);
try {
new ClassReader(className).accept(traceClassVisitor, par);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
例如:使用print方法打印如下代码的字节码
package org.example;
public class ASMTest {
public ASMTest(){
}
public void test(){
long start = System.currentTimeMillis();
System.out.println("Hello ASM");
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
public static void main(String[] args) throws Exception {
// 生成class二进制
String className = "org.example.ASMTest";
PrintASM.print(className);
}
使用print打印后结果如下
package asm.org.example;
import java.util.*;
import jdk.internal.org.objectweb.asm.*;
public class ASMTestDump implements Opcodes {
public static byte[] dump () throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(52, ACC_PUBLIC + ACC_SUPER, "org/example/ASMTest", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 1);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello ASM");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 3);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(LLOAD, 3);
mv.visitVarInsn(LLOAD, 1);
mv.visitInsn(LSUB);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(5, 5);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
我们可以直接复制粘贴打印出来的字节码进行修改,例如:
import jdk.internal.org.objectweb.asm.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
public class Test {
public static void main(String[] args) throws Exception {
// 生成class二进制
final byte[] bytes = dump();
System.out.println(new ClassReader(bytes).getClassName());
// String className = "org.example.ASMTest";
// PrintASM.print(className);
}
public static byte[] dump () throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(52, ACC_PUBLIC + ACC_SUPER, "org/example/ASMTest", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "hello", "()V", null, null);
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 1);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello ASM");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 3);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(LLOAD, 3);
mv.visitVarInsn(LLOAD, 1);
mv.visitInsn(LSUB);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(5, 5);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
通过调用dump()方法获取生成的class二进制字节码信息,可以在dump()方法中修改字节码数据,调用方式如上所示。
我们可以通过mv = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null); 中根据“test”方法名,创建一个新的MethodVisitor,在mv.visitCode()方法中,可以通过mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");和mv.visitLdcInsn("Hello aaaaaaa");两行代码分别找到system.out对象和输出字符串,再通过mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);调用println方法,打印出修改的内容。
例如:
我们通过直接修改system.out方法的字节码,修改原本应该打印的内容System.out.println("Hello ASM");变为"Hello aaaaaaa"
代码如下:
package org.example;
import jdk.internal.org.objectweb.asm.*;
import java.io.FileOutputStream;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
public class Test {
public static void main(String[] args) throws Exception {
// 生成class二进制
final byte[] bytes = dump2();
FileOutputStream fileOutputStream = new FileOutputStream("aaaaa.class");
fileOutputStream.write(bytes);
fileOutputStream.close();
// System.out.println(new ClassReader(bytes).getClassName());
// String className = "org.example.ASMTest";
// PrintASM.print(className);
}
public static byte[] dump1 () throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(52, ACC_PUBLIC + ACC_SUPER, "org/example/aaa", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 1);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello ASM");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 3);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(LLOAD, 3);
mv.visitVarInsn(LLOAD, 1);
mv.visitInsn(LSUB);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(5, 5);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
public static byte[] dump2 () throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(52, ACC_PUBLIC + ACC_SUPER, "org/example/ASMTest", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello aaaaaaa");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
package org.example;
public class ASMTest {
public ASMTest(){
}
public void test(){
// long start = System.currentTimeMillis();
System.out.println("Hello ASM");
// long end = System.currentTimeMillis();
// System.out.println(end-start);
}
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.example;
public class ASMTest {
public ASMTest() {
}
public void test() {
System.out.println("Hello aaaaaaa");
}
}
可以看到最终的class文件已经从原本的打印内容变成了我们需要修改的内容。
三、另外手搓了两个demo示例
示例1:我愿称之为无中生有,直接凭空新增一个类。
// 使用ASM直接无中生有,生成一个类
public static void main(String[] args) {
// 1. 创建一个ClassWriter,用于生成字节码
ClassWriter cw = new ClassWriter(0);
// 2. 创建一个类
String className = "newClass";
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);
// 3. 添加一个无参的构造方法
MethodVisitor constructor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
constructor.visitCode();
constructor.visitVarInsn(Opcodes.ALOAD, 0);
constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructor.visitInsn(Opcodes.RETURN);
constructor.visitMaxs(1, 1);
constructor.visitEnd();
// 4. 添加一个名为"test"的方法
MethodVisitor method = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test", "()V", null, null);
method.visitCode();
method.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
method.visitLdcInsn("creat new class");
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
method.visitInsn(Opcodes.RETURN);
method.visitMaxs(2, 0);
method.visitEnd();
// 5. 字节码生成完毕,获取最终的字节数组
byte[] byteCode = cw.toByteArray();
// 6. 使用自定义的类加载器加载并执行生成的字节码
MyClassLoader2 cl = new MyClassLoader2();
Class<?> newClass= cl.defineClass(className, byteCode);
try {
newClass.getDeclaredMethod("test").invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}
static class MyClassLoader2 extends ClassLoader {
public Class<?> defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
示例2:使用druid连接数据库,获取连接池中的连接信息、性能信息等。(这部分使用方法我直接在代码中注释,感觉会更方便查看)
package org.example;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class ASMTest {
public ASMTest(){
}
public static void test() throws SQLException {
// 创建Druid数据源
DruidDataSource dataSource = new DruidDataSource();
// 设置数据库连接信息
dataSource.setUrl("jdbc:mysql://168.64.33.159:8880/paas_repeater?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true");
dataSource.setUsername("repeater_dml");
dataSource.setPassword("iB8ESTV8UT04_");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
// 配置连接池参数
dataSource.setInitialSize(5); // 初始化连接数
dataSource.setMaxActive(20); // 最大连接数
dataSource.setMinIdle(5); // 最小空闲连接数
Connection connection = dataSource.getConnection();
// 启用监控统计功能
dataSource.setFilters("stat");
String catalog = connection.getCatalog();
int holdability = connection.getHoldability();
int activeCount = dataSource.getActiveCount();
int activePeak = dataSource.getActivePeak();
System.out.println("没变过的");
// 关闭连接
connection.close();
}
}
import java.io.FileOutputStream;
import java.lang.reflect.Method;
import org.objectweb.asm.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
public class DruidConfig extends ClassVisitor{
public DruidConfig(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
public static void main(String[] args) throws Exception {
// 1.修改原本的字节码
final byte[] bytes = dump();
byte[] transform = transform(bytes);
// 2. 加载字节码
MyClassLoader cl = new MyClassLoader();
Class<?> newClass = cl.defineClass("org.example.ASMTest", transform);
// 3. 使用类
Object instance = newClass.getDeclaredConstructor().newInstance();
Method test = newClass.getDeclaredMethod("test2");
test.invoke(instance);
// 4. 也可以用下面的方法把这个类写到指定位置,生成一个class文件
// FileOutputStream fileOutputStream = new FileOutputStream("aaaaa.class");
// fileOutputStream.write(bytes);
// fileOutputStream.close();
}
public static byte[] dump () throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
/**
* 在ASM中,通常会创建一个ClassWriter(或其子类)的实例,然后使用其提供的各种visit方法来定义类的结构、字段、方法、指令等。
* 例如:
* visit(int version, int access, String name, String signature, String superName, String[] interfaces):用于开始访问一个类。
* visitField(int access, String name, String descriptor, Object value):用于访问一个字段。
* visitMethod(int access, String name, String descriptor, String signature, String[] exceptions):用于开始访问一个方法。
* 在visitMethod之后,你会使用MethodVisitor(通常是由ClassWriter的visitMethod方法返回的)来定义方法体中的指令。
*/
cw.visit(52, ACC_PUBLIC + ACC_SUPER, "org/example/ASMTest", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
/**
* visitInsn:用于插入一个操作码(opcode)作为指令,该操作码对应于Java虚拟机(JVM)指令集中的一条指令。
* visitVarInsn:用于插入一个操作局部变量或参数的指令(如 ALOAD, ASTORE 等)。
* visitIntInsn:用于插入一个需要整数参数的指令(如 BIPUSH, SIPUSH 等)。
* visitTypeInsn:用于插入一个引用类型(如类、接口)的指令(如 NEW, CHECKCAST 等)。
* visitFieldInsn:用于插入一个字段访问或修改的指令(如 GETFIELD, PUTFIELD 等)。
* visitMethodInsn:用于插入一个方法调用的指令(如 INVOKEVIRTUAL, INVOKESTATIC 等)。
*/
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "test2", "()V", null, new String[] { "java/sql/SQLException" });
mv.visitCode();
mv.visitTypeInsn(NEW, "com/alibaba/druid/pool/DruidDataSource");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "com/alibaba/druid/pool/DruidDataSource", "<init>", "()V", false);
mv.visitVarInsn(ASTORE, 0);
mv.visitVarInsn(ALOAD, 0);
// 用于加载常量字符串或者其他类型常量
mv.visitLdcInsn("jdbc:mysql://168.64.33.159:8880/paas_repeater?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true");
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "setUrl", "(Ljava/lang/String;)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn("repeater_dml");
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "setUsername", "(Ljava/lang/String;)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn("iB8ESTV8UT04_");
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "setPassword", "(Ljava/lang/String;)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn("com.mysql.jdbc.Driver");
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "setDriverClassName", "(Ljava/lang/String;)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(ICONST_5);
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "setInitialSize", "(I)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitIntInsn(BIPUSH, 20);
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "setMaxActive", "(I)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(ICONST_5);
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "setMinIdle", "(I)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "getConnection", "()Lcom/alibaba/druid/pool/DruidPooledConnection;", false);
mv.visitVarInsn(ASTORE, 1);
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn("stat");
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "setFilters", "(Ljava/lang/String;)V", false);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEINTERFACE, "java/sql/Connection", "getCatalog", "()Ljava/lang/String;", true);
mv.visitVarInsn(ASTORE, 2);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEINTERFACE, "java/sql/Connection", "getHoldability", "()I", true);
mv.visitVarInsn(ISTORE, 3);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "getActiveCount", "()I", false);
mv.visitVarInsn(ISTORE, 4);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "com/alibaba/druid/pool/DruidDataSource", "getActivePeak", "()I", false);
mv.visitVarInsn(ISTORE, 5);
// 下面这一段是获取holdability的值,并且把它打印出来,并且在打印的内容里加上"ASM打印holdability数据:"
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn("ASM打印holdability数据:");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(ILOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
// 下面这一段是获取activeCount的值,并且把它打印出来,并且在打印的内容里加上"ASM打印activeCount数据:"
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn("ASM打印activeCount数据:");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(ILOAD, 4);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("我把他也改了");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEINTERFACE, "java/sql/Connection", "close", "()V", true);
mv.visitInsn(RETURN);
mv.visitMaxs(3, 6);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
/**
* 也可以通过重写visitMethod的方法,直接改变字节码中的内容。
* 例如我在原本的代码中写了 System.out.println("没变过的");
* 通过上面修改字节码的方法,把这个打印改成了 mv.visitLdcInsn("我把他也改了");
* 再使用下面的方法在这个打印出来的内容后面加上 "->你看变了吧"
*/
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
// 只处理你感兴趣的方法,这里假设我们处理所有方法
return new MethodVisitor(Opcodes.ASM7, mv) {
@Override
public void visitLdcInsn(Object cst) {
// 检查是否是要修改的字符串
if (cst instanceof String && ((String) cst).equals("我把他也改了")) {
// 修改字符串
super.visitLdcInsn(cst + "->你看变了吧");
}
else {
// 否则,按原样处理
super.visitLdcInsn(cst);
}
}
};
}
/**
* 使用这个ClassVisitor来转换一个类
*/
public static byte[] transform(byte[] classBytes) {
ClassWriter cw = new org.objectweb.asm.ClassWriter(0);
ClassReader cr = new org.objectweb.asm.ClassReader(classBytes);
cr.accept(new DruidConfig(Opcodes.ASM9, cw), 0);
return cw.toByteArray();
}
/**
* 自定义ClassLoader以支持加载字节数组形式的字节码
*/
static class MyClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
}
最终效果就是
更多用法等我探索明白了再来更新!
参考文献: