有 cglib 的替代品吗?[已关闭]

2022-08-31 15:51:08

只是出于好奇,除了cglib之外,是否有任何(稳定的)开源项目用于运行时java代码生成?我为什么要使用它们?


答案 1

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建议或自定义检测代码基于普通代码,因此在此代码中无法进行类型检查和调试,而ByteBuddy允许使用纯Java编写那些代码,因此强制执行类型检查并允许调试。String

  • 注释驱动(灵活)

    用户回调可以配置注释,允许在回调中接收所需的参数。

  • 可作为代理

    漂亮的代理构建器允许将 ByteBuddy 用作纯代理或附加代理。它允许不同的种类

  • 有据可查
  • 很多例子
  • 干净的代码,约94%的测试覆盖率
  • 安卓 DEX 支持

也许主要的缺点是,API对于初学者来说有点冗长,但它被设计为一个选择加入的API,形状为代理代DSL;没有魔术或可疑的默认值。在操作字节码时,它可能是最安全,最合理的选择。此外,通过多个示例和一个大教程,这不是一个真正的问题。

2015年10月,该项目获得了甲骨文杜克选择奖。此时它刚刚达到1.0.0里程碑,这是一项相当大的成就。

请注意,在2.1.0版本中已经用Byte Buddy取代了CGLIB

Javassist

Javassist的javadoc比CGLIB的javadoc要好得多。类工程API是可以的,但Javassist也不完美。特别是,这相当于CGLIB也遭受了一些缺点,仅举几例:ProxyFactoryEnhancer

  • 不完全支持桥接方法(即为协变返回类型生成的桥接方法)
  • ClassloaderProvider是一个静态字段,然后它应用于同一类装入器中的所有实例
  • 自定义命名本来是受欢迎的(检查签名的jar)
  • 没有扩展点,几乎所有感兴趣的方法都是私有的,如果我们想改变一些行为,这很麻烦。
  • 虽然 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方法)或编织或委派。

虽然,这个是相当罕见的,如果它工作得很好,就没有信息存在。在处理字节码时,有很多角落情况需要处理。

AspectJ

AspectJ是面向方面的编程的非常强大的工具(仅)。AspectJ操纵字节码来实现其目标,这样您就可以使用它来实现您的目标。但是,这需要在编译时进行操作。spring提供自版本2.54.1.x以来通过代理在加载时编织。

CGLIB

关于CGLIB的一句话,自从这个问题被问到以来已经更新了。

CGLIB非常快,这是它仍然存在的主要原因之一,以及CGLIB迄今为止(2014-2015)几乎比任何替代品都好。

一般来说,允许在运行时重写类的库必须避免在重写相应的类之前加载任何类型。因此,它们不能使用 Java 反射 API,该 API 要求加载反射中使用的任何类型。相反,他们必须通过IO(这是一个性能破坏者)读取类文件。这使得Javassist或Proxetta比Cglib慢得多,Cglib只是通过反射API读取方法并覆盖它们。

然而,CGLIB不再处于积极开发状态。有最近的版本,但这些更改被许多人认为是微不足道的,大多数人从未更新到版本3,因为CGLIM在上一个版本中引入了一些严重的错误,并没有真正建立信心。版本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 6开始,注释处理被集成到Java编译器中。apt

在某个时候,您需要显式传递处理器,现在使用该方法(只需将此文件添加到jar中)编译器可以自动检测注释处理器。ServiceLoaderMETA-INF/services/javax.annotation.processing.Processor

这种代码生成方法也有缺点,它需要大量的工作和对Java语言而不是字节码的理解。这个API有点麻烦,并且由于编译器中的插件必须非常小心,以使此代码成为最具弹性和用户友好的错误消息。

这里最大的优点是它避免了运行时的另一个依赖关系,可以避免permgen内存泄漏。一个人可以完全控制生成的代码。

结论

2002年,CGLIB定义了一个新的标准来轻松操作字节码。我们现在拥有的许多工具和方法(CI,覆盖范围,TDD等)在当时不可用或不成熟。CGLIB设法与十多年来保持联系;这是一个相当不错的成就。它速度快,并且具有易于使用的API,而不是直接操作操作码。

它定义了有关代码生成的新标准,但现在它不再是因为环境和需求已经改变,标准和目标也发生了变化。

JVM在最近和将来的Java(7/8/9/10)版本(invokedynamic,默认方法,值类型等)中发生了变化。ASM定期升级他的API和内部结构以遵循这些变化,但CGLIM和其他人尚未使用它们。

虽然注释处理正在获得牵引力,但它并不像运行时生成那样灵活。

截至2015年,Byte Buddy虽然在现场相当新,但为运行时生成提供了最引人注目的卖点。一个体面的更新率,作者对Java字节码内部有深入的了解。


答案 2

Javassist.

如果你需要制作代理,看看commons-proxy - 它同时使用CGLIB和Javassit。


推荐