Java字节码增强库ByteBuddy

1.什么是ByteBuddy ?

ByteBuddy 是一个强大的 Java 字节码操作库,主要应用场景包括以下几个方面:

  1. 动态代理和 AOP(面向切面编程)
    • ByteBuddy 可以用来创建动态代理,替代 Java 自带的 java.lang.reflect.Proxy,并且支持非接口类的代理。
    • 常用于 AOP 框架中,例如在方法执行前后插入逻辑,实现日志记录、事务管理、权限校验等功能。
  2. 字节码增强
    • 可以对已有类的字节码进行增强,比如添加字段、方法或改变方法行为。
    • 适合用在框架和工具开发中,例如 Hibernate、Spring 等框架都可以通过 ByteBuddy 动态生成增强后的类,增加特定功能。
  3. Mock 框架
    • 在测试中,ByteBuddy 可以用于创建类的 Mock 对象,模拟实际业务类的行为。
    • 一些 Mock 框架,如 Mockito 也集成了 ByteBuddy,用于动态代理和方法拦截。
  4. 编写 Java Agent
    • Java Agent 是一种在 JVM 启动时加载的工具,ByteBuddy 非常适合用来编写 Java Agent 进行类的加载时增强。
    • 常用于性能监控、日志记录和诊断工具,可以动态修改和增强应用程序中的类。
  5. 热加载和动态类加载
    • ByteBuddy 可以帮助实现类的热加载,即在运行时动态替换类或方法,这对实现零停机更新、应用调试非常有用。
  6. 自定义序列化和反序列化
    • 可以在序列化和反序列化过程中动态修改类的行为,适合自定义的序列化需求。
  7. 跨语言调用和兼容性处理
    • 在多语言环境中,ByteBuddy 可以用于动态生成和修改类,以便支持特定的跨语言调用需求,或调整应用程序以支持不同版本的 Java。

2.代码工程

实验目的:

  1. 实现类生成
  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();
        }
    }
}

详细解析

  1. new ByteBuddy()
    • 初始化 ByteBuddy 实例,用于创建新的类或修改现有类。
  2. subclass(Object.class)
    • 创建一个动态类并指定它继承自 Object 类。
  3. name("com.example.HelloWorld")
    • 定义新类的完整限定名为 com.example.HelloWorld
  4. method(named("toString"))
    • 使用 named("toString") 匹配器来选择 toString 方法,以便后续对该方法进行拦截和自定义。
    • named 是 ByteBuddy 的方法匹配器,允许按名称匹配方法。
  5. intercept(FixedValue.value("Hello, ByteBuddy!"))
    • 使用 FixedValue 指定 toString 方法的返回值为固定值 "Hello, ByteBuddy!"
    • intercept 表示拦截 toString 方法并定义其新行为。在这里,FixedValue.value("Hello, ByteBuddy!") 表示 toString 方法将始终返回 "Hello, ByteBuddy!"
  6. make()
    • 构建字节码,并生成包含动态类定义的 DynamicType.Unloaded 实例。
  7. load(ByteBuddyExample.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
    • 将动态生成的类加载到 JVM 中,使用 WRAPPER 加载策略确保类加载不会冲突。
    • ClassLoadingStrategy.Default.WRAPPER 创建一个类加载器包装,以安全地加载该类。
  8. getLoaded()
    • 返回已加载的类的 Class 对象,赋值给 dynamicType 变量。
  9. dynamicType.getDeclaredConstructor().newInstance()
    • 使用反射创建 HelloWorld 类的实例。
  10. 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";
        }
    }
}

适配过程中的注意事项

  1. 使用 @BindingPriority
    • @BindingPriority 控制了多个候选方法的选择。当 Interceptor 中有多个方法符合条件时,优先级最高的将会执行。
    • 如果去掉 @BindingPriority,ByteBuddy 将根据默认优先级选择方法(通常是方法的声明顺序),或者在多个候选方法不明确时抛出异常。
  2. 方法签名匹配
    • ByteBuddy 根据方法签名匹配拦截方法。在 Interceptor 中添加重载方法时,只有签名精确匹配的方法才能成功拦截。
    • intercept(String, String) 方法有两个参数,但不会匹配 sayHello,因为 sayHello 没有参数。

代码执行流程

  1. 代理类创建 :ByteBuddy 生成 Foo 类的代理子类 FooProxy
  2. 方法拦截和委托
    • 代理类的所有方法调用都被拦截,并按照 @BindingPriority 注解的优先级委托给 Interceptor 的方法。
    • 由于 sayHello 没有参数,因此它匹配 sssintercept()intercept(),但 sssintercept() 的优先级更高。
  3. 输出结果 :调用 proxy.sayHello(),实际调用的是 Interceptor 中的 sssintercept(),因此输出 "ssss"

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

3.引用

相关推荐
bobz9652 分钟前
ovs patch port 对比 veth pair
后端
Asthenia041212 分钟前
Java受检异常与非受检异常分析
后端
uhakadotcom26 分钟前
快速开始使用 n8n
后端·面试·github
JavaGuide33 分钟前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz96543 分钟前
qemu 网络使用基础
后端
Asthenia04121 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04121 小时前
Spring 启动流程:比喻表达
后端
Asthenia04122 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua2 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫
致心2 小时前
记一次debian安装mariadb(带有迁移数据)
后端