有没有替代cglib?

Mau*_*uli 57 java code-generation cglib

出于好奇,有没有(稳定的)开源项目用于运行java代码生成而不是cglib?我为什么要用它们呢?

Bri*_*ice 104

ASM

CGLIB和几乎所有其他库都建立在ASM之上,ASM本身的作用非常低.对于大多数人来说,这是一个显示阻止,因为你必须理解字节代码和一些JVMS来正确使用它.但掌握ASM肯定非常有趣.但请注意,虽然有一个很好的 ASM 4指南,但在API的某些部分,如果javadoc文档完全存在,它可以非常简洁,但它正在改进.它紧跟JVM版本以支持新功能.

但是,如果您需要完全控制,ASM是您的首选武器.

该项目定期更新; 在此编辑版本5.0.4版本于2015年5月15日发布.

Byte Buddy

Byte Buddy是一个相当新的库,但提供CGLIB或Javassist提供的任何功能以及更多功能.Byte Buddy可以完全自定义到字节代码级别,并附带一个富有表现力的域特定语言,允许非常易读的代码.

  • 它支持所有JVM字节码版本,包括有关默认方法的某些操作码的Java 8语义更改.
  • ByteBuddy似乎没有遭受其他图书馆的缺点
  • 高度可配置
  • 相当快(基准 代码)
  • 输入安全流畅的API
  • 键入安全回调

    Javassist建议或自定义检测代码基于简单的代码,String因此类型检查和调试在此代码中是不可能的,而ByteBuddy允许使用纯Java编写,因此强制执行类型检查并允许调试.

  • 注释驱动(灵活)

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

  • 可作为代理商使用

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

  • 记录很好
  • 很多例子
  • 清洁代码,约94%的测试覆盖率
  • Android DEX支持

可能的主要缺点是API对初学者来说有点冗长,但它被设计为一个选择性的API,形成代理生成DSL; 没有任何神奇或有问题的默认值.在操作字节代码时,它可能是最安全和最合理的选择.此外,有多个示例和一个大教程,这不是一个真正的问题.

2015年10月,这些项目获得了Oracle Duke的选择奖.在这个时候,它刚刚达到1.0.0里程碑,这是一个相当大的成就.

请注意,已经在版本2.1.0中替换了Byte Buddy的CGLIB.

Javassist

Javassist的javadoc比CGLIB的javadoc好.类工程API是可以的,但Javassist也不是完美的.特别是,ProxyFactoryCGLIB的等价物也有Enhancer一些缺点,仅列举一些:

  • 不完全支持Bridge方法(即为协变返回类型生成的方法)
  • ClassloaderProvider 相反,它是一个静态字段,然后它适用于同一个类加载器中的所有实例
  • 自定义命名可能是受欢迎的(检查签名的罐子)
  • 没有扩展点,几乎所有感兴趣的方法都是私有的,如果我们想改变某些行为,这很麻烦
  • 虽然Javassist在类中提供对注释属性的支持,但它们不受支持ProxyFactory.

在面向方面,可以在代理中注入代码,但Javassist中的这种方法是有限的并且有点容易出错:

  • 方面代码是用在操作码中编译的普通Java字符串编写
  • 没有类型检查
  • 没有泛型
  • 没有lambda
  • 没有自动(非)拳击

Javassist也被认为比Cglib慢.这主要是因为它读取类文件而不是读取CGLIB等加载类.而实现自身难以阅读是公平的; 如果需要在Javassist代码中进行更改,则有很多机会破坏某些东西.

Javassist也遭受了不活动的困扰,他们大约在2013年转向github似乎已经证明是有用的,因为它显示了社区的常规提交和拉取请求.

这些限制仍然存在于3.17.1版本中.版本已经升级到版本3.20.0,但似乎Javassist可能仍然存在Java 8支持的问题.

JiteScript

JiteScript看起来像是一个很好地为ASM制作DSL的新部件,它基于最新的ASM版本(4.0).代码看起来很干净.

该项目仍处于早期阶段,因此API /行为可能会发生变化,而且文档很难实现.如果不放弃,更新很少.

Proxetta

这是一个相当新的工具,但它提供了迄今为止最好的人工 API.它允许不同类型的代理,例如子类代理(cglib方法)或编织或委托.

虽然这个很少见,但如果运作良好,则不存在任何信息.处理字节码时需要处理的案例很多.

AspectJ

AspectJ是一个非常强大的面向方面编程工具(仅限).AspectJ操纵字节代码以实现其目标,以便您可以用它实现目标.但是,这需要在编译时进行操作; 弹簧提供自版本通过代理在加载时织2.5,4.1.x的.

CGLIB

关于CGLIB的一个词已被提出,因为该问题已被更新.

CGLIB非常快,这是它仍然存在的主要原因之一,以及CGLIB直到现在(2014-2015)几乎比任何替代品都更好的事实.

一般而言,允许在运行时重写类的库必须避免在重写相应的类之前加载任何类型.因此,他们无法使用Java反射API,它需要加载反射中使用的任何类型.相反,他们必须通过IO(这是性能破坏者)读取类文件.这使得例如Javassist或Proxetta明显慢于Cglib,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,它附带了单独的命令行工具来处理注释: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字节码内部有一个深入的了解.

  • 不要因为ASM的低级性质而被推迟.在不需要太多字节码知识的情况下深入了解它的好方法是使用包含的ASM-ifier,它检查一个类并输出ASM代码,该代码在运行时会生成该类! (4认同)

Boz*_*zho 10

Javassist.

如果您需要创建代理,请查看commons-proxy - 它使用CGLIB和Javassit.


Cur*_*Dog 10

我更喜欢原始ASM,我相信cglib无论如何都会使用它.这是低级别的,但文档很精彩,一旦你习惯它,你就会飞.

要回答你的第二个问题,当你的反射和动态代理开始感觉有点拼凑而你需要坚如磐石的解决方案时,你应该使用代码生成.在过去,我甚至在Eclipse的构建过程中添加了代码生成步骤,有效地为我提供了编译时报告任何事物和所有内容.