出于好奇,是否有任何(稳定的)开源项目用于运行java代码生成而不是cglib?我为什么要使用它们?
答案 0 :(得分:101)
CGLIB和几乎所有其他库都建立在ASM之上,ASM本身的作用非常低。对于大多数人来说,这是一个显示阻止,因为你必须理解字节代码和JVMS的一些正确使用它。但掌握ASM肯定非常有趣。但请注意,虽然有一个很好 ASM 4 guide,但在API的某些部分,如果javadoc文档完全存在,它可以非常简洁,但它正在改进。它紧跟JVM版本以支持新功能。
但是,如果您需要完全控制,ASM是您的首选武器。
该项目定期更新;在此编辑版本5.0.4版本于2015年5月15日发布。
Byte Buddy是一个相当新的库,但提供了CGLIB或Javassist提供的任何功能以及更多功能。 Byte Buddy可以完全自定义到字节代码级别,并附带一个富有表现力的域特定语言,允许非常易读的代码。
输入安全回调
Javassist建议或自定义检测代码基于普通
String
中的代码,因此类型检查和调试在此代码中是不可能的,而ByteBuddy允许使用纯Java编写那些,因此强制执行类型检查并允许调试。 / p>
注释驱动(灵活)
可以使用注释配置用户回调,以便在回调中接收所需的参数。
作为代理商提供
漂亮的代理构建器允许将ByteBuddy用作纯代理或附加代理。它允许不同类型
主要缺点可能是API对初学者来说有点冗长,但它被设计为一个选择性的API,形成代理生成DSL;没有任何神奇或可疑的默认值。在操作字节代码时,它可能是最安全和最合理的选择。此外,有多个示例和一个大教程,这不是一个真正的问题。
2015年10月,这些项目收到了Oracle Duke's choice award。此时它刚刚达到1.0.0 milestone,这是一项非常成就。
请注意,mockito已替换版本2.1.0中的CGLIB by Byte Buddy。
Javassist的javadoc比CGLIB的javadoc好。类工程API是可以的,但Javassist也不是完美的。特别是,ProxyFactory
相当于CGLIB的Enhancer
也有一些缺点,只列举几个:
ClassloaderProvider
是一个静态字段,然后它适用于同一个类加载器中的所有实例ProxyFactory
不支持它们。在面向方面,可以在代理中注入代码,但Javassist中的这种方法是有限的,并且有点容易出错:
此外,Javassist被认为比Cglib慢。这主要是因为它读取类文件而不是读取CGLIB等加载类。 implementation本身很难理解为公平;如果需要对Javassist代码进行更改,那么很有可能会破坏某些内容。
Javassist也因此不活跃,他们向github circa 2013的迁移似乎已证明有用,因为它显示了社区的常规提交和拉取请求。
这些限制仍然存在于3.17.1版本中。版本已经升级到版本3.20.0,但似乎Javassist可能仍然存在Java 8支持的问题。
JiteScript看起来像是一个很好地为ASM制作DSL的新片段,它基于最新的ASM版本(4.0)。代码看起来很干净。
但该项目还处于早期阶段,因此API /行为可能会发生变化,而且文档很难实现。如果不放弃,更新很少。
这是一个相当新的工具,但它提供了迄今为止最好的人类 API。它允许使用不同类型的代理,例如子类代理(cglib方法)或编织或委托。
虽然这个很少见,但如果运作良好,则不存在任何信息。处理字节码时需要处理很多案例。
AspectJ是面向方面编程(仅限)的强大工具。 AspectJ操纵字节代码以实现其目标,以便您可以用它实现目标。但是,这需要在编译时进行操作;弹簧提供自版本2.5 4.1.x后的代理在加载时编织。
关于CGLIB的一个词,自问该问题以来已经更新过。
CGLIB速度非常快,这是它仍然存在的主要原因之一,以及CGLIB直到现在(2014-2015)的工作状况几乎比任何替代品都好。
一般来说,允许在运行时重写类的库必须避免在重写相应的类之前加载任何类型。因此,他们无法使用Java反射API,它需要加载反射中使用的任何类型。相反,他们必须通过IO(这是性能破坏者)读取类文件。这使得例如Javassist或Proxetta明显慢于Cglib,Cglib只是通过反射API读取方法并覆盖它们。
然而,CGLIB已不再处于积极发展阶段。最近发布了这些版本,但很多人认为这些更改并不重要,大多数人从未更新到版本3,因为CGLIB在上一版本中引入了一些severe bugs,这些内容并没有真正建立信心。版本3.1已修复版本3.0的许多问题(从版本4.0.3 Spring框架重新包装version 3.1)。
此外,CGLIB源代码相当poor quality,因此我们不会看到新的开发人员加入CGLIB项目。有关CGLIB活跃度的印象,请参阅他们的mailing list。
请注意,在proposition on the guice mailing list之后,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字节代码内部有深入的了解。
答案 1 :(得分:10)
如果您需要创建代理,请查看commons-proxy - 它同时使用CGLIB和Javassit。
答案 2 :(得分:10)
我更喜欢原始的ASM,我相信它仍然被cglib使用。这是低级别的,但文档是辉煌的,一旦你习惯它,你就会飞。
要回答第二个问题,当您的反射和动态代理开始感觉有点拼凑而且您需要坚如磐石的解决方案时,您应该使用代码生成。在过去,我甚至在Eclipse的构建过程中添加了代码生成步骤,有效地为我提供了编译时报告任何事物和所有内容。
答案 3 :(得分:4)
我认为使用Javassist代替cglib更有意义。例如。与cglib不同,javasist完美地使用签名的罐子。此外,像Hibernate项目decided to stop using cglib in favor of Javassist这样宏伟。
答案 4 :(得分:0)
CGLIB是十多年前在AOP和ORM时代设计和实施的。 目前我认为没有理由使用它,我不再维护这个库(除了我的遗留应用程序的错误修复)。 实际上我见过的所有CGLIB用例都是现代编程中的反模式。 通过任何JVM脚本语言实现相同的功能应该是微不足道的,例如常规