Java字节码工程库 Byte Buddy 最新版本完全支持Java 11以及自Java 8以来引入的所有类文件和字节码新特性。其中包括新的ConstantDynamic(有时称为condy)特性和Java 11 Nestmates。
InfoQ采访了ByteBuddy的作者Rafael Winterhalter,以了解更多信息。
Rafael Winterhalter: Byte Buddy是一个代码生成库 ,通过简单的Java API定义新类或修改现有类。这个库会生成并操作Java字节码。通过处理字节码,它可以与使用任何JVM语言编写的代码进行交互,并且可以在Java应用程序的运行期间使用该库来修改当前要执行的代码,甚至包括自己的代码。
Byte Buddy主要被用在其他库和框架中。例如,Hibernate使用Byte Buddy来实现实体代理,Mockito使用它来生成模拟类。Byte Buddy也渐渐被用于开发可以改变整个应用程序行为的Java代理。APM工具(例如Instana)正在使用这类代理收集应用程序运行期间的度量指标。
我从2014年开始开发Byte Buddy,并在2015年发布了第一个非beta版本。从那以后,它开始获得相当多的关注。现在,这个库每年将近有一亿次的下载量。
要想使用这个库,只需将它添加到项目中,然后使用它的DSL生成类。Byte Buddy的GitHub页面和官网都提供了如何创建简单类的示例。网页上还提供了综合性文档,还有很多博客文章、YouTube和Vimeo上的视频资料。
Winterhalter:在最新的版本中,我一直在尝试支持Java 11和12。不过我的大部分时间都用于添加对Java模块系统的支持,这是一条漫漫长路。随着最新版本的发布,对模块系统的支持变得更加稳定,Byte Buddy甚至会在即将发布的1.9.0版本中添加一个module-info.class。同时,这个库保留了对Java 6、7和8的兼容性。
用户的特性请求通常包括对较新Java版本的支持,因为缺乏对新版本的支持通常会影响项目在较新的VM上编译。类似地,还有很多特性请求要求支持Kotlin或Scala等JVM语言,这些语言在字节码转换方面有一些特别之处。如果Java语言正在添加新特性,那么就要十分小心,并且通常需要通过扩展JVM的功能来实现。其他语言有时会尝试模拟特定的行为,否则Byte Buddy会出现问题。
Winterhalter:对于每个Java版本,Byte Buddy都需要根据Java类文件格式的变化进行调整。通常这些变化是很微小的,但也可能很复杂。例如,当Java 8发布时,Byte Buddy必须支持接口的默认方法。这看起来像是一个很小的变化,但它需要进行大量的重构。随着Java发布速度的加快,我的工作变得有点令人厌烦,但我不能抱怨,因为这个项目是我当前职业生涯的焦点。
到目前为止我遇到的一个问题是,因为很多库依赖了Byte Buddy,所以每次新的Java主要版本发布后,我都会被要求赶快为他们提供支持。如果我不做出更新,依赖Byte Buddy的库就不能使用新的Java版本,这阻碍了其他维护者采用Byte Buddy。与此同时,像Maven这样的工具通常也需要一些时间才能支持新的Java版本,这也使得我很难快速做出更新。半年时间很快就过去了。但我觉得其他工具开发者已经适应了这种变化。与先前的版本相比,支持Java 10到Java 12要容易得多,因为每个版本中包含的变更更少了。
Winterhalter:Java开发人员通常使用static final关键字来定义常量。不过,Java类文件中的常量可以是通过符号引用的非类字段。有几种类型已经通过这些符号来表示字面量,例如Java字符串。
有了ConstantDynamic,就可以将任意变量表示为常量池中引用的常量值。使用类文件常量的主要好处是只在第一次使用时才创建,而不是在类加载时创建,而static final字段是在类加载时创建的。使用ConstantDynamic,将来可以避免在JVM中进行大量的加载。例如,在JVM启动时,它必须初始化Locale类,而这个类引用了JVM支持的所有语言。这种初始化相当昂贵并且通常是不必要的,因为大多数程序只使用默认语言。通过使用ConstantDyanmic,未来可以改进核心库,实现更快的JVM启动,当然其他库也可以做类似的事情。
截至今天,Java或其他JVM语言还没有公开支持ConstantDynamic。但因为JVM已经提供支持,所以Byte Buddy也已经能够在字节码中创建这样的动态常量。动态常量是通过引导(bootstrap)方法创建的,这个方法会返回常量值。在使用常量值的位置需要引用这个引导方法。
我在博文中详细介绍了 ConstantDynamic 和 InvokeDynamic ,以及如何在Byte Buddy中使用它们。InvokeDynamic实际上和ConstantDynamic非常类似,区别在于它使用引导方法绑定方法调用而不是常量值。当然,Byte Buddy也为它提供了API。
Winterhalter:除了ConstantDynamic之外,JVM还引入了嵌套伴侣(nest mate)的概念,以便为嵌套类提供更好的方法访问控制。通过在两个类文件中将它们定义为嵌套伴侣,它们就可以获得调用对方私有方法的权限。以前,javac编译器通过添加package-private访问器方法来实现嵌套类的私有方法调用。
Byte Buddy支持嵌套伴侣,但目前DSL不允许更改或添加嵌套伴侣。这是一个比较大的特性,我想在今年晚些时候解决这个问题。
Winterhalter:目前,几乎所有即将推出的Java特性都让我兴奋不已。我最期待的是Loom,它将提供对JVM continuation的原生支持。作为一名软件顾问,我参与的项目通常使用actor模型或反应式回调之类的抽象来实现并发。这些应用程序通常会越变越复杂,业务逻辑被埋没在模拟并发性的仪式代码中。这导致业务代码难以重构,因为它们更多地满足了技术需求却忽略了领域逻辑。我希望Loom能够让我们不需要在业务代码中显式地使用并发模型。
除了Loom之外,我也很期待看到对Graal编译器的扩展支持,我认为它是JVM能够保持其作为世界上最好运行时之一的重要基石。除了JIT编译之外,AOT编译和原生镜像创建也将扩展Java在未来的使用范围。我也希望Metropolis项目能够有助于弥合大多数Java开发人员的知识与JVM的功能之间的一些差距。
Winterhalter:我常常觉得一部分Java开发人员对甲骨文的管理持怀疑态度,他们觉得模块系统或新发布周期对甲骨文来说可能意味着更大的负担而不是好处。
我认为甲骨文最终解决了很多可能危及平台长期发展的问题。JVM现在处在一个更好的状态,很多即将发生的变化都是基于这个基础。成为Java开发人员是一个激动人心的时刻,我们都有很多值得期待的事情。
查看英文原文: New Version of ByteBuddy Fully Supports Java 11