色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

手把手教你實現一個Java Agent(JVM啟動時的處理流程)

jf_ro2CN3Fa ? 來源:稀土掘金 ? 2023-11-27 10:53 ? 次閱讀

故事的小黃花

團隊中有同事在做性能優化相關的工作,因為公司基礎設施不足,同事在代碼中寫了大量的代碼統計某個方法的耗時,大概的代碼形式就是

@Override
publicvoidmethod(Reqreq){
StopWatchstopWatch=newStopWatch();
stopWatch.start("某某方法-耗時統計");
method()
stopWatch.stop();
log.info("查詢耗時分布:{}",stopWatch.prettyPrint());
}

這樣的代碼非常多,侵入性很大,聯想到之前學習的Java Agent技術,可以無侵入式地解決這類問題,所以做了一個很小很小的demo

Instrumentation

在了解Agent之前需要先看看Instrumentation

JDK從1.5版本開始引入了java.lang.instrument包,該包提供了一些工具幫助開發人員實現字節碼增強,Instrumentation接口的常用方法如下

publicinterfaceInstrumentation{
/**
*注冊Class文件轉換器,轉換器用于改變Class文件二進制流的數據
*
*@paramtransformer注冊的轉換器
*@paramcanRetransform設置是否允許重新轉換
*/
voidaddTransformer(ClassFileTransformertransformer,booleancanRetransform);

/**
*移除一個轉換器
*
*@paramtransformer需要移除的轉換器
*/
booleanremoveTransformer(ClassFileTransformertransformer);

/**
*在類加載之后,重新轉換類,如果重新轉換的方法有活躍的棧幀,那些活躍的棧幀繼續運行未轉換前的方法
*
*@param重新轉換的類數組
*/
voidretransformClasses(Class...classes)throwsUnmodifiableClassException;

/**
*當前JVM配置是否支持重新轉換
*/
booleanisRetransformClassesSupported();

/**
*獲取所有已加載的類
*/
@SuppressWarnings("rawtypes")
Class[]getAllLoadedClasses();
}
publicinterfaceClassFileTransformer{
//className參數表示當前加載類的類名,classfileBuffer參數是待加載類文件的字節數組
//調用addTransformer注冊ClassFileTransformer以后,后續所有JVM加載類都會被它的transform方法攔截
//這個方法接收原類文件的字節數組,在這個方法中做類文件改寫,最后返回轉換過的字節數組,由JVM加載這個修改過的類文件
//如果transform方法返回null,表示不對此類做處理,如果返回值不為null,JVM會用返回的字節數組替換原來類的字節數組
byte[]transform(ClassLoaderloader,
StringclassName,
ClassclassBeingRedefined,
ProtectionDomainprotectionDomain,
byte[]classfileBuffer)
throwsIllegalClassFormatException;
}

Instrumentation有兩種使用方式

在JVM啟動的時候添加一個Agent jar包

JVM運行以后在任意時刻通過Attach API遠程加載Agent的jar包

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

項目地址:https://github.com/YunaiV/yudao-cloud

視頻教程:https://doc.iocoder.cn/video/

Agent

使用Java Agent需要借助一個方法,該方法的方法簽名如下

publicstaticvoidpremain(StringagentArgs,Instrumentationinstrumentation){

}

從字面上理解,就是運行在main()函數之前的類。在Java虛擬機啟動時,在執行main()函數之前,會先運行指定類的premain()方法,在premain()方法中對class文件進行修改,它有兩個入參

agentArgs:啟動參數,在JVM啟動時指定

instrumentation:上文所將的Instrumentation的實例,我們可以在方法中調用上文所講的方法,注冊對應的Class轉換器,對Class文件進行修改

如下圖,借助Instrumentation,JVM啟動時的處理流程是這樣的:JVM會執行指定類的premain()方法,在premain()中可以調用Instrumentation對象的addTransformer方法注冊ClassFileTransformer。當JVM加載類時會將類文件的字節數組傳遞給ClassFileTransformer的transform方法,在transform方法中對Class文件進行解析和修改,之后JVM就會加載轉換后的Class文件

55de5326-8c08-11ee-939d-92fbcf53809c.jpg

JVM啟動時的處理流程

那我們需要做的就是寫一個轉換Class文件的ClassFileTransformer,下面用一個計算函數耗時的小例子看看Java Agent是怎么使用的

publicclassMyClassFileTransformerimplementsClassFileTransformer{
@Override
publicbyte[]transform(ClassLoaderloader,StringclassName,ClassclassBeingRedefined,ProtectionDomainprotectionDomain,byte[]classfileBuffer){
if("com/example/aop/agent/MyTest".equals(className)){
//使用ASM框架進行字節碼轉換
ClassReadercr=newClassReader(classfileBuffer);
ClassWritercw=newClassWriter(cr,ClassWriter.COMPUTE_FRAMES);
ClassVisitorcv=newTimeStatisticsVisitor(Opcodes.ASM7,cw);
cr.accept(cv,ClassReader.SKIP_FRAMES|ClassReader.SKIP_DEBUG);
returncw.toByteArray();
}
returnclassfileBuffer;

}
}
publicclassTimeStatisticsVisitorextendsClassVisitor{

publicTimeStatisticsVisitor(intapi,ClassVisitorclassVisitor){
super(Opcodes.ASM7,classVisitor);
}

@Override
publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdescriptor,Stringsignature,String[]exceptions){
MethodVisitormv=cv.visitMethod(access,name,descriptor,signature,exceptions);
if(name.equals("")){
returnmv;
}
returnnewTimeStatisticsAdapter(api,mv,access,name,descriptor);
}
}
publicclassTimeStatisticsAdapterextendsAdviceAdapter{

protectedTimeStatisticsAdapter(intapi,MethodVisitormethodVisitor,intaccess,Stringname,Stringdescriptor){
super(api,methodVisitor,access,name,descriptor);
}

@Override
protectedvoidonMethodEnter(){
//進入函數時調用TimeStatistics的靜態方法start
super.visitMethodInsn(Opcodes.INVOKESTATIC,"com/example/aop/agent/TimeStatistics","start","()V",false);
super.onMethodEnter();
}

@Override
protectedvoidonMethodExit(intopcode){
//退出函數時調用TimeStatistics的靜態方法end
super.onMethodExit(opcode);
super.visitMethodInsn(Opcodes.INVOKESTATIC,"com/example/aop/agent/TimeStatistics","end","()V",false);
}
}

publicclassTimeStatistics{
publicstaticThreadLocalt=newThreadLocal<>();

publicstaticvoidstart(){
t.set(System.currentTimeMillis());
}
publicstaticvoidend(){
longtime=System.currentTimeMillis()-t.get();
System.out.println(Thread.currentThread().getStackTrace()[2]+"spend:"+time);
}
}
publicclassAgentMain{
//premain()函數中注冊MyClassFileTransformer轉換器
publicstaticvoidpremain(StringagentArgs,Instrumentationinstrumentation){
System.out.println("premain方法");
instrumentation.addTransformer(newMyClassFileTransformer(),true);
}
}



org.apache.maven.plugins
maven-assembly-plugin
3.1.1


 
jar-with-dependencies



//指定premain()的所在方法
com.example.aop.agent.AgentMain
com.example.aop.agent.AgentMain
true
true





package

single





org.apache.maven.plugins
maven-compiler-plugin
3.1

${maven.compiler.source}
${maven.compiler.target}




使用命令行執行下面的測試類

java-javaagent:/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jarcom.example.aop.agent.MyTest
publicclassMyTest{
publicstaticvoidmain(String[]args)throwsInterruptedException{
Thread.sleep(3000);
}
}

計算出了某個方法的耗時

55ebec5c-8c08-11ee-939d-92fbcf53809c.jpg

計算出某個方法的耗時

Attach

在上面的例子中,我們只能在JVM啟動時指定一個Agent,這種方式局限在main()方法執行前,如果我們想在項目啟動后隨時隨地地修改Class文件,要怎么辦呢?這個時候需要借助Java Agent的另外一個方法,該方法的簽名如下

publicstaticvoidagentmain(StringagentArgs,Instrumentationinst){

}

agentmain()的參數與premain()有著同樣的含義,但是agentmain()是在Java Agent被Attach到Java虛擬機上時執行的,當Java Agent被attach到Java虛擬機上,Java程序的main()函數一般已經啟動,并且程序很可能已經運行了相當長的時間,此時通過Instrumentation.retransformClasses()方法,可以動態轉換Class文件并使之生效,下面用一個小例子演示一下這個功能

下面的類啟動后,會不斷打印出100這個數字,我們通過Attach功能使之打印出50這個數字

publicclassPrintNumTest{

publicstaticvoidmain(String[]args)throwsInterruptedException{
while(true){
System.out.println(getNum());
Thread.sleep(3000);
}
}
privatestaticintgetNum(){
return100;
}
}

依然是定義一個ClassFileTransformer,使用ASM框架修改getNum()方法

publicclassPrintNumTransformerimplementsClassFileTransformer{
@Override
publicbyte[]transform(ClassLoaderloader,StringclassName,ClassclassBeingRedefined,ProtectionDomainprotectionDomain,byte[]classfileBuffer)throwsIllegalClassFormatException{
if("com/example/aop/agent/PrintNumTest".equals(className)){
System.out.println("asm");
ClassReadercr=newClassReader(classfileBuffer);
ClassWritercw=newClassWriter(cr,ClassWriter.COMPUTE_FRAMES);
ClassVisitorcv=newTransformPrintNumVisitor(Opcodes.ASM7,cw);
cr.accept(cv,ClassReader.SKIP_FRAMES|ClassReader.SKIP_DEBUG);
returncw.toByteArray();
}
returnclassfileBuffer;
}
}
publicclassTransformPrintNumVisitorextendsClassVisitor{
publicTransformPrintNumVisitor(intapi,ClassVisitorclassVisitor){
super(Opcodes.ASM7,classVisitor);
}
@Override
publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdescriptor,Stringsignature,String[]exceptions){
MethodVisitormv=cv.visitMethod(access,name,descriptor,signature,exceptions);
if(name.equals("getNum")){
returnnewTransformPrintNumAdapter(api,mv,access,name,descriptor);
}
returnmv;
}

}

publicclassTransformPrintNumAdapterextendsAdviceAdapter{

protectedTransformPrintNumAdapter(intapi,MethodVisitormethodVisitor,intaccess,Stringname,Stringdescriptor){
super(api,methodVisitor,access,name,descriptor);
}

@Override
protectedvoidonMethodEnter(){
super.visitIntInsn(BIPUSH,50);
super.visitInsn(IRETURN);
}
}
publicclassPrintNumAgent{

publicstaticvoidagentmain(StringagentArgs,Instrumentationinst)throwsUnmodifiableClassException{
System.out.println("agentmain");
inst.addTransformer(newPrintNumTransformer(),true);

Class[]allLoadedClasses=inst.getAllLoadedClasses();
for(ClassallLoadedClass:allLoadedClasses){
if(allLoadedClass.getSimpleName().equals("PrintNumTest")){
System.out.println("Reloading:"+allLoadedClass.getName());
inst.retransformClasses(allLoadedClass);
break;
}
}
}
}



org.apache.maven.plugins
maven-assembly-plugin
3.1.1


 
jar-with-dependencies



//指定agentmain所在的類
com.example.aop.agent.PrintNumAgent
com.example.aop.agent.PrintNumAgent
true
true





package

single





org.apache.maven.plugins
maven-compiler-plugin
3.1

${maven.compiler.source}
${maven.compiler.target}




因為是跨進程通信,Attach的發起端是一個獨立的java程序,這個java程序會調用VirtualMachine.attach方法開始合目標JVM進行跨進程通信

publicclassMyAttachMain{

publicstaticvoidmain(String[]args)throwsIOException,AttachNotSupportedException,AgentLoadException,AgentInitializationException{
VirtualMachinevirtualMachine=VirtualMachine.attach(args[0]);
try{
virtualMachine.loadAgent("/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jar");
}finally{
virtualMachine.detach();
}
}
}

使用jps查詢到PrintNumTest的進程id,再用下面的命令執行MyAttachMain類

java-cp/Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/lib/tools.jar:/Users/zhangxiaobin/IdeaProjects/aop-demo/target/aop-0.0.1-SNAPSHOT-jar-with-dependencies.jarcom.example.aop.agent.MyAttachMain49987

可以清楚地看到打印的數字變成了50

5611553c-8c08-11ee-939d-92fbcf53809c.jpg

效果

Arthas

以上是我寫的小demo,有很多不足之處,看看大佬是怎么寫的,arthas的trace命令可以統計方法耗時,如下圖

56236cd6-8c08-11ee-939d-92fbcf53809c.jpg

Arthas

搭建調試環境

Arthas debug需要借助IDEA的遠程debug功能,可以參考 https://github.com/alibaba/arthas/issues/222

先寫一個可以循環執行的Demo

publicclassArthasTest{
publicstaticvoidmain(String[]args)throwsInterruptedException{
inti=0;
while(true){
Thread.sleep(2000);
print(i++);
}
}
publicstaticvoidprint(Integercontent){
System.out.println("Mainprint:"+content);
}
}

命令行執行改demo

java-Xdebug-Xrunjdwp:transport=dt_socket,server=y,address=8000com.example.aop.agent.ArthasTest

在Arthas源碼的項目中設置遠程debug

564fa490-8c08-11ee-939d-92fbcf53809c.jpg

在Arthas源碼的項目中設置遠程debug

566441b6-8c08-11ee-939d-92fbcf53809c.jpg

在Arthas源碼的項目中設置遠程debug

在這個方法com.taobao.arthas.agent334.AgentBootstrap#main任意位置打上斷點,切換到剛剛設置的遠程debug模式,啟動項目

56845488-8c08-11ee-939d-92fbcf53809c.jpg

遠程debug模式

可以看到剛剛處于Listening的ArthasTest開始執行,啟動arthas-boot.jar,就可以看到斷點跳進Arthas源碼的項目中

569d445c-8c08-11ee-939d-92fbcf53809c.jpg

跳進Arthas源碼的項目中

bytekit

在看trace命令之前需要一點前置知識,使用ASM進行字節碼增強,代碼邏輯不好修改,理解困難,所以bytekit基于ASM提供了一套簡潔的API,讓開發人員可以比較輕松地完成字節碼增強,我們先來看一個簡單的demo,來自https://github.com/alibaba/bytekit

publicclassSampleInterceptor{
@AtEnter(inline=false,suppress=RuntimeException.class,suppressHandler=PrintExceptionSuppressHandler.class)
publicstaticvoidatEnter(@Binding.ThisObjectobject,
@Binding.ClassObjectclazz,
@Binding.ArgsObject[]args,
@Binding.MethodNameStringmethodName,
@Binding.MethodDescStringmethodDesc){
System.out.println("atEnter,args[0]:"+args[0]);
}

@AtExit(inline=true)
publicstaticvoidatExit(@Binding.ReturnObjectreturnObject){
System.out.println("atExit,returnObject:"+returnObject);
}

@AtExceptionExit(inline=true,onException=RuntimeException.class)
publicstaticvoidatExceptionExit(@Binding.ThrowableRuntimeExceptionex,
@Binding.Field(name="exceptionCount")intexceptionCount){
System.out.println("atExceptionExit,ex:"+ex.getMessage()+",fieldexceptionCount:"+exceptionCount);
}
}

上文說過,bytekit的宗旨是提供簡介的API讓開發可以輕松地完成字節碼增強,從注解名我們就可以知道@AtEnter是在方法進入時插入,@AtExit是在方法退出時插入,@AtExceptionExit時在發生異常退出時插入

inline = true表示方法中的代碼直接插入增強方法中,inline = false表示是調用這個方法,有點難理解,我們等下看反編譯后的代碼

配置了 suppress = RuntimeException.class 和 suppressHandler = PrintExceptionSuppressHandler.class,說明插入的代碼會被 try/catch 包圍

@AtExceptionExit在原方法體范圍try-catch指定異常進行處理

這是我們要進行增強的方法

publicclassSample{
privateintexceptionCount=0;
publicStringhello(Stringstr,booleanexception){
if(exception){
exceptionCount++;
thrownewRuntimeException("testexception,str:"+str);
}
return"hello"+str;
}
}
publicclassSampleMain{
publicstaticvoidmain(String[]args)throwsException{
//解析定義的Interceptor類和相關的注解
DefaultInterceptorClassParserinterceptorClassParser=newDefaultInterceptorClassParser();
Listprocessors=interceptorClassParser.parse(SampleInterceptor.class);
//加載字節碼
ClassNodeclassNode=AsmUtils.loadClass(Sample.class);
//對加載到的字節碼做增強處理
for(MethodNodemethodNode:classNode.methods){
if(methodNode.name.equals("hello")){
MethodProcessormethodProcessor=newMethodProcessor(classNode,methodNode);
for(InterceptorProcessorinterceptor:processors){
interceptor.process(methodProcessor);
}
}
}
//獲取增強后的字節碼
byte[]bytes=AsmUtils.toBytes(classNode);
//查看反編譯結果
System.out.println(Decompiler.decompile(bytes));
//修改Sample
AgentUtils.reTransform(Sample.class,bytes);
//執行sample的方法
try{
Samplesample=newSample();
sample.hello("3",false);
sample.hello("4",true);
}catch(Exceptione){
e.printStackTrace();
}
}
}

這是Sample反編譯后的結果,代碼量劇增

publicclassSample{
privateintexceptionCount=0;

/*
*WARNING-voiddeclaration
*/
publicStringhello(Stringstring,booleanbl){
try{
Stringstring2;
voidstr;
voidexception;
try{
//@AtEnter直接調用,inline為false的效果
SampleInterceptor.atEnter((Object)this,Sample.class,(Object[])newObject[]{string,newBoolean(bl)},(String)"hello",(String)"(Ljava/lang/String;Z)Ljava/lang/String;");
}
catch(RuntimeExceptionruntimeException){
Classclazz=Sample.class;
RuntimeExceptionruntimeException2=runtimeException;
System.out.println("exceptionhandler:"+clazz);
runtimeException2.printStackTrace();
}
if(exception!=false){
++this.exceptionCount;
thrownewRuntimeException("testexception,str:"+(String)str);
}
Stringstring3=string2="hello"+(String)str;
//@AtExit代碼直接插入
System.out.println("atExit,returnObject:"+string3);
returnstring2;
}
catch(RuntimeExceptionruntimeException){
intn=this.exceptionCount;
RuntimeExceptionruntimeException3=runtimeException;
//@AtExceptionExit代碼直接插入
System.out.println("atExceptionExit,ex:"+runtimeException3.getMessage()+",fieldexceptionCount:"+n);
throwruntimeException;
}
}
}

有了這個前置知識,我們來看看trace命令

trace

56bb8232-8c08-11ee-939d-92fbcf53809c.jpg

trace

Arthas命令很多,如果是exit、logout、quit、jobs、fg、bg、kill等簡單的命令,就會直接執行,如果是trace這種復雜的命令,會專門用一個類寫處理的邏輯,如上圖,根據名字就可以猜到這個類是處理什么命令的,這么多類的組織形式是模版模式,入口在com.taobao.arthas.core.shell.command.AnnotatedCommand#process,

publicabstractclassAnnotatedCommand{
publicabstractvoidprocess(CommandProcessprocess);
}
publicclassTraceCommandextendsEnhancerCommand{
}
publicabstractclassEnhancerCommandextendsAnnotatedCommand{
@Override
publicvoidprocess(finalCommandProcessprocess){
//ctrl-Csupport
process.interruptHandler(newCommandInterruptHandler(process));
//qexitsupport
process.stdinHandler(newQExitHandler(process));
//starttoenhance
enhance(process);
}
}

有一些命令都有字節碼增強的邏輯,這些邏輯共同封裝在了EnhancerCommand這個類中,TraceCommand繼承了EnhancerCommand,當trace命令執行的時候,增強的邏輯在EnhancerCommand,我們只看核心代碼

com.taobao.arthas.core.command.monitor200.EnhancerCommand#enhance
com.taobao.arthas.core.advisor.Enhancer#enhance(java.lang.instrument.Instrumentation)
publicsynchronizedEnhancerAffectenhance(finalInstrumentationinst)throwsUnmodifiableClassException{
......
try{
//很明顯,這里添加了一個文件轉換器,注意,此處的轉換器為本類
ArthasBootstrap.getInstance().getTransformerManager().addTransformer(this,isTracing);
......
}catch(Throwablee){
logger.error("Enhancererror,matchingClasses:{}",matchingClasses,e);
affect.setThrowable(e);
}
returnaffect;
}

根據方法名就可以在本類搜索到,具體代碼如下

@Override
publicbyte[]transform(finalClassLoaderinClassLoader,StringclassName,ClassclassBeingRedefined,
ProtectionDomainprotectionDomain,byte[]classfileBuffer)throwsIllegalClassFormatException{
try{
//檢查classloader能否加載到SpyAPI,如果不能,則放棄增強
try{
if(inClassLoader!=null){
inClassLoader.loadClass(SpyAPI.class.getName());
}
}catch(Throwablee){
logger.error("theclassloadercannotloadSpyAPI,ignoreit.classloader:{},className:{}",
inClassLoader.getClass().getName(),className,e);
returnnull;
}
//這里要再次過濾一次,為啥?因為在transform的過程中,有可能還會再誕生新的類
//所以需要將之前需要轉換的類集合傳遞下來,再次進行判斷
if(matchingClasses!=null&&!matchingClasses.contains(classBeingRedefined)){
returnnull;
}
//ClassNode中有各種屬性,對應Class文件結構
//keeporiginclassreaderforbytecodeoptimizations,avoidingJVMmetaspaceOOM.
ClassNodeclassNode=newClassNode(Opcodes.ASM9);
ClassReaderclassReader=AsmUtils.toClassNode(classfileBuffer,classNode);
//removeJSRhttps://github.com/alibaba/arthas/issues/1304
classNode=AsmUtils.removeJSRInstructions(classNode);
//重要代碼,生成增強字節碼的攔截器
DefaultInterceptorClassParserdefaultInterceptorClassParser=newDefaultInterceptorClassParser();
finalListinterceptorProcessors=newArrayList();
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor1.class));
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor2.class));
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor3.class));
if(this.isTracing){
//根據配置判斷trace命令是否要跳過計算Java類庫的代碼的耗時
if(!this.skipJDKTrace){
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor1.class));
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor2.class));
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor3.class));
}else{
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor1.class));
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor2.class));
interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor3.class));
}
}

ListmatchedMethods=newArrayList();
for(MethodNodemethodNode:classNode.methods){
if(!isIgnore(methodNode,methodNameMatcher)){
matchedMethods.add(methodNode);
}
}
//https://github.com/alibaba/arthas/issues/1690
if(AsmUtils.isEnhancerByCGLIB(className)){
for(MethodNodemethodNode:matchedMethods){
if(AsmUtils.isConstructor(methodNode)){
AsmUtils.fixConstructorExceptionTable(methodNode);
}
}
}
.......
for(MethodNodemethodNode:matchedMethods){
if(AsmUtils.isNative(methodNode)){
logger.info("ignorenativemethod:{}",
AsmUtils.methodDeclaration(Type.getObjectType(classNode.name),methodNode));
continue;
}
//先查找是否有atBeforeInvoke函數,如果有,則說明已經有trace了,則直接不再嘗試增強,直接插入listener
if(AsmUtils.containsMethodInsnNode(methodNode,Type.getInternalName(SpyAPI.class),"atBeforeInvoke")){
for(AbstractInsnNodeinsnNode=methodNode.instructions.getFirst();insnNode!=null;insnNode=insnNode
.getNext()){
if(insnNodeinstanceofMethodInsnNode){
finalMethodInsnNodemethodInsnNode=(MethodInsnNode)insnNode;
if(this.skipJDKTrace){
if(methodInsnNode.owner.startsWith("java/")){
continue;
}
}
//原始類型的box類型相關的都跳過
if(AsmOpUtils.isBoxType(Type.getObjectType(methodInsnNode.owner))){
continue;
}
AdviceListenerManager.registerTraceAdviceListener(inClassLoader,className,
methodInsnNode.owner,methodInsnNode.name,methodInsnNode.desc,listener);
}
}
}else{
//重點代碼,增強動作就是在這里完成的
MethodProcessormethodProcessor=newMethodProcessor(classNode,methodNode,groupLocationFilter);
for(InterceptorProcessorinterceptor:interceptorProcessors){
try{
Listlocations=interceptor.process(methodProcessor);
for(Locationlocation:locations){
if(locationinstanceofMethodInsnNodeWare){
MethodInsnNodeWaremethodInsnNodeWare=(MethodInsnNodeWare)location;
MethodInsnNodemethodInsnNode=methodInsnNodeWare.methodInsnNode();
AdviceListenerManager.registerTraceAdviceListener(inClassLoader,className,
methodInsnNode.owner,methodInsnNode.name,methodInsnNode.desc,listener);
}
}
}catch(Throwablee){
logger.error("enhancererror,class:{},method:{},interceptor:{}",classNode.name,methodNode.name,interceptor.getClass().getName(),e);
}
}
}

//enter/exist總是要插入listener
AdviceListenerManager.registerAdviceListener(inClassLoader,className,methodNode.name,methodNode.desc,
listener);
affect.addMethodAndCount(inClassLoader,className,methodNode.name,methodNode.desc);
}

//https://github.com/alibaba/arthas/issues/1223,V1_5的majorversion是49
if(AsmUtils.getMajorVersion(classNode.version)

這段代碼很長,其實主要邏輯就兩個

解析Interceptor Class的@AtXxx,@Binding等注解,生成InterceptorProcessor對象集合

遍歷InterceptorProcessor集合,修改原方法的字節碼

整體的流程如下圖

56c73410-8c08-11ee-939d-92fbcf53809c.jpg

整體的流程如圖

那這些攔截器長什么樣子呢?我們隨便找一個例子來看看

publicstaticclassSpyInterceptor1{
@AtEnter(inline=true)
publicstaticvoidatEnter(@Binding.ThisObjecttarget,@Binding.ClassClassclazz,
@Binding.MethodInfoStringmethodInfo,@Binding.ArgsObject[]args){
SpyAPI.atEnter(clazz,methodInfo,target,args);
}
}

看到這里,就很熟悉了,跟上面bytekit的例子很像,是在方法進入時插入的,當然,這里只是淺講一下trace的原理,bytekit背后的原理,需要更底層的知識儲備,我還需要繼續學習

審核編輯:黃飛

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • JAVA
    +關注

    關注

    19

    文章

    2974

    瀏覽量

    104977
  • API
    API
    +關注

    關注

    2

    文章

    1510

    瀏覽量

    62293
  • 代碼
    +關注

    關注

    30

    文章

    4823

    瀏覽量

    68900
  • JVM
    JVM
    +關注

    關注

    0

    文章

    158

    瀏覽量

    12252
  • 虛擬機
    +關注

    關注

    1

    文章

    931

    瀏覽量

    28360

原文標題:手把手教你實現一個Java Agent

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    圖文教程:手把手教你焊接貼片元件

    圖文教程:手把手教你焊接貼片元件,首先來張全部焊接點的PCB圖
    發表于 04-01 11:03 ?3.6w次閱讀
    圖文教程:<b class='flag-5'>手把手</b><b class='flag-5'>教你</b>焊接貼片元件

    手把手教你構建完整的工程

    手把手教你構建完整的工程
    發表于 08-03 09:54 ?33次下載
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b>構建<b class='flag-5'>一</b><b class='flag-5'>個</b>完整的工程

    手把手教你寫批處理-批處理的介紹

    手把手教你寫批處理-批處理的介紹
    發表于 10-25 15:02 ?69次下載

    美女手把手教你如何裝機(中)

    美女手把手教你如何裝機(中) 再來是硬碟的部份,這款機殼還不錯,可以旋轉支架~
    發表于 01-27 11:14 ?1475次閱讀

    美女手把手教你如何裝機(下)

    美女手把手教你如何裝機(下) 接著下來就是今天的重頭戲,開核蘿!~
    發表于 01-27 11:16 ?2928次閱讀

    手把手教你學習FPGA—LED篇

    電子專業單片機相關知識學習教材資料——手把手教你學習FPGA—LED篇
    發表于 08-08 17:19 ?0次下載

    手把手教你學電子書制作

    手把手教你學電子書制作,可以自己DIY電子書
    發表于 09-13 11:26 ?0次下載

    手把手教你安裝Quartus II

    本章手把手把教你如何安裝 Quartus II 軟件 ,并將它激活 。此外 還有USB -Blaster下載器的驅動安裝步驟 。
    發表于 09-18 14:55 ?9次下載

    手把手教你在家搭建監控系統

    手把手教你在家搭建監控系統
    發表于 01-17 19:47 ?25次下載

    手把手教你做電子時鐘---前言

    手把手教你做彩鈴電子時鐘
    發表于 11-14 16:53 ?11次下載

    手把手教你如何開始DSP編程

    手把手教你如何開始DSP編程。
    發表于 04-09 11:54 ?12次下載
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b>如何開始DSP編程

    手把手教你學LabVIEW視覺設計

    手把手教你學LabVIEW視覺設計手把手教你學LabVIEW視覺設計手把手教你學LabVIEW視
    發表于 03-06 01:41 ?3168次閱讀

    手把手教你開關電源PCB排板

    手把手教你開關電源PCB排板(新型電源技術)-分享下開關電源PCB排板的基本要點及分析,以及例子講解。絕對的手把手
    發表于 09-18 12:27 ?58次下載
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b>開關電源PCB排板

    手把手教你pcb壓合的整個流程,小白也能玩轉電路板制作

    手把手教你pcb壓合的整個流程,小白也能玩轉電路板制作
    的頭像 發表于 09-18 10:43 ?4024次閱讀

    手把手教你學FPGA仿真

    電子發燒友網站提供《手把手教你學FPGA仿真.pdf》資料免費下載
    發表于 10-19 09:17 ?2次下載
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b>學FPGA仿真
    主站蜘蛛池模板: 肉欲横流(NP高H) | 国产偷啪自怕网 | 午夜福利在线观看6080 | 精品国产自在天天线2019 | 姐姐不~不可以动漫在线观看 | 国产成人免费全部网站 | 久久精品影院永久网址 | 儿媳妇完整版视频播放免费观看 | 网址在线观看你懂我意思吧免费的 | 校花在公车上被内射好舒服 | 国语自产二区高清国语自产拍 | 在线观看免费av网 | 精品久久伦理中文字幕 | 最新在线黄色网址 | 国产高清-国产av | 成人午夜剧场 | 在线国内自拍精品视频 | 伊人AV一区二区三区夜色撩人 | 在线中文字幕亚洲日韩 | 78m成人亚洲| 97人摸人人澡人人人超一碰 | 色呦呦导航 | 狠狠色丁香婷婷久久综合五月 | 99青草青草久热精品视频 | 久久99精品国产免费观看 | 强奷漂亮女老板在线播放 | 一本道久久综合久久88 | 小莹的性荡生活40章 | www.青青草原| 国产亚洲精品久久无亚洲 | 色欲AV无码乱码精品国产 | 我解开了岳的乳第一个女人 | 日韩爽爽影院在线播放 | 国产女人视频免费观看 | 亚洲蜜芽在线观看精品一区 | 国产成人精品一区二区三区视频 | 国产在线观看免费观看 | yellow日本动漫免费观看 | 免费 高清 中文在线观看 | 亚洲欧美成人在线 | 伦 乱真实故事 |