不用 Jar 包的 Agent ?几行代码实现运行时加强


提起 JavaAgent,不少人都说几句,就像古龙武侠小说里的「孔雀翎」,威力很大,江湖上都是它的传说。但真的见识过的人并没几个。
javascript


JavaAgent 虽然说没这么神秘,但也一直给人曲高和寡的感受,除了一些中间件产品、大型的框架中使用外,在业务中一直不多出现。css


缘由可能有不少,一来是可能确实不须要,再者须要开发独立的 Agent Jar 文件,在 Jar 内对类的 transform 开发也并不容易。java


咱们知道,不管是启动时的 Java Agent,仍是运行时的动态 attach 到远程JVM, 都是为了拿到 Instrument,对 class 的字节码进行修改。这么底层的东西,固然使用起来让人不太容易下手。程序员

不过就像机器语言不方便,人们发明了汇编语言,又发现了高级语言。对于字节码的操做也相似,人们以为直接操做字节码有难度,并且须要深刻理解 JVM 规范,具体什么位置多少字节表明啥,这不是通常人喜欢的,因而 ASM 框架出现了;但仍是有规范的影子,不太「高级」,因而又出现了Javassist 这一类的「高级」工具。
web


咱们今天要说的这个工具,和 Javassist 相似,都提供了更高层的API,来操做class,对普通程序员更友好,除此以外呢?
typescript


就像今天人们购物、读书等,都更相信专业的平台、或者专家的推荐,像XX严选,XX读书会推荐。今天说的这个工具是Duke 的推荐,对,就是它, Java 的吉祥物,这个小胖子。今天的这个工具在 2015年被 Oracle 评选为「Duke's Choice award」。数据库




除了Duke,框架也获得了众多开发者的承认,每一年有七千多万次的下载。
swift


这个工具是:Bytebuddy。 tomcat

从名字你就看的出来,它立志要作字节码的好伙伴。因此在不少开源框架里也能看到它的身影。微信


既然已经有了很多的工具, byteBuddy能带来什么不同呢?


除了API 上的简洁易操做,官方本身也大字强调了运行时动态的「代码生成和字节码操做」,不须要再借助 Java 编译器。


来看看官网是怎么自我介绍的,后面再附上几个代码片断,就能很快 Get 到了。

官网:https://bytebuddy.net/

Byte Buddy is a code generation and manipulation library for creating and modifying Java classes during the runtime of a Java application and without the help of a compiler. Other than the code generation utilities that ship with the Java Class Library, Byte Buddy allows the creation of arbitrary classes and is not limited to implementing interfaces for the creation of runtime proxies. Furthermore, Byte Buddy offers a convenient API for changing classes either manually, using a Java agent or during a build.


阅读理解开始。重点你必定会看到了:

  • 「code generation」

  • 「creating and modifying Java classes」


做者贴心的加了一段小字来描述框架的优点。选重点的说就是:

  1. 不须要理解字节码,也不须要理解class 文件格式

  2. API 非侵入,设计简洁易懂

  3. 高度可定制,能够任意自定义


我本身认为该把这点也加上,不写 Java Agent 也能够 Attach 到 JVM 上,把 ByteBuddy 本身当成一个Agent,运行时直接install。Cool。


不写JVM Agent 也能对类拦截和修改,咱们来认识下揭开字节码修改黑魔法的家伙。

为了对 Class 进行一些操做,咱们通常都离不了 JVM Agent。不论是启动时直接链接,仍是运行时动态的 Attach到对应的JVM 上,都须要 Agent。也就是咱们熟悉的premainagentmain 的触发入口,经过它们,咱们才能拿到 Instrumentation,从而进行 transformredeine


但这个东西的使用,给人老是「阳春白雪」的感受,让人以为是黑魔法同样,通常不会轻易尝试使用。

有了ByteBuddy,就不用再羡慕一些框架的「运行时加强」,「动态织入」等等,均可以实现。

如何上手呢?

只须要下载 Jar 文件或者 Maven 添加依赖以后就能狂奔了。


好比官方的这个   HelloWorld

Class<?> dynamicType = new ByteBuddy() .subclass(Object.class) .method(ElementMatchers.named("toString")) .intercept(FixedValue.value("Hello World!")) .make() .load(getClass().getClassLoader()) .getLoaded(); assertThat(dynamicType.newInstance().toString(), is("Hello World!"));


直接把 Object 的 toString 方法改写了。


再好比咱们能够在开发 Java Agent的时候使用这个伙计

public static void premain(String args, Instrumentation inst) { AgentBuilder agentBuilder = new AgentBuilder.Default();        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() { public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {                String className = typeDescription.getCanonicalName(); builder = builder.method(ElementMatchers.any())//匹配任意方法 .intercept(MethodDelegation.to(new SimplePackageInstanceMethodInterceptor())); return builder; } };         agentBuilder = agentBuilder.type(ElementMatchers.nameStartsWith("com.example.hello.sample")).transform(transformer); agentBuilder.installOn(inst); }


在类里进行拦截匹配的时候,能够经过类名来限定,同时以不一样的模式去匹配方法名等,这里的ElementMatchers能够用在类名与方法名等匹配场景中

 //ElementMatchers.named("abc") // 特定名称的方法 //ElementMatchers.nameStartsWith("hello") // 以什么开头的 //ElementMatchers.nameEndsWith("test")   // 以什么结尾的


咱们看到前面的代码中 agentBuilder.installOn(inst);

经过 JavaAgent的Instrument 进行类修改。

AgentBuilder 还提供了一个神奇的方法:

agentBuilder.installOnByteBuddyAgent();

这样无须提供 Jar 文件也同样能实现运行时加强。不过须要注意,这样使用时,必定要先执行这行代码,这也是其实现的秘密:

 ByteBuddyAgent.install();


由于 ByteBuddy 本身作为一个 Jar 也 Attach ,而后再将其它后续的加强代码加入,像不像「特洛伊木马」 :)


另外, ByteBuddy 还支持相似于 AOP 的 Advice实现,在拦截指定方法后能够实现OnMethodEnter 和 OnMethodExit 的控制,在这其中,能够完成绕过用户代码,执行自定义内容的逻辑。


我们在开始的时候,还提到了代码的生成。这在 ByteBuddy 看来也是易如反掌。


和上面的代码同样,先要拿到 AgentBuilder,以后在执行 tranform的时候,直接指定方法名,以及对应的参数,访问控制符等。

 DynamicType.Builder.MethodDefinition.ExceptionDefinition<?> hello =                                builder.defineMethod(methodName, types, Visibility.PUBLIC) .withParameters(m.getParameters().asTypeList());


再好比在运行时给一个方法增长注解,

builder.method(ElementMatchers.named("methodName")).intercept(SuperMethodCall.INSTANCE)                           .annotateMethod(AnnotationDescription.Builder.ofType(TestAnnotation.class) .define("testValue", 123).build());



是否是功能很强大?


更多的用法,能够参考官方的Github或者官网。


欢迎交流。


相关阅读

Sentinel 是怎样拦截异常流量的?

MySQL: 喂,别走,听我解释一下好吗?

多表查询用什么联接?别信感受,用数听说话

一个数据库SQL查询的数次轮回

数据库是咋工做的?(一)

凭什么让日志先写?

Java七武器系列长生剑 -- Java虚拟机的显微镜 Serviceability Agent

Java七武器系列霸王枪 -- 线程状态分析 jstack

Java七武器系列孔雀翎-- 问题诊断神器BTrace

嵌套事务、挂起事务,Spring 是怎样给事务又实现传播特性的?

怎样阅读源代码?





源码|实战|成长|职场


这里是「Tomcat那些事儿

请留下你的足迹

咱们一块儿「终身成长」

本文分享自微信公众号 - Tomcat那些事儿(tomcat0000)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索