在 Java 开发中,代理模式(Proxy Pattern) 是设计模式中的“瑞士军刀”,广泛应用于 AOP(面向切面编程)、RPC 框架(如 Dubbo、gRPC)、Spring 事务管理、MyBatis Mapper 绑定等核心场景。

本文将深入剖析 Java 中的三种主要代理实现方式:静态代理JDK 动态代理CGLIB 动态代理。我们将通过原理图解、完整的代码示例以及底层机制分析,帮助你彻底掌握这一核心技术。


一、什么是代理模式?

代理模式的核心思想是:为其他对象提供一种代理以控制对这个对象的访问

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用,负责预处理、过滤消息、转发请求或事后处理。

核心角色:

  1. 抽象主题(Subject):定义真实主题和代理主题的共同接口(对于 CGLIB 则是父类)。
  2. 真实主题(Real Subject):被代理的真实对象,包含核心业务逻辑。
  3. 代理主题(Proxy):代理对象,内部持有真实主题的引用(或继承自真实主题),负责增强逻辑。

二、静态代理 (Static Proxy)

1. 原理

静态代理是指在编译期就已经确定了代理类与被代理类的关系。代理类和被代理类实现相同的接口,代理类中持有被代理类的引用,并在调用方法前后添加额外的逻辑。

特点:

  • 优点:代码简单,易于理解,无需依赖第三方库,性能略好(无反射开销)。
  • 缺点:扩展性差。每增加一个接口或方法,都需要创建一个新的代理类,导致代码冗余,维护成本高(违反开闭原则)。

2. 代码示例

假设我们有一个用户服务接口 UserService

第一步:定义抽象主题(接口)

public interface UserService {
    void addUser(String name);
    void deleteUser(int id);
}

第二步:定义真实主题(实现类)

public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("【真实业务】正在添加用户: " + name);
        try { Thread.sleep(500); } catch (InterruptedException e) {}
        System.out.println("【真实业务】用户添加成功");
    }

    @Override
    public void deleteUser(int id) {
        System.out.println("【真实业务】正在删除用户 ID: " + id);
    }
}

第三步:定义静态代理类

public class UserServiceStaticProxy implements UserService {
    private final UserService target; // 持有真实对象

    public UserServiceStaticProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void addUser(String name) {
        beforeMethod(); 
        target.addUser(name); // 调用真实方法
        afterMethod(); 
    }

    @Override
    public void deleteUser(int id) {
        beforeMethod();
        target.deleteUser(id);
        afterMethod();
    }

    private void beforeMethod() {
        System.out.println("--- [静态代理] 开始事务 / 记录日志 ---");
    }

    private void afterMethod() {
        System.out.println("--- [静态代理] 提交事务 / 清理资源 ---");
    }
}

第四步:测试运行

public class TestStaticProxy {
    public static void main(String[] args) {
        UserService realService = new UserServiceImpl();
        UserService proxy = new UserServiceStaticProxy(realService);
        
        proxy.addUser("张三");
    }
}

三、JDK 动态代理 (JDK Dynamic Proxy)

1. 原理

JDK 动态代理是指在运行期才创建代理类。它利用 java.lang.reflect.Proxy 类和 InvocationHandler 接口。

  • 限制:只能代理实现了接口的类。
  • 机制:生成的代理类继承了 java.lang.reflect.Proxy 并实现了目标接口。所有方法调用都会转发到 InvocationHandler.invoke() 方法中。

2. 代码示例

定义 InvocationHandler 实现类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JdkProxyHandler implements InvocationHandler {
    private final Object target; 

    public JdkProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("--- [JDK 动态代理] 前置增强:开启事务 ---");
        
        // 执行真实方法 (反射调用)
        Object result = method.invoke(target, args);
        
        System.out.println("--- [JDK 动态代理] 后置增强:提交事务 ---");
        return result;
    }
}

测试运行

import java.lang.reflect.Proxy;

public class TestJdkProxy {
    public static void main(String[] args) {
        UserService realService = new UserServiceImpl();

        // 生成代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
                realService.getClass().getClassLoader(),
                realService.getClass().getInterfaces(),
                new JdkProxyHandler(realService)
        );

        proxy.addUser("李四");
    }
}

四、CGLIB 动态代理 (Code Generation Library)

1. 原理

当目标类没有实现任何接口时,JDK 动态代理就无法使用了。这时就需要 CGLIB(Code Generation Library)。

  • 机制:CGLIB 通过继承目标类,在内存中动态生成一个子类。它重写父类的所有非 final 方法,并在重写的方法中插入增强逻辑(使用 ASM 字节码框架)。
  • 限制
    1. 不能代理 final 类(因为无法继承)。
    2. 不能代理 final 方法(因为无法重写)。
  • 依赖:需要引入第三方库(Maven 坐标:cglibspring-core,因为 Spring 内置了 CGLIB)。

2. 代码示例

为了演示 CGLIB,我们需要一个没有实现接口的具体类。

第一步:定义无接口的真实类

// 注意:这个类没有实现任何接口
public class OrderService {
    
    public void createOrder(String orderNo) {
        System.out.println("【真实业务-CGLIB】正在创建订单: " + orderNo);
        try { Thread.sleep(500); } catch (InterruptedException e) {}
        System.out.println("【真实业务-CGLIB】订单创建成功");
    }

    public void cancelOrder(String orderNo) {
        System.out.println("【真实业务-CGLIB】正在取消订单: " + orderNo);
    }
    
    // final 方法无法被 CGLIB 代理增强
    public final void logInfo() {
        System.out.println("这是一条无法被代理的日志");
    }
}

第二步:定义 MethodInterceptor 实现类 CGLIB 使用 MethodInterceptor 接口,类似于 JDK 的 InvocationHandler

(注:以下代码需要引入 CGLIB 依赖。如果是 Maven 项目,请在 pom.xml 中添加:)

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

Java 代码实现:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibProxyHandler implements MethodInterceptor {

    /**
     * 创建代理对象的方法
     */
    public static Object getProxyInstance(Class<?> targetClass) {
        Enhancer enhancer = new Enhancer();
        // 设置父类(即目标类)
        enhancer.setSuperclass(targetClass);
        // 设置回调函数(即拦截器)
        enhancer.setCallback(new CglibProxyHandler());
        // 创建代理对象
        return enhancer.create();
    }

    /**
     * 拦截方法
     * @param obj 代理对象本身
     * @param method 被拦截的方法
     * @param args 方法参数
     * @param proxy 用于调用父类(真实对象)方法的代理
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("--- [CGLIB 动态代理] 前置增强:权限校验 ---");

        // 调用父类(真实对象)的方法
        // 注意:这里使用 proxy.invokeSuper 而不是 method.invoke,效率更高且避免死循环
        Object result = proxy.invokeSuper(obj, args);

        System.out.println("--- [CGLIB 动态代理] 后置增强:记录操作日志 ---");
        return result;
    }
}

第三步:测试运行

public class TestCglibProxy {
    public static void main(String[] args) {
        // 1. 获取代理对象 (传入的是 Class 对象,而不是实例)
        OrderService proxy = (OrderService) CglibProxyHandler.getProxyInstance(OrderService.class);

        // 2. 调用方法
        proxy.createOrder("ORD-2026-001");
        
        System.out.println("----------------");
        
        // 3. 尝试调用 final 方法 (不会被增强)
        proxy.logInfo();
    }
}

输出结果预期:

--- [CGLIB 动态代理] 前置增强:权限校验 ---
【真实业务-CGLIB】正在创建订单: ORD-2026-001
【真实业务-CGLIB】订单创建成功
--- [CGLIB 动态代理] 后置增强:记录操作日志 ---
----------------
这是一条无法被代理的日志

注意:logInfo 方法是 final 的,所以 CGLIB 无法重写它,也就无法添加代理逻辑,直接执行了原方法。


五、三种代理方式深度对比

特性静态代理JDK 动态代理CGLIB 动态代理
生成时机编译期运行期运行期
实现方式手动编写类,实现相同接口Proxy 类 + InvocationHandler,实现相同接口继承目标类,重写方法 (Enhancer + MethodInterceptor)
依赖要求目标类必须实现接口目标类不能有 final 修饰 (需第三方库)
代码冗余高 (每个接口需一个代理类)低 (通用 handler)低 (通用 interceptor)
性能最高 (直接调用)较高 (JDK 8+ 优化后接近静态,仍有反射开销)较高 (使用 FastClass 机制,比纯反射快,但创建代理对象较慢)
应用场景极少使用,仅用于教学或极简单场景Spring AOP 默认策略 (有接口时)Spring AOP 备选策略 (无接口时)
底层技术Java 语言特性Java ReflectionASM 字节码生成

核心区别总结

  1. 接口依赖:JDK 动态代理强依赖接口;CGLIB 通过继承绕过接口限制,但受限于 final
  2. 灵活性:动态代理(JDK & CGLIB)完美解决了静态代理的代码爆炸问题,实现了业务逻辑与横切关注点的解耦。
  3. Spring 的选择
    • 在 Spring AOP 中,如果 Bean 实现了接口,默认使用 JDK 动态代理
    • 如果 Bean 没有实现接口,Spring 会自动切换到 CGLIB
    • 可以通过配置 @EnableAspectJAutoProxy(proxyTargetClass = true) 强制 Spring 始终使用 CGLIB。

六、底层原理深挖(进阶)

1. JDK 动态代理的类结构

生成的代理类(如 $Proxy0)大致如下:

public final class $Proxy0 extends Proxy implements UserService {
    private InvocationHandler h;
    
    // 构造函数
    public $Proxy0(InvocationHandler h) { this.h = h; }

    // 实现接口方法
    public void addUser(String name) {
        // 核心:转发给 handler
        h.invoke(this, methodObject, new Object[]{name});
    }
}

因为它继承了 Proxy (该类是 final 的),所以 JDK 代理类也是 final 的,不能被继承。

2. CGLIB 的类结构

CGLIB 生成的类(如 OrderService$$EnhancerByCGLIB$$...)大致如下:

public class OrderService$$EnhancerByCGLIB$$... extends OrderService {
    
    private MethodInterceptor callback;

    // 重写父类方法
    public void createOrder(String orderNo) {
        // 检查是否有 callback
        if (callback != null) {
            // 拦截调用
            callback.intercept(this, methodObject, new Object[]{orderNo}, proxy);
        } else {
            // 直接调用父类
            super.createOrder(orderNo);
        }
    }
}

它通过继承实现了代理,因此无法代理 final 类。


七、总结与建议

  1. 学习路径:先理解静态代理的流程,再掌握 JDK 动态代理的反射机制,最后理解 CGLIB 的继承与字节码生成原理。
  2. 开发选择
    • 在日常业务开发中,不要手写静态代理或动态代理代码,直接使用 Spring AOP (@Aspect, @Before, @Around 等注解)。Spring 已经帮你做好了 JDK 和 CGLIB 的自动切换和优化。
    • 在编写基础框架、RPC 客户端或需要高度定制代理逻辑时,才需要手动使用 Proxy.newProxyInstanceEnhancer
  3. 面试重点
    • JDK 动态代理和 CGLIB 的区别是什么?(接口 vs 继承,final 限制)
    • Spring AOP 默认使用哪种?(有接口用 JDK,无接口用 CGLIB)
    • 为什么 CGLIB 不能代理 final 方法?(继承机制限制)

掌握这三种代理模式,你就掌握了 Java 框架设计的基石。希望这篇博文对你的学习和工作有所帮助!

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com