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 对象。
相关推荐
TPBoreas3 小时前
Jenkins 改完端口号启动不起来了
java·开发语言
金斗潼关3 小时前
SpringCloud GateWay网关
java·spring cloud·gateway
秋名RG4 小时前
深入解析建造者模式(Builder Pattern)——以Java实现复杂对象构建的艺术
java·开发语言·建造者模式
eternal__day4 小时前
Spring Boot 实现验证码生成与校验:从零开始构建安全登录系统
java·spring boot·后端·安全·java-ee·学习方法
陈大爷(有低保)5 小时前
swagger3融入springboot
java
海天胜景5 小时前
HTTP Error 500.31 - Failed to load ASP.NET Core runtime
后端·asp.net
海天胜景5 小时前
Asp.Net Core IIS发布后PUT、DELETE请求错误405
数据库·后端·asp.net
源码云商7 小时前
Spring Boot + Vue 实现在线视频教育平台
vue.js·spring boot·后端
weixin_376934637 小时前
JDK Version Manager (JVMS)
java·开发语言
月月大王7 小时前
easyexcel导出动态写入标题和数据
java·服务器·前端