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 对象。
相关推荐
fat house cat_24 分钟前
【redis】线程IO模型
java·redis
草捏子27 分钟前
状态机设计:比if-else优雅100倍的设计
后端
stein_java1 小时前
springMVC-10验证及国际化
java·spring
weixin_478689761 小时前
C++ 对 C 的兼容性
java·c语言·c++
LUCIAZZZ2 小时前
HikariCP数据库连接池原理解析
java·jvm·数据库·spring·springboot·线程池·连接池
考虑考虑2 小时前
Springboot3.5.x结构化日志新属性
spring boot·后端·spring
涡能增压发动积2 小时前
一起来学 Langgraph [第三节]
后端
sky_ph2 小时前
JAVA-GC浅析(二)G1(Garbage First)回收器
java·后端
涡能增压发动积2 小时前
一起来学 Langgraph [第二节]
后端
IDRSolutions_CN3 小时前
PDF 转 HTML5 —— HTML5 填充图形不支持 Even-Odd 奇偶规则?(第二部分)
java·经验分享·pdf·软件工程·团队开发