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


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


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


原因可能有很多,一来是可能确实不需要,再者需要开发独立的 Agent Jar 文件,在 Jar 内对类的 transform 开发也并不容易。


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

不过就像机器语言不方便,人们发明了汇编语言,又发现了高级语言。对于字节码的操作也类似,人们觉得直接操作字节码有难度,而且需要深入理解 JVM 规范,具体什么位置多少字节代表啥,这不是一般人喜欢的,于是 ASM 框架出现了;但还是有规范的影子,不太「高级」,于是又出现了Javassist 这一类的「高级」工具。


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


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




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


这个工具是:Bytebuddy。 

从名字你就看的出来,它立志要做字节码的好伙伴。所以在很多开源框架里也能看到它的身影。


既然已经有了不少的工具, 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源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

微信关注我们

原文链接:https://my.oschina.net/u/4585957/blog/4710813

转载内容版权归作者及来源网站所有!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

相关文章

发表评论

资源下载

更多资源
Apache Tomcat7、8、9(Java Web服务器)

Apache Tomcat7、8、9(Java Web服务器)

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

Eclipse(集成开发环境)

Eclipse(集成开发环境)

Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括Java开发工具(Java Development Kit,JDK)。

Java Development Kit(Java开发工具)

Java Development Kit(Java开发工具)

JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。

Sublime Text 一个代码编辑器

Sublime Text 一个代码编辑器

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。