1.什么是ByteBuddy ?
ByteBuddy 是一个强大的 Java 字节码操作库,主要应用场景包括以下几个方面:
- 动态代理和 AOP(面向切面编程):
- ByteBuddy 可以用来创建动态代理,替代 Java 自带的
java.lang.reflect.Proxy
,并且支持非接口类的代理。 - 常用于 AOP 框架中,例如在方法执行前后插入逻辑,实现日志记录、事务管理、权限校验等功能。
- ByteBuddy 可以用来创建动态代理,替代 Java 自带的
- 字节码增强:
- 可以对已有类的字节码进行增强,比如添加字段、方法或改变方法行为。
- 适合用在框架和工具开发中,例如 Hibernate、Spring 等框架都可以通过 ByteBuddy 动态生成增强后的类,增加特定功能。
- Mock 框架:
- 在测试中,ByteBuddy 可以用于创建类的 Mock 对象,模拟实际业务类的行为。
- 一些 Mock 框架,如 Mockito 也集成了 ByteBuddy,用于动态代理和方法拦截。
- 编写 Java Agent:
- Java Agent 是一种在 JVM 启动时加载的工具,ByteBuddy 非常适合用来编写 Java Agent 进行类的加载时增强。
- 常用于性能监控、日志记录和诊断工具,可以动态修改和增强应用程序中的类。
- 热加载和动态类加载:
- ByteBuddy 可以帮助实现类的热加载,即在运行时动态替换类或方法,这对实现零停机更新、应用调试非常有用。
- 自定义序列化和反序列化:
- 可以在序列化和反序列化过程中动态修改类的行为,适合自定义的序列化需求。
- 跨语言调用和兼容性处理:
- 在多语言环境中,ByteBuddy 可以用于动态生成和修改类,以便支持特定的跨语言调用需求,或调整应用程序以支持不同版本的 Java。
2.代码工程
实验目的:
- 实现类生成
- 方法拦截和增强
pom.xml
xml
代码解读
复制代码
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>Java-demo</artifactId> <groupId>com.et</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>ByteBuddy</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.14.5</version> </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy-agent</artifactId> <version>1.14.5</version> </dependency> </dependencies> </project>
动态生成类
typescript
代码解读
复制代码
package com.et; import net.bytebuddy.ByteBuddy; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.FixedValue; import static net.bytebuddy.matcher.ElementMatchers.named; public class ByteBuddyExample { public static void main(String[] args) { try { // Use ByteBuddy to create a new class Class<?> dynamicType = new ByteBuddy() .subclass(Object.class) // Inherit from the Object class .name("com.example.HelloWorld") // Define the class name .method(named("toString")) // Define the method to intercept .intercept(FixedValue.value("Hello, ByteBuddy!")) // Method returns a fixed value .make() .load(ByteBuddyExample.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded(); // Create an instance of the class and call the toString method Object instance = dynamicType.getDeclaredConstructor().newInstance(); System.out.println(instance.toString()); // Output: Hello, ByteBuddy! } catch (Exception e) { e.printStackTrace(); } } }
详细解析
new ByteBuddy()
:- 初始化 ByteBuddy 实例,用于创建新的类或修改现有类。
subclass(Object.class)
:- 创建一个动态类并指定它继承自
Object
类。
- 创建一个动态类并指定它继承自
name("com.example.HelloWorld")
:- 定义新类的完整限定名为
com.example.HelloWorld
。
- 定义新类的完整限定名为
method(named("toString"))
:- 使用
named("toString")
匹配器来选择toString
方法,以便后续对该方法进行拦截和自定义。 named
是 ByteBuddy 的方法匹配器,允许按名称匹配方法。
- 使用
intercept(FixedValue.value("Hello, ByteBuddy!"))
:- 使用
FixedValue
指定toString
方法的返回值为固定值"Hello, ByteBuddy!"
。 intercept
表示拦截toString
方法并定义其新行为。在这里,FixedValue.value("Hello, ByteBuddy!")
表示toString
方法将始终返回"Hello, ByteBuddy!"
。
- 使用
make()
:- 构建字节码,并生成包含动态类定义的
DynamicType.Unloaded
实例。
- 构建字节码,并生成包含动态类定义的
load(ByteBuddyExample.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
:- 将动态生成的类加载到 JVM 中,使用 WRAPPER 加载策略确保类加载不会冲突。
ClassLoadingStrategy.Default.WRAPPER
创建一个类加载器包装,以安全地加载该类。
getLoaded()
:- 返回已加载的类的
Class
对象,赋值给dynamicType
变量。
- 返回已加载的类的
dynamicType.getDeclaredConstructor().newInstance()
:- 使用反射创建
HelloWorld
类的实例。
- 使用反射创建
instance.toString()
:
- 调用
toString
方法。 - 由于
toString
方法已被拦截并固定返回"Hello, ByteBuddy!"
,因此输出结果将是"Hello, ByteBuddy!"
。
方法拦截和增强
typescript
代码解读
复制代码
package com.et; import net.bytebuddy.ByteBuddy; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.BindingPriority; import net.bytebuddy.matcher.ElementMatchers; import java.lang.reflect.InvocationTargetException; public class ByteBuddyProxyExample { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { // Create an original object Foo foo = new Foo(); // Use ByteBuddy to create a proxy Foo proxy = (Foo) new ByteBuddy() .subclass(Foo.class) .method(ElementMatchers.any()) // Intercept all methods .intercept(MethodDelegation.to(new Interceptor())) // Delegate to the Interceptor class .make() .load(Foo.class.getClassLoader()) .getLoaded() .getDeclaredConstructor() .newInstance(); // Call the method System.out.println(proxy.sayHello()); } public static class Foo { public String sayHello() { return "Hello from Foo"; } } public static class Interceptor { @BindingPriority(3) public String sssintercept() { return "ssss"; } @BindingPriority(2) public String intercept() { return "Hello from Interceptor"; } public String intercept(String sss,String bbbb) { return " two parameters"; } } }
适配过程中的注意事项
- 使用
@BindingPriority
:@BindingPriority
控制了多个候选方法的选择。当Interceptor
中有多个方法符合条件时,优先级最高的将会执行。- 如果去掉
@BindingPriority
,ByteBuddy 将根据默认优先级选择方法(通常是方法的声明顺序),或者在多个候选方法不明确时抛出异常。
- 方法签名匹配:
- ByteBuddy 根据方法签名匹配拦截方法。在
Interceptor
中添加重载方法时,只有签名精确匹配的方法才能成功拦截。 - 如
intercept(String, String)
方法有两个参数,但不会匹配sayHello
,因为sayHello
没有参数。
- ByteBuddy 根据方法签名匹配拦截方法。在
代码执行流程
- 代理类创建:ByteBuddy 生成
Foo
类的代理子类FooProxy
。 - 方法拦截和委托:
- 代理类的所有方法调用都被拦截,并按照
@BindingPriority
注解的优先级委托给Interceptor
的方法。 - 由于
sayHello
没有参数,因此它匹配sssintercept()
和intercept()
,但sssintercept()
的优先级更高。
- 代理类的所有方法调用都被拦截,并按照
- 输出结果:调用
proxy.sayHello()
,实际调用的是Interceptor
中的sssintercept()
,因此输出"ssss"
。
以上只是一些关键代码,所有代码请参见下面代码仓库