如何使用 ByteBuddy 在运行时创建可调用原方法的动态方法代理

阿晨姑娘_7844

阿晨姑娘_7844

2026-04-01

982人浏览

原创

如何使用 ByteBuddy 在运行时创建可调用原方法的动态方法代理

本文详解如何借助 bytebuddy 的 rebase() 机制,在不丢失原始方法逻辑的前提下,为已有类的方法添加运行时前置校验逻辑,并确保能安全调用原方法(super call),适用于插件环境下的无侵入式增强。

本文详解如何借助 bytebuddy 的 rebase() 机制,在不丢失原始方法逻辑的前提下,为已有类的方法添加运行时前置校验逻辑,并确保能安全调用原方法(super call),适用于插件环境下的无侵入式增强。

在 ByteBuddy 中,“重定义(redefine)”与“重基(rebase)”是两个关键但易混淆的概念:

  • redefine() 会完全替换目标方法字节码,原始方法逻辑(即 super 行为)将不可访问;
  • rebase() 则会保留原方法体,并将其自动重命名为 *$original 形式的桥接方法,从而允许你在拦截逻辑中显式调用它——这正是实现“前置检查 + 条件性委托原逻辑”的核心前提。

✅ 正确方案:使用 rebase() 实现可调用原方法的运行时代理

以下是一个完整、可直接集成到插件中的示例,假设你要增强 com.example.Foo.bar(String, float, Integer) 方法:

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;

// 1. 安装 Java Agent(需在插件初始化早期调用)
ByteBuddyAgent.install();

// 2. 使用 rebase() 而非 redefine(),保留原方法可调用性
new ByteBuddy()
    .rebase(Foo.class) // ← 关键:不是 redefine()
    .method(ElementMatchers.named("bar")
        .and(ElementMatchers.takesArguments(String.class, float.class, Integer.class)))
    .intercept(MethodDelegation.to(BarInterceptor.class))
    .make()
    .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());

对应拦截器需支持条件调用原方法:

public class BarInterceptor {
    @RuntimeType
    public static Object intercept(
            @SuperCall(callOriginal = true) Callable> zuper, // 自动注入对原方法的可调用引用
            @AllArguments Object[] args,
            @This Object self) throws Exception {

        // ✅ 前置检查逻辑(例如权限/状态校验)
        if (!shouldProceed(args)) {
            System.out.println("[Proxy] Blocked call to bar() due to pre-check");
            return null; // 或抛出异常、返回默认值等
        }

        System.out.println("[Proxy] Proceeding to original bar()...");
        return zuper.call(); // ← 安全调用原始方法体(已由 rebase 自动保存)
    }

    private static boolean shouldProceed(Object[] args) {
        // 示例:检查第一个参数非空
        return args.length > 0 && args[0] != null;
    }
}

? 关键注解说明:

  • @SuperCall(callOriginal = true) 是 rebase() 场景下的黄金注解,它让 ByteBuddy 注入一个 Callable,封装了对重命名后原始方法(如 bar$original)的反射调用,语义上等价于 super.bar(...),且线程安全、零开销。
  • @AllArguments 和 @This 确保你能访问全部上下文,支撑灵活的业务判断。

⚠️ 注意事项与最佳实践

  • 加载时机至关重要:rebase() 必须在目标类被首次初始化之前完成(尤其在插件场景中)。由于你控制插件加载顺序,请确保代理逻辑在 Foo 类被任何其他插件或主程序加载前执行(例如在 Plugin#onLoad() 中触发)。
  • 避免重复 rebase:同一类多次 rebase() 可能导致方法名冲突(如 bar$original$original)。建议通过 AgentBuilder 做幂等注册,或加锁保障单次生效。
  • 不推荐手动 redefine() + SuperMethodCall:虽然 redefine() 也可配合 SuperMethodCall,但它要求目标方法未被内联或优化(JVM 层面不稳定),而 rebase() 是 ByteBuddy 官方推荐的、稳定支持 super 调用的方案。
  • 生产环境建议启用 AgentBuilder:对于更复杂的匹配(如按包名、注解筛选)、热更新容错及日志追踪,应迁移到 AgentBuilder 模式:
new AgentBuilder.Default()
    .type(named("com.example.Foo"))
    .transform((builder, typeDescription, classLoader, module, protectionDomain) ->
        builder.method(named("bar").and(takesArguments(String.class, float.class, Integer.class)))
               .intercept(MethodDelegation.to(BarInterceptor.class)))
    .installOnByteBuddyAgent();

✅ 总结

要实现“运行时方法代理 + 条件性调用原逻辑”,必须放弃 redefine(),改用 rebase() 配合 @SuperCall。该方案不仅语义清晰、线程安全,而且与 JVM 兼容性极佳,是 ByteBuddy 在 AOP、监控、灰度等场景中的标准实践。在多插件共存环境中,只需把控好类加载时序,即可实现零侵入、高可控的方法级行为增强。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

10357

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

7482

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

6552

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1124

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

928

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1356

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2729

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

20051

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1215

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.4万人学习