只是出于好奇,除了 cglib 之外,是否有任何(稳定的)开源项目用于运行时 java 代码生成?我为什么要使用它们?
5 回答
ASM java-asm
CGLIB 和几乎所有其他库都建立在 ASM 之上,而 ASM 本身的作用非常低。这对大多数人来说是一个阻碍,因为您必须了解字节码和一点点JVMS才能正确使用它。但是掌握 ASM 无疑是非常有趣的。但是请注意,虽然有一个很棒的 ASM 4 指南,但在 API 的某些部分,如果存在 javadoc 文档,它可能会非常简洁,但它正在被改进。它紧跟 JVM 版本以支持新功能。
但是,如果您需要完全控制,ASM 是您的首选武器。
该项目定期更新;在本次编辑时,版本 5.0.4 于 2015 年 5 月 15 日发布。
字节好友 字节好友
Byte Buddy 是一个相当新的库,但提供了 CGLIB 或 Javassist 提供的任何功能等等。Byte Buddy 可以完全自定义到字节码级别,并带有一种富有表现力的领域特定语言,允许非常可读的代码。
- 它支持所有 JVM 字节码版本,包括 Java 8 关于默认方法的一些操作码的语义更改。
- ByteBuddy 似乎没有其他库的缺点
- 高度可配置
- 相当快(基准 代码)
- 键入安全的流式 API
类型安全回调
Javassist 建议或自定义检测代码基于普通代码,
String
因此无法在此代码中进行类型检查和调试,而 ByteBuddy 允许使用纯 Java 编写这些代码,因此强制执行类型检查并允许调试。注释驱动(灵活)
用户回调可以配置注释,允许在回调中接收想要的参数。
可作为代理
漂亮的代理构建器允许 ByteBuddy 用作纯代理或附加代理。它允许不同种类
- 非常有据可查
- 很多例子
- 干净的代码,~94% 的测试覆盖率
- 安卓 DEX 支持
主要的缺点可能是 API 对于初学者来说有点冗长,但它被设计为一个可选的 API,形状类似于代理生成 DSL;没有神奇或可疑的默认值。在操作字节码时,它可能是最安全、最合理的选择。还有多个示例和一个大教程,这不是一个真正的问题。
2015 年 10 月,该项目获得了Oracle Duke 的选择奖。此时它刚刚达到1.0.0 里程碑,这是一个相当大的成就。
请注意,mockito在 2.1.0 版本中已将CGLIB替换为 Byte Buddy 。
javassist javassist
Javassist 的 javadoc 比 CGLIB 的要好得多。类工程 API 还可以,但 Javassist 也不完美。特别ProxyFactory
是,相当于CGLIB的Enhancer
也有一些缺点,仅列出一些:
- 不完全支持桥接方法(即为协变返回类型生成的方法)
ClassloaderProvider
是一个静态字段,然后它适用于同一类加载器中的所有实例- 自定义命名可能会受到欢迎(检查签名的罐子)
- 没有扩展点,几乎所有感兴趣的方法都是私有的,如果我们想改变一些行为,这很麻烦
- 虽然 Javassist 提供对类中注释属性的支持,但它们在
ProxyFactory
.
在面向方面方面,可以在代理中注入代码,但是 Javassist 中的这种方法是有限的并且有点容易出错:
- 方面代码以纯 Java 字符串编写,并以操作码编译
- 没有类型检查
- 没有泛型
- 没有拉姆达
- 没有自动(取消)装箱
Javassist 也被认为比 Cglib 慢。这主要是由于它读取类文件而不是读取加载的类(如 CGLIB)的方法。而且实现本身很难公平地阅读;如果需要对 Javassist 代码进行更改,则有很多机会破坏某些东西。
Javassist 也受到了不活跃的影响,他们在 2013 年左右迁移到 github似乎被证明是有用的,因为它显示了来自社区的定期提交和拉取请求。
这些限制在 3.17.1 版本中仍然存在。版本已升级到 3.20.0 版,但似乎 Javassist 可能仍然存在 Java 8 支持问题。
JiteScript
JiteScript 确实看起来像是为 ASM 精心打造的新 DSL,它基于最新的 ASM 版本 (4.0)。代码看起来很干净。
但该项目仍处于早期阶段,因此 API / 行为可以改变,而且文档很糟糕。如果不放弃,更新很少。
普罗克塞塔 乔德
这是一个相当新的工具,但它提供了迄今为止最好的人类API。它允许不同类型的代理,例如子类代理(cglib 方法)或编织或委托。
虽然,这个相当罕见,但如果它运作良好,则不存在任何信息。处理字节码时有很多极端情况需要处理。
方面J 方面j
AspectJ 是一个非常强大的面向方面编程的工具(仅限)。AspectJ 操纵字节码来实现它的目标,这样你就可以用它来实现你的目标。但是,这需要在编译时进行操作;自版本2.5、4.1.x以来, spring 在加载时通过代理提供编织。
CGLIB _
自从提出这个问题以来,关于 CGLIB 的一个词已经更新。
CGLIB 速度非常快,这是它仍然存在的主要原因之一,同时 CGLIB 的工作效果几乎比迄今为止(2014-2015 年)的任何替代品都要好。
一般来说,允许在运行时重写类的库必须避免在重写相应的类之前加载任何类型。因此,它们不能使用要求加载反射中使用的任何类型的 Java 反射 API。相反,他们必须通过 IO 读取类文件(这是一个性能破坏者)。这使得 Javassist 或 Proxetta 比 Cglib 慢得多,后者只是通过反射 API 读取方法并覆盖它们。
但是,CGLIB 不再处于积极开发中。有最近的版本,但许多人认为这些变化微不足道,大多数人从未更新到版本 3,因为 CGLIB 在上一个版本中引入了一些严重的错误,这些错误并没有真正建立信心。3.1 版修复了 3.0 版的许多问题(因为 4.0.3 版 Spring 框架重新打包了3.1 版)。
此外,CGLIB 源代码质量较差,因此我们看不到新的开发人员加入 CGLIB 项目。有关 CGLIB 活跃度的印象,请参阅他们的邮件列表。
请注意,根据 guice 邮件列表上的建议,CGLIB 现在可以在github上使用,以使社区能够更好地帮助该项目,它似乎正在工作(多次提交和拉取请求,ci,更新的 maven),但大多数问题仍然存在.
目前正在开发 3.2.0 版本,并且他们将精力集中在 Java 8 上,但到目前为止,想要支持 Java 8 的用户必须在构建时使用技巧。但进展非常缓慢。
众所周知,CGLIB 仍然受到 PermGen 内存泄漏的困扰。但其他项目可能这么多年都没有经过实战考验。
编译时注解处理 注解处理
这当然不是运行时,而是生态系统的重要组成部分,大多数代码生成使用不需要运行时创建。
从 Java 5 开始,Java 5 附带了单独的命令行工具来处理注解:apt
,从 Java 6 开始,注解处理被集成到 Java 编译器中。
有时您需要显式传递处理器,现在使用这种ServiceLoader
方法(只需将此文件添加META-INF/services/javax.annotation.processing.Processor
到 jar 中)编译器可以自动检测注释处理器。
这种代码生成方法也有缺点,它需要大量的工作和对 Java 语言而不是字节码的理解。这个 API 有点麻烦,因为它是编译器中的插件,所以必须非常小心地使这个代码成为最具弹性和用户友好的错误消息。
这里最大的优点是它在运行时避免了另一个依赖,你可以避免 permgen 内存泄漏。并且可以完全控制生成的代码。
结论
2002年,CGLIB 定义了一个新的标准来轻松操作字节码。我们现在拥有的许多工具和方法(CI、覆盖率、TDD 等)当时不可用或不成熟。十多年来,CGLIB 一直保持相关性;这是一个相当不错的成就。与直接操作操作码相比,它速度快且 API 易于使用。
它定义了关于代码生成的新标准,但现在它不再是因为环境和要求发生了变化,标准和目标也发生了变化。
JVM 发生了变化,并将在最近和未来的 Java (7/8/9/10) 版本(invokedynamic、默认方法、值类型等)中发生变化。ASM 定期升级他的 API 和内部结构以跟踪这些变化,但 CGLIB 和其他人尚未使用它们。
虽然注释处理越来越受欢迎,但它不如运行时生成灵活。
截至 2015 年,Byte Buddy -虽然在现场相当新- 为运行时生成提供了最引人注目的卖点。不错的更新率,并且作者对 Java 字节码内部结构有深入的了解。
如果您需要制作代理,请查看commons-proxy - 它同时使用 CGLIB 和 Javassit。
我更喜欢 raw ASM,我相信 cglib 无论如何都会使用它。它的级别很低,但文档很棒,一旦你习惯了它,你就会飞起来。
要回答您的第二个问题,当您的反射和动态代理开始感觉有点拼凑在一起并且您需要一个坚如磐石的解决方案时,您应该使用代码生成。在过去,我什至在 Eclipse 的构建过程中添加了一个代码生成步骤,有效地为我提供了任何东西的编译时间报告。
我认为使用Javassist而不是 cglib 更有意义。例如 javasist 与 cglib 不同的签名 jar 完美地工作。此外,像 Hibernate 这样的大项目决定停止使用 cglib,转而使用 Javassist。
CGLIB 是十多年前在 AOP 和 ORM 时代设计和实现的。目前我认为没有理由使用它,并且我不再维护这个库(除了我的遗留应用程序的错误修复)。实际上,我见过的所有 CGLIB 用例都是现代编程中的反模式。通过任何 JVM 脚本语言(例如 groovy)实现相同的功能应该是微不足道的。