故事的小黄花
团队中有同事在做性能优化相关的工作,因为公司基础设施不足,同事在代码中写了大量的代码统计某个方法的耗时,大概的代码形式就是
@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, Class>classBeingRedefined, 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文件
JVM启动时的处理流程
那我们需要做的就是写一个转换Class文件的ClassFileTransformer,下面用一个计算函数耗时的小例子看看Java Agent是怎么使用的
publicclassMyClassFileTransformerimplementsClassFileTransformer{ @Override publicbyte[]transform(ClassLoaderloader,StringclassName,Class>classBeingRedefined,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{ publicstaticThreadLocal t=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.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); } }
计算出了某个方法的耗时
计算出某个方法的耗时
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,Class>classBeingRedefined,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.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
效果
Arthas
以上是我写的小demo,有很多不足之处,看看大佬是怎么写的,arthas的trace命令可以统计方法耗时,如下图
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
在Arthas源码的项目中设置远程debug
在Arthas源码的项目中设置远程debug
在这个方法com.taobao.arthas.agent334.AgentBootstrap#main任意位置打上断点,切换到刚刚设置的远程debug模式,启动项目
远程debug模式
可以看到刚刚处于Listening的ArthasTest开始执行,启动arthas-boot.jar,就可以看到断点跳进Arthas源码的项目中
跳进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
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,Class>classBeingRedefined, 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)); } } List matchedMethods=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{ List locations=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)< 49) { classNode.version = AsmUtils.setMajorVersion(classNode.version, 49); } byte[] enhanceClassByteArray = AsmUtils.toBytes(classNode, inClassLoader, classReader); // 增强成功,记录类 classBytesCache.put(classBeingRedefined, new Object()); // dump the class dumpClassIfNecessary(className, enhanceClassByteArray, affect); // 成功计数 affect.cCnt(1); return enhanceClassByteArray; } catch (Throwable t) { logger.warn("transform loader[{}]:class[{}] failed.", inClassLoader, className, t); affect.setThrowable(t); } return null; }
这段代码很长,其实主要逻辑就两个
解析Interceptor Class的@AtXxx,@Binding等注解,生成InterceptorProcessor对象集合
遍历InterceptorProcessor集合,修改原方法的字节码
整体的流程如下图
整体的流程如图
那这些拦截器长什么样子呢?我们随便找一个例子来看看
publicstaticclassSpyInterceptor1{ @AtEnter(inline=true) publicstaticvoidatEnter(@Binding.ThisObjecttarget,@Binding.ClassClass>clazz, @Binding.MethodInfoStringmethodInfo,@Binding.ArgsObject[]args){ SpyAPI.atEnter(clazz,methodInfo,target,args); } }
看到这里,就很熟悉了,跟上面bytekit的例子很像,是在方法进入时插入的,当然,这里只是浅讲一下trace的原理,bytekit背后的原理,需要更底层的知识储备,我还需要继续学习
审核编辑:黄飞
-
JAVA
+关注
关注
19文章
2966浏览量
104701 -
API
+关注
关注
2文章
1499浏览量
61961 -
代码
+关注
关注
30文章
4779浏览量
68521 -
JVM
+关注
关注
0文章
158浏览量
12220 -
虚拟机
+关注
关注
1文章
914浏览量
28160
原文标题:手把手教你实现一个Java Agent
文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论