上一篇文章不停机不更新代码线上调试BUG的工具
教会了大家如果使用arthas是定位系统线上问题
这篇文章教大家关于arthas的原理
arthas其实也是利用是了java agent。
这个是jvm提供出来的接口,在类加载之前会触发一个方法,让大家自定义自己想要切入的业务;
如何写一个自己的java agent
1、新建一个springboot的工程,用来模拟自己的app应用,应用每5秒会调用一个方法
package com.eujian.arthaslearn.controller;
public class MyService {
public String send(){
System.out.println("send被调用了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "send";
}
}
github地址 https://github.com/hd-eujian/arthas-learn.git
码云地址 https://gitee.com/guoeryyj/arthas-learn.git
2、新建 agent工程,打包成jar包,去做class文件的aop
pom文件引入依赖
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<Premain-Class>com.eujian.agent.PreMainTraceAgent</Premain-Class>
<Agent-Class>com.eujian.agent.PreMainTraceAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
新建文件PreMainTraceAgent
package com.eujian.agent;
import java.lang.instrument.Instrumentation;
public class PreMainTraceAgent {
public static void agentmain (String agentArgs, Instrumentation inst) throws Exception {
System.out.println("agent begin agentArgs="+agentArgs);
MyClassFileTransformer myClassFileTransformer;
//如果入参是1就清除aop
if("1".equals(agentArgs)){
myClassFileTransformer = new MyClassFileTransformer(true);
inst.addTransformer(myClassFileTransformer,true);
}else {
myClassFileTransformer = new MyClassFileTransformer();
inst.addTransformer(myClassFileTransformer,true);
}
System.out.println("agent end");
Class[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class clazz : allLoadedClasses){
if(clazz.getName().contains("com.eujian.arthaslearn.controller.MyService")){
inst.retransformClasses(clazz);
System.out.println("重新加载"+clazz);
}
}
}
}
新建文件MyClassFileTransformer实现接口ClassFileTransformer
package com.eujian.agent;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class MyClassFileTransformer implements ClassFileTransformer {
private boolean isReLoad = false;
public MyClassFileTransformer() {
}
public MyClassFileTransformer(boolean isReLoad) {
this.isReLoad = isReLoad;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!className.equals("com/eujian/arthaslearn/controller/MyService")) {
return null;
}
try {
System.out.println("进入 isReLoad:"+isReLoad);
System.out.println("进入 className:"+className);
System.out.println("进入 loader:"+loader);
System.out.println("进入 classBeingRedefined:"+classBeingRedefined);
CtClass cl = null;
ClassPool classPool = ClassPool.getDefault();
cl = classPool.getCtClass("com.eujian.arthaslearn.controller.MyService");
System.out.println("cl.isFrozen()+"+cl.isFrozen());
if(isReLoad){
//重新加载本地class文件
cl.defrost();
return readStream(ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true);
}
CtMethod method = cl.getDeclaredMethod("send");
//插入你想要的代码
method.insertBefore("System.out.println(\"send-begin\");");
method.insertAfter("System.out.println(\"send-end\");");
byte[] transformed = cl.toBytecode();
cl.detach();
return transformed;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
private static byte[] readStream(InputStream inputStream, boolean close) throws IOException {
if(inputStream == null) {
throw new IOException("Class not found");
} else {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] data = new byte[4096];
int bytesRead;
while((bytesRead = inputStream.read(data, 0, data.length)) != -1) {
outputStream.write(data, 0, bytesRead);
}
outputStream.flush();
byte[] var5 = outputStream.toByteArray();
return var5;
} finally {
if(close) {
inputStream.close();
}
}
}
}
}
执行命令打包mvn clean assembly:assembly
github 地址:https://github.com/hd-eujian/agent.git
码云地址: https://gitee.com/guoeryyj/agent.git
新建一个main函数的工程,工程主要作用是把打包的jar包注入到运行的app中
代码如下
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.util.List;
public class Main {
public static void main(String[] args) throws Exception{
System.out.println("running JVM start ");
String jarUrl = "/xxx/agent/target/agent-1.0-SNAPSHOT-jar-with-dependencies.jar";
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for (VirtualMachineDescriptor vmd : list) {
if (vmd.displayName().contains("ArthasLearnApplication")) {
VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
//这个是加载jar包增强class文件
virtualMachine.loadAgent(jarUrl);
//这个是恢复class文件
// virtualMachine.loadAgent(jarUrl,"1");
virtualMachine.detach();
}
}
}
}
实操环节
1、先启动arthas-learn工程
2、agent工程打包
mvn clean assembly:assembly
3、执行main函数