Byte Buddy是一个代码生成和操纵库,用于在Java应用程序的运行时创建和修改Java类,而无需编译器。除了与Java类库一起运送的代码生成实用程序外,字节伙伴允许创建任意类,并且不限于实现创建运行时代理的接口。此外,Byte Buddy提供了方便的API,用于使用Java代理或构建过程中手动更改课程。
为了使用字节好友,不需要了解Java字节代码或类文件格式。相比之下,Byte Buddy的API的目标是简洁且易于理解的代码。但是,字节好友仍然可以完全自定义,以定义定义自定义字节代码的可能性。此外,API被设计为尽可能侵蚀性,因此,字节好友不会在其创建的类中留下任何痕迹。因此,生成的类可以存在,而无需在类路径上需要字节好友。由于此功能,Byte Buddy的吉祥物被选为幽灵。
Byte Buddy用Java 5编写,但支持任何Java版本的类别。字节好友是一个轻量级的库,仅取决于Java字节代码解析器库ASM的访问者API ASM本身不需要任何进一步的依赖。
乍一看,运行时代码生成似乎是某种应该避免的黑魔法,只有少数开发人员编写在运行时明确生成代码的应用程序。但是,在创建需要与编译时未知的任意代码和类型交互的库时,此图片会发生变化。在这种情况下,库实施程序通常必须在要求用户实现库专有界面或在用户类型首先知道库时在运行时生成代码之间进行选择。许多已知的库,例如Spring或Hibernate选择后一种方法,该方法在使用普通的旧Java对象的术语下在用户中流行。结果,代码生成已成为Java空间中普遍存在的概念。 Byte Buddy试图创新Java类型的运行时创建,以便为依靠代码生成的人提供更好的工具。
2015年10月,Byte Buddy获得了Oracle的Duke选择奖。该奖项赞赏Byte Buddy的“ Java Technology中的大量创新”。我们因获得这个奖项而感到非常荣幸,并希望感谢所有帮助Byte Buddy取得成功的用户和其他所有用户。我们真的很感激!
Byte Buddy在生产质量方面提供了出色的性能。它是稳定的,在杰出的框架和工具(例如Mockito,Hibernate,Jackson,Google的Bazel Build System等)中使用。大量商业产品还使用了字节好友。目前每年下载超过7500万次。
用字节好友说“你好”是尽可能容易的。 Java类的任何创建都以ByteBuddy
类的实例开头,该类别代表创建新类型的配置:
Class <?> dynamicType = new ByteBuddy ()
. subclass ( Object . class )
. method ( ElementMatchers . named ( "toString" ))
. intercept ( FixedValue . value ( "Hello World!" ))
. make ()
. load ( getClass (). getClassLoader ())
. getLoaded ();
assertThat ( dynamicType . newInstance (). toString (), is ( "Hello World!" ));
上面示例中使用的默认ByteBuddy
配置在“类文件”格式的最新版本中创建了Java类,该类别由处理Java虚拟机理解。希望从示例代码中可以明显看出,创建类型将扩展Object
类并覆盖其toString
方法,该方法应该返回Hello World!
。要覆盖的方法由所谓的ElementMatcher
确定。在上面的示例中,使用了named(String)
预定义元素匹配器,该匹配器通过其确切名称标识方法。字节好友配备了许多预定义且经过良好测试的匹配器,这些匹配器是在ElementMatchers
类中收集的,并且很容易组成。但是,自定义匹配器的创建与实现(功能) ElementMatcher
接口一样简单。
为了实现toString
方法, FixedValue
类定义了被覆盖方法的恒定返回值。定义恒定值只是许多与字节好友一起运送的方法拦截器的一个示例。通过实现Implementation
接口,甚至可以通过自定义字节代码来定义一种方法。
最后,创建了所述的Java类,然后加载到Java虚拟机中。为此,需要一个目标类加载程序。最终,我们可以通过在创建类的实例上调用toString
方法来说服自己,并找到返回值以表示我们期望的常数值。
当然,一个Hello World示例是一个过于简单的用例,用于评估代码生成库的质量。实际上,这样一个库的用户希望执行更复杂的操作,例如,将钩子引入Java程序的执行路径中。使用字节好友,但是这样做同样简单。下面的示例可以探索如何拦截方法调用。
Byte Buddy通过Implementation
接口的实例表达动态定义的方法实现。在上一个示例中,已经证明了实现该接口的FixedValue
。通过实现此接口,字节好友的用户可以转到一种方法的定义自定义字节代码的长度。通常,使用字节伙伴的预定义实现(例如MethodDelegation
更容易,该实现允许在Pline Java中实现任何方法。使用此实现是直接的,因为它通过将控制流委托给任何POJO来运行。作为这样的pojo的一个例子,字节好友可以例如将呼叫重定向到以下类的唯一方法:
public class GreetingInterceptor {
public Object greet ( Object argument ) {
return "Hello from " + argument ;
}
}
请注意,上面的GreetingInterceptor
不取决于任何字节好友类型。这是个好消息,因为字节好友在课堂路径上都不会生成Byte Buddy的课程!鉴于上述GreetingInterceptor
,我们可以使用字节好友实现Java 8 java.util.function.Function
界面及其摘要apply
方法:
Class <? extends java . util . function . Function > dynamicType = new ByteBuddy ()
. subclass ( java . util . function . Function . class )
. method ( ElementMatchers . named ( "apply" ))
. intercept ( MethodDelegation . to ( new GreetingInterceptor ()))
. make ()
. load ( getClass (). getClassLoader ())
. getLoaded ();
assertThat (( String ) dynamicType . newInstance (). apply ( "Byte Buddy" ), is ( "Hello from Byte Buddy" ));
执行上述代码,字节伙伴实现了Java的Function
接口,并将apply
方法作为委托给我们以前定义的GreetingInterceptor
Pojo的实例。现在,每次调用Function::apply
方法时,将将控制流派遣到GreetingInterceptor::greet
,而后者方法的返回值将从接口的方法返回。
可以通过注释Interceptor的参数来定义拦截器使用更多通用输入和输出。当字节好友发现注释时,库注入了拦截器参数所需的依赖性。更一般的拦截器的一个示例是以下类:
public class GeneralInterceptor {
@ RuntimeType
public Object intercept ( @ AllArguments Object [] allArguments ,
@ Origin Method method ) {
// intercept any method of any signature
}
}
使用上述拦截器,可以匹配和处理任何拦截方法。例如,当匹配Function::apply
时,该方法的参数将作为数组的单个元素传递。同样,由于@Origin
注释,将通过拦截器的第二个参数将通过Fuction::apply
的Method
。通过在该方法上声明@RuntimeType
注释,字节好友最终将返回的值施放为截距方法的返回值,如果有必要的话。在此过程中,字节好友还采用自动拳击和拆箱。
除了已经提到的注释外,还有许多其他预定义的注释。例如,当在Runnable
或Callable
类型上使用@SuperCall
注释时,字节好友注入代理实例,如果存在这种方法,该实例允许调用非吸收超级方法。即使字节好友不涵盖用例,字节好友也提供了定义自定义注释的扩展机制。
您可能希望使用这些注释将您的代码与字节好友联系起来。但是,Java会忽略注释,以防类加载程序不可见。这样,如果没有字节好友,生成的代码仍然可以存在!您可以在其Javadoc和Byte Buddy的教程中找到有关MethodDelegation
的更多信息以及其所有预定义注释。
字节好友不仅限于创建子类,还可以重新定义现有代码。为此,Byte Buddy为定义所谓的Java代理提供了方便的API。 Java代理是普通的旧Java程序,可用于在其运行时更改现有Java应用程序的代码。例如,我们可以使用字节好友更改方法来打印其执行时间。为此,我们首先定义一个类似于以前示例的拦截器的拦截器:
public class TimingInterceptor {
@ RuntimeType
public static Object intercept ( @ Origin Method method ,
@ SuperCall Callable <?> callable ) {
long start = System . currentTimeMillis ();
try {
return callable . call ();
} finally {
System . out . println ( method + " took " + ( System . currentTimeMillis () - start ));
}
}
}
使用Java代理,我们现在可以将此拦截器应用于匹配ElementMatcher
的所有类型中的TypeDescription
。例如,我们选择将上述拦截器添加到所有类型的名称中,该名称以Timed
结束。这样做是为了简单起见,而注释可能是为生产代理标记此类类别的更合适的选择。使用Byte Buddy的AgentBuilder
API,创建Java代理就像定义以下代理类一样容易:
public class TimerAgent {
public static void premain ( String arguments ,
Instrumentation instrumentation ) {
new AgentBuilder . Default ()
. type ( ElementMatchers . nameEndsWith ( "Timed" ))
. transform (( builder , type , classLoader , module , protectionDomain ) ->
builder . method ( ElementMatchers . any ())
. intercept ( MethodDelegation . to ( TimingInterceptor . class ))
). installOn ( instrumentation );
}
}
与Java的main
方法类似, premain
方法是我们使用重新定义的任何Java代理的入口点。作为一个参数,Java代理接收了Instrumentation
接口的实例,该实例允许字节伙伴挂钩JVM的标准API以进行运行时类重新定义。
该程序与一个清单文件一起包装,其中指向TimerAgent
Premain-Class
属性。现在,可以通过设置-javaagent:timingagent.jar
类似于将JAR添加到类路径,可以将结果的JAR文件添加到任何Java应用程序中。随着代理活动,所有Timed
结束的课程现在都将其执行时间打印到控制台。
字节好友还能够通过禁用类文件格式并使用Advice
仪器来应用所谓的运行时附件。有关更多信息,请参阅Advice
和代理商班级课程的Javadoc和AgentBuilder
类。 Byte Buddy还通过ByteBuddy
实例或使用字节好友Maven和Gradle插件来显式更改Java类。
Byte Buddy是一个综合的库,我们只刮擦了字节好友的能力表面。但是,Byte Buddy的目标是通过提供用于创建类的领域的语言来易于使用。大多数运行时代码生成都可以通过编写可读代码来完成,而无需了解Java的类文件格式。如果您想了解有关字节好友的更多信息,可以在字节好友的网页上找到这样的教程(也有中文翻译)。
此外,Byte Buddy附带了详细的编码文档和广泛的测试案例覆盖范围,也可以用作示例代码。最后,您可以在Wiki中找到有关字节好友的最新文章和演示列表。使用字节好友时,还必须确保阅读有关维护项目依赖性的以下信息。
字节好友的使用是免费的,不需要购买许可证。为了充分利用图书馆或获得简单的开始,可以购买培训,开发时间或支持计划。费率取决于参与的范围和持续时间。请与[email protected]联系以获取更多信息。
Byte Buddy在Tidelift上列出。如果您不使用字节好友在某种程度上要购买明确的支持并希望一般支持开源社区,请考虑订阅。
您可以通过GitHub赞助商来支持我的工作。请注意,此选项仅适用于正在寻找简单支付渠道并且不希望支持的商业参与者。通过GITHUB赞助商提供支持是不可能维持增值税的合规性。请取消直接支持协议。
可以在堆栈溢出或字节好友邮件列表上提出一般问题,该邮件也可以作为问题的档案。当然,错误报告也将在商业计划之外考虑。对于开源项目,有时有可能会获得扩展的帮助,以使Byte Buddy进入使用。
Byte Buddy写在ASM的顶部,ASM是一个成熟且经过充分测试的图书馆,用于阅读和写作编译的Java课程。为了允许高级类型的操作,字节好友有意将ASM API展示给其用户。当然,直接使用ASM仍然是完全可选的,大多数用户很可能永远不会需要它。做出了这样的选择,使Byte Buddy的用户不限制在其高级功能上,而是可以在必要时大惊小怪地实现自定义实现。
ASM先前已更改其公共API,但从图书馆的版本4开始添加了API兼容性的机制。为了避免版本与此类旧版本发生冲突,字节好友将ASM依赖性重新包装到其自己的名称空间中。如果您想直接使用ASM,则byte-buddy-dep
伪像提供了一个字节伙伴的版本,具有明确的依赖性ASM。这样做时,您必须将字节好友和ASM重新包装到您的命名空间中,以避免版本冲突。
请注意此项目的安全政策。
Byte Buddy支持第五版和单个JAR中的所有JVM版本的执行。这样做是为了减轻通常需要支持未积极更新的旧应用程序或未知应用程序的Java代理的开发。为此,同时还支持现代Java,以及使用堆栈地图框架(Byte Buddy Ship的主要罐子)作为多用释放罐子的主要罐子,这些罐子中包含第五版和八版中的类文件。结果,字节好友的罐子尺寸较高。 JAR文件大小通常不是问题,因为大多数字节好友的类都不会加载。但是,在分发Java代理时,文件大小可能是一个问题。因此,由于已经需要将代理捆绑为一个JAR,因此建议删除基本的Java五版本,或者要么多释放的Java Java八版本的包含类文件,以减少此问题。为此,大多数构建插件都支持这一点,例如Maven Shade插件。
Byte Buddy已获得自由和业务友好的Apache许可证的许可,版本2.0 ,可在Github上免费获得。此外,根据3条规定BSD许可发布的字节捆绑分销捆绑包ASM。
Byte Buddy二进制文件出版给Maven Central和Jcenter的存储库。从字节伙伴1.10.3开始,可以针对此PGP公钥进行验证工件签名。可以根据该较旧的和较弱的证书来验证较旧的版本。
该项目是使用Maven构建的。从您的外壳中,克隆和构建该项目将是这样的:
git clone https://github.com/raphw/byte-buddy.git
cd byte-buddy
mvn package
在这些命令上,字节好友从github克隆并在您的机器上构建。 Root POM文件中列出了进一步的构建选项。可以使用至少6版6版的任何JDK构建字节好友。但是,建议使用至少版本8的JDK作为6和7版本的构建,需要使用未加密的HTTP。它的支持仅用于针对此JDK版本进行测试,并且可以使您接触到中间的攻击。因此,应避免这些构建。当前,Byte Buddy已在CI服务器上测试了JDK的6个及以上的版本。
请使用GitHub的问题跟踪器报告错误。进行代码时,请提供证明您功能功能或证明错误修复的测试用例。此外,请确保您不会打破任何现有的测试用例。如果可能的话,请花点时间写一些文档。对于功能请求或一般反馈,您也可以使用问题跟踪器或在我们的邮件列表中与我们联系。
由于一排专门的资源和对项目的关注,因此也可以进行字节好友的工作。请花点时间看看这些支持者及其产品。