专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java字节码增强技术 ByteBuddy

temp10 2025-01-07 15:57:38 java教程 11 ℃ 0 评论

简介

简单地说,ByteBuddy 是一个用于在运行时动态生成 Java 类的库。

在这篇切中要害的文章中,我们将使用该框架来操作现有类,按需创建新类,甚至拦截方法调用。

Java字节码增强技术 ByteBuddy

依赖

让我们首先将依赖项添加到我们的项目中。对于基于 Maven 的项目,我们需要将此依赖项添加到我们的 pom.xml 中:

<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.6</version>
</dependency>

在运行时创建 Java 类

让我们首先通过子类化现有类来创建一个动态类。我们将看看经典的 Hello World 项目。

在此示例中,我们创建一个类型(Class),它是 Object.class 的字类,并重写 toString() 方法:

DynamicType.Unloaded unloadedType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.isToString())
.intercept(FixedValue.value("Hello World ByteBuddy!"))
.make();

我们刚刚做的是创建一个 ByteBuddy 的实例。然后,我们使用 subclass() API 来扩展 Object.class,并使用 ElementMatchers 选择了超类 (Object.class) 的 toString()。

最后,使用 intercept() 方法,我们提供了 toString() 的实现并返回一个固定值。

make() 方法触发新类的生成。

此时,我们的类已经创建,但尚未加载到 JVM 中。它由 DynamicType.Unloaded 的实例表示,该实例是生成类型的二进制形式。

因此,我们需要先将生成的类加载到 JVM 中,然后才能使用它:

Class<?> dynamicType = unloadedType.load(getClass()
.getClassLoader())
.getLoaded();

现在,我们可以实例化 dynamicType 并在其上调用 toString() 方法:

assertEquals(
dynamicType.newInstance().toString(), "Hello World ByteBuddy!");

请注意,调用 dynamicType.toString() 将不起作用,因为这只会调用 ByteBuddy.classtoString() 实现。

newInstance() 是一个 Java 反射方法,用于创建此 ByteBuddy 对象所表示类型的新实例;以类似于将 new 关键字与 no-arg 构造函数一起使用的方式。

方法委托和自定义逻辑

在前面的示例中,我们从 toString() 方法返回一个固定值。

实际上,应用程序需要比这更复杂的逻辑。促进自定义逻辑并将其预配到动态类型的一种有效方法是委派方法调用。

让我们创建一个动态类型,该类型子类化了具有 sayHelloFoo() 方法的 Foo.class

public class Foo {
public String sayHelloFoo() {
return "Hello in Foo!";
}
}

此外,让我们创建另一个类 Bar,其静态 sayHelloBar() 的签名和返回类型与 sayHelloFoo() 相同:

public class Bar {
public static String sayHelloBar() {
return "Holla in Bar!";
}
}

现在,让我们使用 ByteBuddy 的 DSL 将 sayHelloFoo() 的所有调用委托给 sayHelloBar()。 这允许我们在运行时为新创建的类提供用纯 Java 编写的自定义逻辑:

@Test
void testSayHello() throws InstantiationException, IllegalAccessException {
String r = new ByteBuddy()
.subclass(Foo.class)
.method(named("sayHelloFoo")
.and(isDeclaredBy(Foo.class)
.and(returns(String.class))))
.intercept(MethodDelegation.to(Bar.class))
.make()
.load(getClass().getClassLoader())
.getLoaded()
.newInstance()
.sayHelloFoo();
assertEquals(r, Bar.sayHelloBar());
}

调用 sayHelloFoo() 将相应地调用 sayHelloBar()。

ByteBuddy如何知道要调用Bar.class中的哪个方法?它根据方法签名、返回类型、方法名称和注释选择匹配的方法。

sayHelloFoo() 和 sayHelloBar() 方法的名称不同,但它们具有相同的方法签名和返回类型。

如果 Bar.class 中有多个可调用方法,并且签名和返回类型匹配,我们可以使用@BindingPriority注解来解决歧义。

@BindingPriority采用整数参数 – 整数值越高,调用特定实现的优先级就越高。因此,在下面的代码片段中,sayHelloBar() 将优先于 sayBar():

public class Bar {
@BindingPriority(3)
public static String sayHelloBar() {
return "Holla in Bar!";
}
@BindingPriority(2)
public static String sayBar() {
return "bar";
}
}

方法和字段定义

我们已经能够重写在动态类型的超类中声明的方法。让我们更进一步,将一个新方法(和一个字段)添加到我们的类中。

我们将使用 Java 反射来调用动态创建的方法:

Class<?> type = new ByteBuddy()
.subclass(Object.class)
.name("MyClassName")
.defineMethod("custom", String.class, Modifier.PUBLIC)
.intercept(MethodDelegation.to(Bar.class))
.defineField("x", String.class, Modifier.PUBLIC)
.make()
.load(
getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
Method m = type.getDeclaredMethod("custom", null);
assertEquals(m.invoke(type.newInstance()), Bar.sayHelloBar());
assertNotNull(type.getDeclaredField("x"));

我们创建了一个名为 MyClassName 的类,它是 Object.class 的子类。然后,我们定义一个方法 custom,它返回一个 String 并具有一个公共访问修饰符。

就像我们在前面的示例中所做的那样,我们通过截获对它的调用并将其委托给我们在本教程前面创建的 Bar.class 来实现我们的方法。

@Test
void create() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<?> type = new ByteBuddy()
.subclass(Object.class)
.name("MyClassName")
.defineMethod("custom", String.class, Modifier.PUBLIC)
.intercept(MethodDelegation.to(Bar.class))
.defineField("x", String.class, Modifier.PUBLIC)
.make()
.load(
getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
Method m = type.getDeclaredMethod("custom", null);
assertEquals(m.invoke(type.newInstance()), Bar.sayHelloBar());
assertNotNull(type.getDeclaredField("x"));
}

重新定义现有类

虽然我们一直在使用动态创建的类,但我们也可以使用已经加载的类。这可以通过重新定义(或重新变基)现有类并使用 ByteBuddyAgent 将它们重新加载到 JVM 中来完成。

首先,让我们将 ByteBuddyAgent 添加到我们的 pom.xml 中:

<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.7.1</version>
</dependency>

现在,让我们重新定义我们之前在 Foo.class 中创建的 sayHelloFoo() 方法:

@Test
void installHello() {
ByteBuddyAgent.install();
new ByteBuddy()
.redefine(Foo.class)
.method(named("sayHelloFoo"))
.intercept(FixedValue.value("Hello Foo Redefined"))
.make()
.load(
Foo.class.getClassLoader(),
ClassReloadingStrategy.fromInstalledAgent());
Foo f = new Foo();
assertEquals(f.sayHelloFoo(), "Hello Foo Redefined");
}

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表