java 代理

一、Java 中的动态代理类型

Java 中常见的动态代理有两种主要类型:

  1. JDK 动态代理
  2. CGLIB 动态代理

二、JDK 动态代理

✨ 实现方式:

使用 Java 标准库中的 java.lang.reflect.ProxyInvocationHandler

✅ 优点:

  • 不依赖第三方库。

  • 简单轻量,标准库支持。

  • 非常适合接口驱动设计的系统(如 DAO 接口)。

❌ 缺点:

  • 只能代理接口(不能代理普通类)。

✅ 使用场景:

  • 接口型编程,如 Spring 中的基于接口的 AOP。
  • MyBatis Mapper 接口。

流程图

sequenceDiagram participant Client as 客户端代码 participant Proxy as 动态代理对象 (proxy) participant Handler as InvocationHandler 实例 participant Target as 真实目标对象 Client->>Proxy: 调用 proxy.method() Proxy->>Handler: 调用 invoke(proxy, method, args) Handler->>Handler: 前置增强(日志、权限等) Handler->>Target: 反射调用目标方法 method.invoke(target, args) Target-->>Handler: 返回结果 Handler->>Handler: 后置增强(日志、事务等) Handler-->>Proxy: 返回结果 Proxy-->>Client: 返回最终结果### **Mermaid**

示例代码:

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义一个接口,表示服务的功能
interface UserService {
    // 接口方法:打招呼
    void sayHello(String name);
}

// 实现接口的类,提供具体的功能实现
class UserServiceImpl implements UserService {
    // 实现接口中的方法
    public void sayHello(String name) {
        System.out.println("Hello, " + name); // 打印问候语
    }
}

  
// InvocationHandler 的实现类,用于增强目标对象的方法
class LoggingHandler implements InvocationHandler {
    private final Object target; // 被代理的目标对象
    // 构造函数,接收目标对象
    public LoggingHandler(Object target) {
        this.target = target;
    }
    // invoke 方法会在代理对象调用方法时被触发
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法调用前打印日志信息
        System.out.println("Before method: " + method.getName()); // 打印方法名
        // 调用目标对象的实际方法,并传递参数
        Object result = method.invoke(target, args);
        // 在方法调用后打印日志信息
        System.out.println("After method: " + method.getName()); // 打印方法名
        return result; // 返回方法执行的结果
    }
}

// 主类,用于测试动态代理功能
public class Main {
    public static void main(String[] args) {
        // 创建目标对象(实际的服务实现)
        UserService realService = new UserServiceImpl();
        // 使用 Proxy.newProxyInstance 创建代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
            realService.getClass().getClassLoader(), // 目标对象的类加载器
            new Class[]{UserService.class},          // 目标对象实现的接口数组
            new LoggingHandler(realService)         // 自定义的 InvocationHandler 实现
        );
        // 调用代理对象的方法,实际会触发 LoggingHandler 中的 invoke 方法
        proxy.sayHello("Alice");
    }
}

输出结果

sql 复制代码
Before method: sayHello 
Hello, Alice 
After method: sayHello

三、CGLIB 动态代理

✨ 实现方式:

通过继承目标类,重写方法来实现代理。底层使用字节码生成技术(如 ASM)动态创建子类。

常用库:cglib(如在 Spring AOP 中用于非接口代理)。

✅ 优点:

  • 可以代理普通类(非接口)

  • 性能比 JDK 动态代理略高(尤其是大量方法调用场景)。

❌ 缺点:

  • 被代理类 不能是 final 类

  • 被代理方法 不能是 final/static/private

  • 依赖第三方库(如 CGLIB 或 ByteBuddy)。

✅ 使用场景:

  • 没有接口的类。
  • 需要代理已有类(如 Controller、Service 等 Bean)。

流畅图

sequenceDiagram participant Client as 客户端代码 participant Proxy as CGLIB 生成的子类对象 participant Interceptor as MethodInterceptor 实例 participant SuperClass as 目标类(被代理对象) Client->>Proxy: 调用 proxy.method() Proxy->>Interceptor: 调用 intercept(proxy, method, args, methodProxy) Interceptor->>Interceptor: 前置增强逻辑(日志、事务等) Interceptor->>SuperClass: methodProxy.invokeSuper(proxy, args) SuperClass-->>Interceptor: 返回原始方法执行结果 Interceptor->>Interceptor: 后置增强逻辑 Interceptor-->>Proxy: 返回最终结果 Proxy-->>Client: 返回最终结果

示例代码:

java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 目标类,不需要实现接口
class UserService {
    // 目标方法:打招呼
    public void sayHello(String name) {
        System.out.println("Hello, " + name); // 打印问候语
    }
}

// MethodInterceptor 的实现类,用于增强目标类的方法
class LoggingInterceptor implements MethodInterceptor {
    /**
     * intercept 方法会在代理对象调用方法时被触发
     * @param obj 代理对象本身
     * @param method 被拦截的方法对象
     * @param args 方法参数
     * @param proxy 方法代理对象,用于调用父类方法
     * @return 方法执行的结果
     * @throws Throwable 抛出异常
     */

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在方法调用前打印日志信息
        System.out.println("Before method: " + method.getName()); // 打印方法名
        // 使用 MethodProxy 调用父类(目标类)的实际方法
        Object result = proxy.invokeSuper(obj, args);
        // 在方法调用后打印日志信息
        System.out.println("After method: " + method.getName()); // 打印方法名
        return result; // 返回方法执行的结果
    }
}

// 主类,用于测试 CGLIB 动态代理功能
public class Main {
    public static void main(String[] args) {
        // 创建 Enhancer 对象,用于生成代理类
        Enhancer enhancer = new Enhancer();
        // 设置目标类作为父类,CGLIB 会动态生成该类的子类,注意这里是调用 invokeSuper 而不是 invoke,否则死循环,invokeSuper方法会直接调用目标类(即被代理的类)的方法实现,而不会再次触发代理逻辑。这是因为在 CGLIB 中,代理类是目标类的子类,`invokeSuper` 实际上调用了父类的方法
        enhancer.setSuperclass(UserService.class);
        // 设置回调函数,使用自定义的 MethodInterceptor 实现
        enhancer.setCallback(new LoggingInterceptor());
        // 创建代理对象,实际是目标类的子类实例
        UserService proxy = (UserService) enhancer.create();
        // 调用代理对象的方法,实际会触发 LoggingInterceptor 中的 intercept 方法
        proxy.sayHello("Bob");
    }
}

输出结果

sql 复制代码
Before method: sayHello 
Hello, Alice 
After method: sayHello

四、JDK 与 CGLIB 的对比总结

对比项 JDK 动态代理 CGLIB 动态代理
代理对象 接口
是否要求接口
底层实现 反射 + Proxy 字节码生成(ASM)
性能 较低 较高
限制 只能代理接口 类不能是 final,方法不能是 final/private ,通过继承
框架支持 Java 标准库 需引入第三方库
使用示例 MyBatis Mapper、Spring AOP Spring AOP、Hibernate、RPC 框架等

五、其他动态代理方式(了解)

  1. ByteBuddy

    • 更现代、灵活的字节码操作库(替代 CGLIB)。
    • 用于 JDK 代理或更复杂的类构建。
    • Spring Boot 2.x+ 使用 ByteBuddy 生成代理类。
  2. Javassist

    • 操作 Java 字节码,生成新类或修改已有类。
  3. ASM

    • 底层字节码编辑器,CGLIB 的基础。
    • 性能高,使用复杂,一般用于框架开发。

六、动态代理的常见使用场景总结

  • AOP 切面编程:日志、事务、权限校验。
  • 远程调用代理(RPC) :如 Dubbo。
  • 懒加载与缓存:Hibernate。
  • 拦截器链实现
  • Mock 框架:如 Mockito 生成 mock 对象。
相关推荐
無限進步D3 小时前
Java 运行原理
java·开发语言·入门
難釋懷3 小时前
安装Canal
java
是苏浙3 小时前
JDK17新增特性
java·开发语言
不光头强4 小时前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp7 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多7 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood7 小时前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员7 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai