上一篇文章不停機不更新代碼線上調(diào)試BUG的工具
教會了大家如果使用arthas是定位系統(tǒng)線上問題
這篇文章教大家關(guān)于arthas的原理
arthas其實也是利用是了java agent。
這個是jvm提供出來的接口能扒,在類加載之前會觸發(fā)一個方法嘀略,讓大家自定義自己想要切入的業(yè)務(wù);
如何寫一個自己的java agent
1天试、新建一個springboot的工程,用來模擬自己的app應(yīng)用,應(yīng)用每5秒會調(diào)用一個方法
package com.eujian.arthaslearn.controller;
public class MyService {
public String send(){
System.out.println("send被調(diào)用了");
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;
//如果入?yún)⑹?就清除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實現(xiàn)接口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("進(jìn)入 isReLoad:"+isReLoad);
System.out.println("進(jìn)入 className:"+className);
System.out.println("進(jìn)入 loader:"+loader);
System.out.println("進(jìn)入 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();
}
}
}
}
}
執(zhí)行命令打包mvn clean assembly:assembly
github 地址:https://github.com/hd-eujian/agent.git
碼云地址: https://gitee.com/guoeryyj/agent.git
新建一個main函數(shù)的工程,工程主要作用是把打包的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);
//這個是恢復(fù)class文件
// virtualMachine.loadAgent(jarUrl,"1");
virtualMachine.detach();
}
}
}
}
實操環(huán)節(jié)
1役耕、先啟動arthas-learn工程
2采转、agent工程打包
mvn clean assembly:assembly
3、執(zhí)行main函數(shù)