我们知道agent技术可以在不入侵项目源代码的情况下来在项目初始化之前执行一些操作,而ASM技术可以直接修改字节码,两个结合起来就可以实现很多有意思的功能,比如监控所有方法的执行时间,代码如下。
1、代理类AgentTest
public class AgentTest {
/**
* 在主程序运行之前执行
* @param agentArgs
* @param isnt
*/
public static void premain(String agentArgs,Instrumentation inst) {
// System.out.println("premain start");
// System.out.println(agentArgs);
inst.addTransformer(new AopAgentTransformer());
}
/**
* 在主程序运行之后执行
* @param args
* @param inst
*/
/* public static void agentmain(String args,Instrumentation inst) {
System.out.println("loadagent after main run.args="+args);
Class<?>[] classes = inst.getAllLoadedClasses();
for (Class<?> cls : classes) {
System.out.println(cls.getName());
}
System.out.println("agent run completely");
}*/
}
这里用的是在程序运行之前执行,因为我们要修改字节码,那么肯定要在主程序运行之前执行,在premain方法中,可以获得一个Instrumentation对象,我们可以向其中加入一个ClassFileTransformer对象,Transformer对象的实现如下所示:
2、AopAgentTransformer
public class AopAgentTransformer implements ClassFileTransformer{
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("Transforming类" + className+"的字节码开始");
/*
TODO
修改字节码,并返回修改后的字节码
*/
byte[] transformed = null;
try {
ClassReader cr = new ClassReader(new java.io.ByteArrayInputStream(classfileBuffer));
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//ClassAdapter ca = new ClassAdapter(cw);
TimeCountAdpter ca = new TimeCountAdpter(cw);
cr.accept(ca, ClassReader.EXPAND_FRAMES);
transformed = cw.toByteArray();
}catch (RuntimeException re){
re.printStackTrace();
}catch (IOException e) {
System.err.println("can't transform "+ className+" "+e);
e.printStackTrace();
}
return transformed;
}
}
JVM会调用transform方法,这样就可以在JVM加载字节码文件时,获取到字节码并进行修改。
3、ASM修改字节码TimeCountAdpter
/**
* 监控所有方法的执行时间
* @author forever
*
*/
public class TimeCountAdpter extends ClassVisitor implements Opcodes {
private String owner;
private boolean isInterface;
private String filedName = "UDASMCN";
private int acc = Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL;
private boolean isPresent = false;
private String methodName;
public TimeCountAdpter(ClassVisitor classVisitor) {
super(ASM6, classVisitor);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
owner = name;
isInterface = (access & ACC_INTERFACE) != 0;
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if (name.equals(filedName)) {
isPresent = true;
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
if (!isInterface && mv != null && !name.equals("<init>") && !name.equals("<clinit>")) {
methodName = name;
AddTimerMethodAdapter at = new AddTimerMethodAdapter(mv);
at.aa = new AnalyzerAdapter(owner, access, name, descriptor, at);
at.lvs = new LocalVariablesSorter(access, descriptor, at.aa);
return at.lvs;
}
return mv;
}
public void visitEnd() {
if (!isInterface) {
FieldVisitor fv = cv.visitField(acc, filedName, "Ljava/lang/String;", null, owner);
if (fv != null) {
fv.visitEnd();
}
}
cv.visitEnd();
}
class AddTimerMethodAdapter extends MethodVisitor {
private int time;
private int maxStack;
public LocalVariablesSorter lvs;
public AnalyzerAdapter aa;
public AddTimerMethodAdapter(MethodVisitor methodVisitor) {
super(ASM6, methodVisitor);
}
@Override
public void visitCode() {
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
time = lvs.newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, time);
maxStack = 4;
}
@Override
public void visitInsn(int opcode) {
if (((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) && !isPresent) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
mv.visitVarInsn(LLOAD, time);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, time);
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.visitFieldInsn(GETSTATIC, owner, filedName, "Ljava/lang/String;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn(" " + methodName + ":");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(LLOAD, time);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)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);
maxStack = Math.max(aa.stack.size() + 4, maxStack);
}
mv.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(Math.max(maxStack, this.maxStack), maxLocals);
}
}
}
4、编译maven install
这里的项目主要参考的是我上一篇文章的agentTest:,但是因为pom.xml中加入了其他jar包,所以为了把外部的jar包也打包进去,pom.xml要改为如下:
<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>com.suibibk</groupId>
<artifactId>AgentTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.2.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>6.2.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>6.2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>
com.suibibk.AgentTest
</Premain-Class>
<!-- <Agent-Class>
com.suibibk.AgentTest
</Agent-Class> -->
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<!-- 执行package的phase -->
<phase>package</phase>
<!-- 为这个phase绑定goal -->
<goals>
<goal>shade</goal>
</goals>
<configuration>
<!-- 过滤掉以下文件,不打包 :解决包重复引用导致的打包错误-->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
5、测试方法
public class Test2 {
public static void main(String[] args) {
//System.out.println("main2");
test();
test2();
}
public static void test() {
System.out.println("我是Test2的test方法");
}
public static void test2() {
System.out.println("我是Test2的test2方法");
}
}
需要加上VM参数:
-javaagent:D:\Work\eclipse_workspace\AgentTest\target\AgentTest-0.0.1-SNAPSHOT.jar=haha
运行输出如下内容,单位为纳秒:
Transforming类sun/misc/URLClassPath$FileLoader$1的字节码开始
sun/misc/URLClassPath$FileLoader$1 getCodeSourceURL:1900
sun/misc/URLClassPath$FileLoader$1 getInputStream:142300
sun/misc/URLClassPath$FileLoader$1 getContentLength:124600
Transforming类com/suibibk/test/Test2的字节码开始
我是Test2的test方法
com/suibibk/test/Test2 test:83100
我是Test2的test2方法
com/suibibk/test/Test2 test2:44600
com/suibibk/test/Test2 main:314500
完成!
参考:
JavaAgent:在主程序运行之前和之后的代理程序
史上最通俗易懂的ASM教程
基于javaAgent和ASM字节码技术跟踪java程序调用链
JVM插码之五:Java agent+ASM实战—监控所有方法执行时间