网站首页 > java教程 正文
简介
简单地说,ByteBuddy 是一个用于在运行时动态生成 Java 类的库。
在这篇切中要害的文章中,我们将使用该框架来操作现有类,按需创建新类,甚至拦截方法调用。
依赖
让我们首先将依赖项添加到我们的项目中。对于基于 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.class 的 toString() 实现。
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");
}
猜你喜欢
- 2025-01-07 Python GUI 编程:tkinter 初学者入门指南——几何布局管理器 Place
- 2025-01-07 偷天换日,用JavaAgent欺骗你的JVM
- 2025-01-07 大数据必学Java基础(二):Java核心机制
- 2025-01-07 求你了,别再说 Java 对象都是在堆内存上分配空间了
- 2025-01-07 Java 虚拟机是什么?——探秘 JVM 的核心机制!
- 2025-01-07 5.2 JVM常见问题及面试题
- 2025-01-07 JVM实战—1.Java代码的运行原理
- 2025-01-07 浅谈字节码增强技术系列2-Asm与Cglib
- 2025-01-07 【JVM字节码】Class文件与字节码,机器码的关系
- 2025-01-07 虚拟机字节码操作黑科技工具简介
你 发表评论:
欢迎- 04-24Java Collections 工具类集合框架中常用算法解析
- 04-24桶排序的简单理解
- 04-24Java集合框架底层实现原理大揭秘
- 04-24Java 集合框架全面解析:选对数据结构,提升开发效率
- 04-24c#集合排序
- 04-24Java面试中常被问到的集合类深度解读
- 04-24VBA技术资料MF278:对集合进行排序
- 04-24Spring 最常用的 7 大类注解,史上最强整理
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)