javaagent
我们先来了解一下javaagent,-javaagent是java命令的一个参数,后加一个独立运行的jar包,jar包的内容是什么呢?我们先来参阅java.lang.instrument包
打开包java.lang.instrument
里面有几个接口和类,我们来看一下Instrumentation接口
Instrumentation
这里说到当JVM以指示代理类的方式(也就是后带-javaagent参数的方式)启动时,会将Instrumentation传递给premain方法。
Instrumentation的实现类sun.instrument.InstrumentationImpl,loadClassAndStartAgent方法会优先加载带Instrumentation参数的方法
public static void premain(String arg, Instrumentation instrumentation)
public static void premain(String arg)
那就是说-javaagent 后带的jar包需要有premain方法,并提供参数接受Instrumentation实例。
打jar包
现在创建一个maven工程,创建类Javaagent,并提供premain方法
import java.lang.instrument.Instrumentation;
public class Javaagent {
public static void premain(String arg, Instrumentation instrumentation) {
System.out.println("获取到Instrumentation实例:" + instrumentation);
}
}
使用插件maven-assembly-plugin打包项目为一个jar包,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>javaagent</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Premain-Class>Javaagent</Premain-Class>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
打包生成javaagent-1.0-SNAPSHOT-jar-with-dependencies.jar
运行java程序并使用-javaagent参数
现在再创建一个项目工程test-agent,创建App类、UserService类
public class UserService {
public String queryUserByName(String name) {
return "User[ name: " + name + " ]";
}
}
public class App {
public static void main(String[] args) {
UserService userService = new UserService();
System.out.println(userService.queryUserByName("MinXie"));
}
}
App的main方法调用UserService的方法queryUserByName打印输出Name
添加VM参数并运行程序
可以看到在queryUserByName方法执行前会调用premain方法,并传递Instrumentation实例
ClassFileTransformer
再回过头来看一下Instrumentation接口,接口提供了addTransformer方法,这个方法用于添加类文件转换器ClassFileTransformer
通过实现ClassFileTransformer可以实现类文件的转换,现在通过ASM框架插入日志打印的操作。
CusClassFileTransformer
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class CusClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if ("UserService".equals(className)) {
System.out.println("transform..." + className);
//修改类
return modifyClass(classfileBuffer);
}
return null;
}
private byte[] modifyClass(byte[] classfileBuffer) {
ClassReader classReader = new ClassReader(classfileBuffer);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
//覆写ClassVisitor的方法
CusClassAdapter classAdapter = new CusClassAdapter(classReader.getClassName(), classWriter);
classReader.accept(classAdapter, 0);
return classWriter.toByteArray();
}
}
CusClassAdapter
过滤不需要处理的方法
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class CusClassAdapter extends ClassVisitor {
private final String className;
public CusClassAdapter(String className, ClassWriter classWriter) {
super(Opcodes.ASM9, classWriter);
this.className = className;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if ((access & Opcodes.ACC_PRIVATE) != 0) {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
if ((access & Opcodes.ACC_ABSTRACT) != 0
|| (access & Opcodes.ACC_NATIVE) != 0
|| (access & Opcodes.ACC_BRIDGE) != 0
|| (access & Opcodes.ACC_SYNTHETIC) != 0) {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
//<init>和<clinit>不需要处理
if ("<init>".equals(name) || "<clinit>".equals(name)) {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
return new CusMethodAdapter(className, access, name, descriptor, methodVisitor);
}
}
CusMethodAdapter
处理方法调用日志打印指令插入
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.List;
public class CusMethodAdapter extends MethodVisitor {
private boolean isStaticMethod = false;
private final String className;
private final String methodName;
private final String descriptor;
private final String[] paramDescriptors;
//异常处理程序范围的开始
private final Label start = new Label();
//异常处理程序范围的结
private final Label end = new Label();
//异常处理程序代码的开头
private final Label handler = new Label();
public CusMethodAdapter(String className, int access, String methodName, String descriptor, MethodVisitor methodVisitor) {
super(Opcodes.ASM9, methodVisitor);
if ((access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC) {
this.isStaticMethod = true;
}
this.className = className;
this.methodName = methodName;
this.descriptor = descriptor;
this.paramDescriptors = getParamDescriptors(descriptor);
}
@Override
public void visitCode() {
super.visitCode();
//调用日志记录方法调用前信息
this.visitLdcInsn(this.className);
this.visitLdcInsn(this.methodName);
this.visitLdcInsn(this.descriptor);
if (paramDescriptors == null) {
this.visitInsn(Opcodes.ACONST_NULL);
} else {
switch (paramDescriptors.length) {
case 1:
mv.visitInsn(Opcodes.ICONST_1);
break;
case 2:
mv.visitInsn(Opcodes.ICONST_2);
break;
case 3:
mv.visitInsn(Opcodes.ICONST_3);
break;
default:
mv.visitVarInsn(Opcodes.BIPUSH, paramDescriptors.length);
}
}
//创建参数大小的Object数组
mv.visitTypeInsn(Opcodes.ANEWARRAY, Type.getInternalName(Object.class));
System.out.println(Type.getDescriptor(Object.class));
int localIndex = isStaticMethod ? 0 : 1;
for (int i = 0; i < paramDescriptors.length; i++) {
mv.visitInsn(Opcodes.DUP);
switch (i) {
case 0:
mv.visitInsn(Opcodes.ICONST_0);
break;
case 1:
mv.visitInsn(Opcodes.ICONST_1);
break;
case 2:
mv.visitInsn(Opcodes.ICONST_2);
break;
case 3:
mv.visitInsn(Opcodes.ICONST_3);
break;
default:
mv.visitVarInsn(Opcodes.BIPUSH, i);
}
String type = paramDescriptors[i];
//
if ("B".equals(type)) {
mv.visitVarInsn(Opcodes.ILOAD, localIndex++);
mv.visitMethodInsn(Opcodes.ACC_STATIC, Type.getInternalName(Byte.class), "valueOf",
"(B)Ljava/lang/Byte;", false);
} else if ("Z".equals(type)) {
mv.visitVarInsn(Opcodes.ILOAD, localIndex++);
mv.visitMethodInsn(Opcodes.ACC_STATIC, Type.getInternalName(Boolean.class), "valueOf",
"(Z)Ljava/lang/Boolean;", false);
} else if ("S".equals(type)) {
mv.visitVarInsn(Opcodes.ILOAD, localIndex++);
mv.visitMethodInsn(Opcodes.ACC_STATIC, Type.getInternalName(Short.class), "valueOf",
"(S)Ljava/lang/Short;", false);
} else if ("C".equals(type)) {
mv.visitVarInsn(Opcodes.ILOAD, localIndex++);
mv.visitMethodInsn(Opcodes.ACC_STATIC, Type.getInternalName(Character.class), "valueOf",
"(C)Ljava/lang/Character;", false);
} else if ("I".equals(type)) {
mv.visitVarInsn(Opcodes.ILOAD, localIndex++);
mv.visitMethodInsn(Opcodes.ACC_STATIC, Type.getInternalName(Integer.class), "valueOf",
"(I)Ljava/lang/Integer;", false);
} else if ("J".equals(type)) {
mv.visitVarInsn(Opcodes.LLOAD, localIndex++);
mv.visitMethodInsn(Opcodes.ACC_STATIC, Type.getInternalName(Long.class), "valueOf",
"(J)Ljava/lang/Long;", false);
} else if ("F".equals(type)) {
mv.visitVarInsn(Opcodes.FLOAD, localIndex++);
mv.visitMethodInsn(Opcodes.ACC_STATIC, Type.getInternalName(Float.class), "valueOf",
"(F)Ljava/lang/Float;", false);
} else if ("D".equals(type)) {
mv.visitVarInsn(Opcodes.DALOAD, localIndex++);
mv.visitMethodInsn(Opcodes.ACC_STATIC, Type.getInternalName(Double.class), "valueOf",
"(D)Ljava/lang/Double;", false);
} else {
mv.visitVarInsn(Opcodes.ALOAD, localIndex++);
}
mv.visitInsn(Opcodes.AASTORE);
}
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Log.class), "before",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V",
false);
// 异常处理程序范围的开始
this.visitLabel(start);
}
private int nextLocalIndex = 0;
@Override
public void visitInsn(int opcode) {
int localIndex = nextLocalIndex == 0 ? 10 : nextLocalIndex;
//方法返回前打印日志
switch (opcode) {
case Opcodes.RETURN:
this.visitLdcInsn(this.className);
this.visitLdcInsn(this.methodName);
this.visitLdcInsn(this.descriptor);
this.visitInsn(Opcodes.ACONST_NULL);
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Log.class), "after",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V",
false);
break;
case Opcodes.IRETURN:
//复制一份返回值,存在局部变量表后一个位置
this.visitInsn(Opcodes.DUP);
this.visitVarInsn(Opcodes.ISTORE, localIndex);
this.visitLdcInsn(this.className);
this.visitLdcInsn(this.methodName);
this.visitLdcInsn(this.descriptor);
this.visitVarInsn(Opcodes.ILOAD, localIndex);
//包装为对象类型
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Integer.class),
"valueOf", "(I)Ljava/lang/Integer;", false);
//调用埋点返回
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Log.class), "after",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V",
false);
break;
case Opcodes.FRETURN:
//复制一份返回值,存在局部变量表后一个位置
this.visitInsn(Opcodes.DUP);
this.visitVarInsn(Opcodes.FSTORE, localIndex);
this.visitLdcInsn(this.className);
this.visitLdcInsn(this.methodName);
this.visitLdcInsn(this.descriptor);
this.visitVarInsn(Opcodes.FLOAD, localIndex);
//包装为对象类型
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Float.class),
"valueOf", "(F)Ljava/lang/Float;", false);
// //调用埋点返回
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Log.class), "after",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V",
false);
break;
case Opcodes.LRETURN:
//复制一份返回值,存在局部变量表后一个位置
this.visitInsn(Opcodes.DUP2);
this.visitVarInsn(Opcodes.LSTORE, localIndex);
this.visitLdcInsn(this.className);
this.visitLdcInsn(this.methodName);
this.visitLdcInsn(this.descriptor);
this.visitVarInsn(Opcodes.LLOAD, localIndex);
//包装为对象类型
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Long.class),
"valueOf", "(J)Ljava/lang/Long;", false);
// //调用埋点返回
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Log.class), "after",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V",
false);
break;
case Opcodes.DRETURN:
//复制一份返回值,存在局部变量表后一个位置
this.visitInsn(Opcodes.DUP2);
this.visitVarInsn(Opcodes.DSTORE, localIndex);
this.visitLdcInsn(this.className);
this.visitLdcInsn(this.methodName);
this.visitLdcInsn(this.descriptor);
this.visitVarInsn(Opcodes.DLOAD, localIndex);
//包装为对象类型
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Double.class),
"valueOf", "(D)Ljava/lang/Double;", false);
// //调用埋点返回
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Log.class), "after",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V",
false);
break;
case Opcodes.ARETURN:
//复制一份返回值,存在局部变量表后一个位置
this.visitInsn(Opcodes.DUP);
this.visitVarInsn(Opcodes.ASTORE, localIndex);
this.visitLdcInsn(this.className);
this.visitLdcInsn(this.methodName);
this.visitLdcInsn(this.descriptor);
this.visitVarInsn(Opcodes.ALOAD, localIndex);
//调用埋点返回
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Log.class), "after",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V",
false);
break;
}
super.visitInsn(opcode);
}
@Override
public void visitVarInsn(int opcode, int var) {
super.visitVarInsn(opcode, var);
if (opcode == Opcodes.ILOAD
|| opcode == Opcodes.FLOAD
|| opcode == Opcodes.ALOAD
|| opcode == Opcodes.ISTORE
|| opcode == Opcodes.FSTORE
|| opcode == Opcodes.ASTORE) {
if (var > nextLocalIndex) {
nextLocalIndex = var + 1;
}
} else if (opcode == Opcodes.LLOAD
|| opcode == Opcodes.DLOAD
|| opcode == Opcodes.LSTORE
|| opcode == Opcodes.DSTORE) {
if (var + 1 > nextLocalIndex) {
nextLocalIndex = var + 2;
}
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
this.visitLabel(end);
this.visitLabel(handler);
this.visitVarInsn(Opcodes.ASTORE, maxLocals + 1);
this.visitLdcInsn(this.className);
this.visitLdcInsn(this.methodName);
this.visitLdcInsn(this.descriptor);
this.visitVarInsn(Opcodes.ALOAD, maxLocals + 1);
//调用埋点返回
this.visitMethodInsn(Opcodes.INVOKESTATIC,
Type.getInternalName(Log.class), "error",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V",
false);
this.visitVarInsn(Opcodes.ALOAD, maxLocals + 1);
this.visitInsn(Opcodes.ATHROW);
this.visitTryCatchBlock(start, end, handler, Type.getInternalName(Throwable.class));
super.visitMaxs(maxStack, maxLocals);
}
public String[] getParamDescriptors(String descriptor) {
List<String> paramDescriptors = new ArrayList<>();
String newString = descriptor.substring(descriptor.indexOf("(") + 1, descriptor.indexOf(")"));
for (String s : newString.split(";")) {
paramDescriptors.add(s + ";");
}
if (paramDescriptors.isEmpty()) return null;
return paramDescriptors.toArray(new String[0]);
}
}
添加类处理
再在Javaagent类中调用addTransformer方法添加类文件处理器
import java.lang.instrument.Instrumentation;
public class Javaagent {
public static void premain(String arg, Instrumentation instrumentation) {
System.out.println("获取到Instrumentation实例:" + instrumentation);
instrumentation.addTransformer(new CusClassFileTransformer());
}
}
效果
最后,调用App看一下类改写之后的效果