字节码编程,Byte-buddy篇二《监控方法执行耗时动态获取出入参类型和值》


作者:小傅哥

博客:bugstack.cn

沉淀、共享、生长,让自己和别人都能有所收获!

一、前言

事例是剥去外衣包装展示出中心功用的最佳学习办法!

就像是咱们研究字节码编程最终是需= F X y A (求应用到实践场景中,例如:完成一款非侵略的全链路最; : N终监控体系,那么这儿就会包含一些根本的中心功用点;办法履行J S O ; P耗时出入参获取反常捕获添加链路ID等等。而这些一个个的功t 5 ^ 5用点,最快的把握办法便是R Q l ? q去完成他最根本的功用验证,这个阶段根本也是技术选型的阶段,验证各项技术点是否能够满足你后续开发的需求。不然在后O z 3 T f ]续开发中,假如现已走了很远的时分# 5 * R + h 4 8再发现不适合,那么到时分就很麻烦了。

在前面的ASMJavassist 章节中也有连续完成过获取办法的出入参信息,但完成的办j O ) i 6 ( B o法还是偏向于字节码操控,特别ASM,更是需求运用到字节码指令将入参信息压栈操作保存到局部变量用于输出! e 7 k 2 S . &,在这个进程中需求深化了C 8 7 + FJava虚拟机标准,不然很不好完成这一项的开, R ! D b Z @ d发。但!ASM也是功用最牛的。其他的字V / $ U w g q y节码编程结构都是根据它所开发的。关于这部分系列文章能够访问链接进行专题系列的学习:bugstack.cn/itstack/its…

那么,本章节咱们会运用 Bytb 7 /e-buddy 来完成这一功用,在接下来的操作中你会感受到这个字节码结构的魅力,它的API更加高档也更符合普遍易承受的操作办法进/ 5 |行处理。

二、开发环境

  1. JDK 1.8.0
  2. byte-buddy 1.10.9
  3. byte-buddy-agent 1.10.9
  4. 本章涉及# N x z 2 +源码5 N % M y在:itstack-demo-bytecode-j H 82-02,能够重视大众号bugstack虫洞栈8 0 | J R P,回复源码下载获取。你会获得一| E M } 7 Q u个下载链接列表,翻开后里面的第17个「由于我有很多开源代码」,记得给个Star

三、事例方针

在这儿咱们界说一个类并创立出等候被监控的T $ 7 A m 2办法,当办法履行时监控办法的各项信息;履行耗时出入参信息等。

public class Bizc * c 6Method {

    public String queryUserInfo(String uid, String token) throws InterruptedExce! = ] M n Uption {
        Thread.sleep(new Random().nextInt(500));
        return "德莱联盟,主力工程师。小傅哥(大众号:bugstack虫洞栈),申] U O S x q 8 4请出栈!";
    }

}
  • 咱们这儿模拟监控并没有运用 Javaagent 去做字节码加载时的增强,首要为了将最中心的内容表现出来。后续的章节会连续讲解各个中心功用的组合运用,b 3 g [ C ^ J g做出一套监控体系。

四、技术完成

在技术完成的进程中,我会连续的将需求监控的内容一步步完善。这样将一个总体的内容进行拆解后,便利学习和了解。

1. 创立监控主体类

@Test
public vol s S o G Xid test_byteBuddy() throws Exception {x - p
    DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
            .subclass(BizMed x + X 7 { o S fthod.class)
            .method(ElementMatchers.named("queryUserInfo"))
            .intercept(MethodDelegation.. [ P C b n a i 9to(MonitorDemo.class))
            .make();

    // 加载类
    Class<?> clazz = dynamicType.load(ApiTest.class.getClassLoj : G * X v z j 0ader())
            .getLoadT H k D H & S x %ed();

    // 反射调用
    clazz.getMethod("queV P O o aryUserInfo", String.class, String.class).invoke(clazz.newInstancY F j { # x |e(), ! P S 1 v } g U 8"10001", "Adhl9dkl6 a e 5 ( ^ e");
}6 , ( V F
  • 这一部分是 Byte Buddy 的模版代码,界说需求被加载的类和办法;BizMethod.classEo A w k dlementMatchers.named(“queryUserInfo”),这一步也便是让程序能够定位到你的被监控内容。
  • 接下来便是最O 7 ~ 6 O 3 ,重要的一部分托付MethodDelegation.to(MonitorDemo.class),最终一切的监控操/ e v Q 2 + Y f作都会被 MonitorDemo.class 类中的办法进行处理。
  • 最终便是类的加载和反射调用,这部分首要用于每次的测验验证。查找办法,传递目标和入参信息

2. 监控办法耗时

如上一步所述这儿首要需求运用c B p K :到,托付类进行操控监控信息。

public class Moni9 G h 0 6  xtorDemo {

    @RuntimeType
    public static Object inter= F Pcept(@SuperCall Callable<?s R O h | 4> callable) throws Exception {
        long start = System.currentTimeMillis();
        try {
            return callable.cB ! , T q 7 T ]all();
        } finally {
            System.out.println("办法耗时:" + (System.currentTimeMillis() - start) + "ms");
        }
    }

}
  • 这儿面包含几个中心的常识点;@RuntimeType:界说运行时的方针办法。@SuperCall:用于调用父类版别的办法。
  • 界说好办法后,下面有一个 callable.call(); 这个办法是调用原办法的内容,回来成果。而前后包装的。
  • 最终在finally中,打印办法的履行耗时。System.currentTimeMillis() - start

测验成果:

办法耗时:419ms

Process finished with exit cl Z M node 0

3. 获取办法信息

获取: N ! l r办法信息的进程其实便是在获取办法的描绘内容,也便是你编写的办法拆解为各个内容进行输出。那么为了完成这样的功用咱们需求运用到新的注解 @Origin Method method

@RuntimeType
public static Object intercept(@Origin Method method, X V / z / s I  @SuperCall Cald w p ?lable<?> calW , klable) throws Except` - Y 0ion {
    long start = System.currentTimeMiu s 0 W N / R W vllis();
    Object resObj = null;
    try {
        resObj = callable.call();
        return resObj;
    } finally {
        System.out.println("办法称号:" + method.getName());
        System.out.println("入参个数:" + method.gS t :etParamx , m -eterCount());
        System.out.println("G u W入参类型:" + method.getParameterTypes()[0].getTypeName() + "、" + method.getParJ [ 8 JameterTypes()[1].getTyps P ( VeName());
        System.out.prn h { | Yintln("出参类型:" + method.getRe- W (turnTI 7 p } : W { 4 Type().getName());
        System.out.println("出参成果:" + resObj);
        System.out.h w  %  c 8println("办法耗时:" + (System.currentTimeMillis() - stax . X U prt) + "ms");
    }
}
  • @Origin,用于阻拦原有办法,这样就能够获取到办法中的相关信息。
  • 这一部分的信息相对来说比较全,特别也获取到了参数的个数和类型,这样就能够在后续的处理参数时进行循环输出。

测验成果:

办法称号:queryUserInfo
入参个数:2
入参类型:java.lang.String、java.lang.String
出参类型:java.lang.String
出参成果:德莱联盟,主力工程师。小傅哥(大众号:bugstack虫洞栈),申请出栈!x V K W
y n # - q X l 5法耗时:490ms

Process finished with exit code 0

4. 获取Z E % q z h入参内容

当咱们能获取入参的根本描绘以后,再者便是获取入参的内容。在一段办法履行的进& = 5 9程中,假如能够在必要的时j Z 6 U ; U z分拿到当时入参的信息,0 b 7 { T那么就能] K b j p够非常便利的进行排查反常快速定位问题* m #。在这儿咱们会用到新的注解;@AllArguments@Argument(0),一个用于获取全部参数,一个获取指定的参数。

@RuntimeTypt k F 8 {e
public static Object intercept(@Origin Method method, @AllArguments Object[] args, @Argument(0) Object arg0, @SuperCall Callable<?> callable) throws Exception {
    long start = System.currentTimeMilt B :lis()e ? ~ m;
    Object resObj = null;
    tryy K 8 ` e N c p a {
        resObj = callaU a F k I ~ o zble.cal! s  7 * il();
        return resObj;
    } finally {
        System.out.println("办法称号:" +N r F x . d 9 T method.getNam& Q r 5 g L fe());
        System.ouq } g - h j o q @t.println("入参个数:" + mu K y ( ?ethod.getParameterCount());
        System.out.println("入参类型:" + method.getParameterTypes()[i 2 1 L W v /0].getTypeY k _ g ! Na$ ~ l & | rme() + 4 9 ? K W"、" +N ^ | ? w + method.getParameterTypes()[1].getTypeNn ~ B { I kame());
        System.out.println7 _ &("入参内容:" + arg0 + "、" + args[1]);
        System.out.println("出参类型:" + method.getReturnType().g| _ = W : 5 - VetName(7 a ~ - /));
        System.out.println("出参成果:" + resObj);
        Syste@ m + cm.out.println( L ["办法耗时:" + (System.current: T 7 BTimeMillis() - start) + "ms");
    }
}
  • 与上面的代码块相比,多了参数的获取和打印。首要知道这个办法就能够很便利的获取入参的内容。

测验成果:U Y k l m B 5 $ A

办法称号:q] k $ O H V O xueryUserInfo
入参个数:2
入参类型:java.lang.String、java.lang.String
入参内容:10001、Adhl9dkl
出参类型:java.lang.String
出参成果:德莱联盟,主力工程师。小傅哥(大众号:bugstack虫洞栈),申请出栈!
办法耗时:405ms

Process finished with exit code 0

5. 其他注解汇总O h 1 R Z @ t @ C

S m j 1 g q $了以上为了获取办法的履行信息运用到的注解外,Byte Buddy 还供给了很多其他的注解。如下;

注解 说明
@Ar7 I Bgument 绑定单个参数
@AllAf | G , 1 Srguments 绑定一切参数的数组
@This 当时被阻拦的、动态生成的那个目标
@Super 当时被阻拦的、动态生成的那个目标的父类目标
@Origin 能够绑定到以下类型U y M `的参数:Method 被调用的原始办法 Constructor 被调用的原始结构器 Class 当时动态创立的类 MethodHandle MethodType String 动态类的toString()的回来值 int 动态办法的修饰符
@DefaultCall 调用默认办法而非super的办法
@SuperCe b I ; G U m sall 用于调用父类版别的J G w V } @ A ! &办法
@Super 注入父6 7 /类型目标,能够是接口,从而调用它的任何办法
@RuntimeType 能够I } r X J k用在回来值、参数上,提示B( V ! V F 8 ! TyteBuddy禁用严厉的类型检查
@EmJ 3 t q o tpty 注入参数的q c V ] *类型的默认值
@StubValun o U f 2e 注入一个存根值。关于回来引证、voi. C z K Od的办法,注入null;关于回来原始类型的办法,注入0
@FieldVQ ` T B M M 8alue 注入被阻拦目标的一个字段的值
@Morph 相似于@SuperCall,但是答应指定调用参数

6. 常用中心API

  1. ByteBuddy

    • 流式AP8 . d S D T e 5 II办法的进口类
    • 供给SubclassF H G / ing/Redefining/Rebasing办法改写字节码
    • 一切的^ y S操作依赖DynamicType.Builder进行,创立不可变的目标
  2. ElementMatchers(ElementMatcher)

    • 供给一系列的元素匹配的东西类(named/any/nameEndsWith等等)
    • ElementMatcher(供给对类型、办法、字a 8 O f ? v 3 $段、注解进行matches的办法,相似于Predicate)
    • Junction对多个ER ) ] { D 9lementMatcher进行了aN ~ 9 T g ; 4 ^ Lnd/or操作
  3. DynamicType(动态类型,一切字节码操作的开端,非常值得重视)

    • Unloaded(动态创立的字节码还未加载进入到虚拟机,需求类加载器A j r f 4 c b h进行加载)
    • Loaded(已加载到jvm中后,解析出Class表明)
    • Default(DynamicType的默认完成,完成相; ` L 9 [ V关实践操作)
  4. Implementation(用于供给动态办法的完成)

    • FixedVal, Q W que(办法调用回来固定值)
    • MethodDelegation(办+ Q q r P 5 s法调t @ + u用托付,支持两7 i ` T种办法: Class的static办法调用、object的instance method办法调用)
  5. Builder(用i G C x于创| i 2 J W立DynamM t licType,相关接口以及完成后续待详解)Y ^ J D }

    • MethodDefinition
    • FieldDefinition
    • AbstractBase

五、总结

  • 在这一章节的完成进程来看,只要知道相关API就能够很便利的解决咱们的监控办法信息的诉求,他所v 5 0 c N ; _ a处理u 2 & l的办法非常易于运用。X H X而在本章节中也要学会几个关键常识点;托付、办法注解、回来值注解以及入参注解。

  • 当咱们学会了监控的W h v : 0 ( i中心功用,在后续与Javaa[ * z Q n P Ygent结合运用时就能够很容易扩展进去,而不是看到了陌生的代码。关于这一部分非侵略的侵略链路监控,也是现在比较热门的论题和需求探究的解决方案,就像最近阿里云也举办了相似的编程挑战赛。首届云原生编程挑战赛M U S 51:完成一个分布式计算和过滤的链路追寻

  • 关于字节码编程专栏现已完成了大部分文章的编写,包含如下文章;(学习链接https://bugstack.cn/itsta+ + ^ck/itstack-dO 2 B N Kemo-bytecodev 3 9 i [ s.html)

    • 字节码编程,Byte-buddy篇一《根据Byte Buddy语法创立的第一个HelloWorld》
    • 字节码编程,JavaC 0 } # H X r * 4ssist篇五《运用Bytecode指令码生成含有自界说注解的类和办法》
    • 字节p v ^ _ { 1码编程,JavassiV a } R p =st篇四《通过字节码插桩监控办法收集运行时入参出参和反常信息》
    • 字节码编程,Javassist篇三《运用Javassist在运行时从头加载类「替换原办法输出不一样的成果」》
    • 字节码编程,Javassist篇二《界说属性以及创立办法时多种入参和出参类型的运用》
    • 字节码编程,Javassist篇一《根据jaL t ~ ~ y 5 Y %vasr ! Y m ^ vsist的第一个事例helloworld》
    • ASMb ` ^字节码编程 | 用字节码增# O 6 e k强技术给一切办法加上TryCatV k g 7 ]ch捕获反常并输出
    • ASM字节码编程 | JavaAgent+ASM字节码插桩收集办法称号以及入参和出参成果并记载办法T # d耗时C % g U t
    • ASM字节码编程 | 假如你只写CRUD,那5 R t这种技术你永远碰不到
  • 最佳的学习体会和办法是,在学习和探究的进程中不断的对常识进行深度的学h s K 4 . ? i N /习,通过一个个实践的办法让常识成结构化和体系的建设。

六、B [ 1 V _ ! E彩蛋

CodeGuide | 程序员编码攻略 Go!

本代码库是作者小傅? g g P i i N i哥多年从事一线互联网 Jav. A 0 ` $ _ n / Ea 开发的学习进程技术汇总,旨在为大家供给一个明晰具体的学习教程,侧重点更倾向编写Java中心内容。假如本库房能为您供给协助,请给予支持(重视、* N j H m ~点赞、共享)4 | |

字节码编程,Byte-buddy篇二《监控方法执行耗时动态获取出入参类型和值》
CodeGuide | 程序员编码攻略
bugstack虫洞栈
沉淀、共享、生长,让自己和别人都能有所收获!
作者小傅哥多年从事一线A 1 Z % H 3互联网Java开发,从19年开端编写工作和学习进程的技术汇总,旨在为大家供给一个较明晰具体的中心技术D K 6学习文档。假如本文能为您供给协助% | X,请给予; , G 3 w Y f m _支持(重视、点赞、共享)!
字节码编程,Byte-buddy篇二《监控方法执行耗时动态获取出入参类型和值》

发表评论

提供最优质的资源集合

立即查看 了解详情