ByteBuddy动态代理

java17以后替换cglib方式的动态代理。他和cglib实现动态代理的优势是不用定义接口也不需要去实现接口,对现有的代码扩展更友好。
  • pom文件添加依赖
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <!-- 我用的最新的, 一般1.14.0以上都没问题 -->
            <version>1.15.1</version>
        </dependency>
  1. 先创建要被代理的类
public class TargetClass {
    public void performAction() {
        System.out.println("原始方法: performAction");
    }

    public String sayHello(String name) {
        return "Hello, " + name;
    }
}
  1. 创建拦截器
import net.bytebuddy.implementation.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

/**
 * 通用方法拦截器
 */
public class GenericInterceptor {

    @RuntimeType
    public static Object intercept(@Origin Method method,
                                   @AllArguments Object[] args,
                                   @This Object obj,
                                   @SuperCall Callable<?> callable) throws Exception {
        // 获取动态添加的字段值
        String proxyField = (String) obj.getClass().getField("proxyField").get(obj);
        System.out.println("[拦截] proxyField 值: " + proxyField);

        // 修改字段值
        System.out.println("[通用拦截] 修改字段proxyField值: Modified by interceptor" );
        obj.getClass().getField("proxyField").set(obj, "Modified by interceptor");

        System.out.println("[通用拦截] 方法开始: " + method.getName());
        Object result = callable.call(); // 执行原始方法
        System.out.println("[通用拦截] 方法结束: " + method.getName());
        return result;
    }
}
  1. 代理工具
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 通用 Byte Buddy 动态代理工具类
 */
public class ByteBuddyProxy {

    /**
     * 创建一个代理对象
     *
     * @param targetClass 目标类类型
     * @param interceptor 拦截器类(需使用 Byte Buddy 注解定义逻辑)
     * @param <T>         泛型
     * @return 代理实例
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws InstantiationException
     */
    public static <T> T createProxy(Class<T> targetClass, Class<?> interceptor) throws Exception {

        Class<?> dynamicType = new ByteBuddy()
                .subclass(targetClass)
                // ElementMatchers.any() 拦截所有方法
                .method(ElementMatchers.any())
                //  MethodDelegation.to(interceptor) 拦截器类 方法增强
                .intercept(MethodDelegation.to(interceptor))
                // 动态的添加字段。这里可以放到参数里面,然后由调用方传递进来要添加什么参数
                .defineField("proxyField", String.class, Visibility.PUBLIC) // 可选:添加字段
                .defineField("abc", String.class, Visibility.PUBLIC) // 可选:添加字段
                .make()
                .load(targetClass.getClassLoader())
                .getLoaded();
                // 支持构造实例
//                .getDeclaredConstructor()
//                .newInstance();

        Constructor<?> constructor = dynamicType.getDeclaredConstructor();
        Object instance = constructor.newInstance();

        // 现在就可以 调用方法触发拦截逻辑 了
        // ((TargetClass) instance).performAction();
        // 设置字段值
        System.out.println("[通用拦截] 设置字段proxyField值: Hello Dynamic Field" );
        dynamicType.getField("proxyField").set(instance, "Hello Dynamic Field");

        System.out.println("[通用拦截] 设置字段abc值: def" );
        dynamicType.getField("abc").set(instance, "def");
        return (T) instance;

    }
}
  1. 一个测试类用来测试动态代理
public class ByteBuddyGenericExample {
    public static void main(String[] args) throws Exception {
        // 创建代理对象
        TargetClass proxyInstance = ByteBuddyProxy
                .createProxy(TargetClass.class, GenericInterceptor.class);

        // 调用方法
        proxyInstance.performAction();
        String result = proxyInstance.sayHello("World");
        System.out.println("返回值: " + result);

        String abc = (String) proxyInstance.getClass().getField("abc").get(proxyInstance);
        System.out.println("last在打印一遍proxyField: " + abc);
    }
}