Java 代理模式深度解析:从静态到动态,从原理到实战

在 Java 开发中,代理模式是一种经典的设计模式,它通过 “代理对象” 代替 “目标对象” 完成交互,在不修改目标对象代码的前提下扩展其功能。无论是 Spring AOP 的切面增强、RPC 框架的远程调用,还是日常开发中的日志记录、权限校验,代理模式都扮演着核心角色。本文基于 JavaGuide 的权威内容,系统梳理代理模式的两种实现(静态代理、动态代理)、核心原理与实战场景,帮你从 “会用” 到 “精通”。

一、代理模式基础:什么是代理?为什么需要代理?

1.1 代理模式的核心定义

代理模式(Proxy Pattern)是指通过一个代理对象(Proxy)包裹目标对象(Target),对外暴露与目标对象一致的接口,同时在代理对象中添加额外逻辑,实现对目标对象功能的扩展或控制。

核心价值:无侵入扩展—— 无需修改目标对象的代码,就能在方法执行前后添加自定义操作(如日志、事务、缓存),符合 “开闭原则”(对扩展开放,对修改关闭)。

1.2 生活化例子理解代理

就像婚礼中 “新娘的姨妈代理接收新郎提问”:

  • 新郎(调用者)不直接向新娘(目标对象)提问;
  • 姨妈(代理对象)先过滤掉不合适的问题,再将有效问题转给新娘;
  • 新娘回答后,姨妈再将结果反馈给新郎;
  • 整个过程中,新娘的行为未被修改,但通过姨妈实现了 “问题过滤” 的扩展功能。

二、静态代理:简单但不灵活的 “手动扩展”

静态代理是代理模式的基础实现,编译期就确定代理类的代码,需要手动为每个目标类编写对应的代理类。它的优点是实现简单,缺点是灵活性极差,日常开发中几乎不用,但理解它是掌握动态代理的前提。

2.1 静态代理的实现步骤

静态代理的核心是 “代理类与目标类实现相同接口”,通过注入目标对象,在代理方法中调用目标方法并添加扩展逻辑。具体步骤如下:

  1. 定义接口:声明目标对象的核心方法(代理类需实现该接口,保证接口一致性);
  2. 实现目标类:编写目标对象的业务逻辑;
  3. 实现代理类:注入目标对象,在对应方法中调用目标方法,并添加扩展逻辑;
  4. 使用代理类:通过代理对象调用方法,而非直接调用目标对象。

2.2 静态代理实战:短信发送增强

以 “发送短信时添加日志记录” 为例,演示静态代理的实现:

步骤 1:定义短信发送接口
// 1. 定义接口:声明核心方法
public interface SmsService {
    // 发送短信的核心方法
    String send(String message);
}
步骤 2:实现目标类(真实发送逻辑)
// 2. 目标类:实现接口,编写业务逻辑
public class SmsServiceImpl implements SmsService {
    @Override
    public String send(String message) {
        // 真实的短信发送逻辑(如调用阿里云短信API)
        System.out.println("【目标对象】发送短信:" + message);
        return message;
    }
}
步骤 3:实现代理类(添加日志扩展)
// 3. 代理类:实现相同接口,注入目标对象
public class SmsProxy implements SmsService {
    // 注入目标对象(通过构造函数)
    private final SmsService target;

    public SmsProxy(SmsService target) {
        this.target = target;
    }

    @Override
    public String send(String message) {
        // 扩展逻辑:方法执行前添加日志
        System.out.println("【代理对象】发送前日志:准备发送短信,内容:" + message);
        
        // 调用目标对象的核心方法
        String result = target.send(message);
        
        // 扩展逻辑:方法执行后添加日志
        System.out.println("【代理对象】发送后日志:短信发送完成,返回结果:" + result);
        
        return result;
    }
}
步骤 4:使用代理类
// 4. 实际调用:通过代理对象交互
public class StaticProxyMain {
    public static void main(String[] args) {
        // 1. 创建目标对象
        SmsService target = new SmsServiceImpl();
        // 2. 创建代理对象,注入目标对象
        SmsService proxy = new SmsProxy(target);
        // 3. 通过代理对象调用方法
        proxy.send("Hello Java Proxy!");
    }
}
执行结果
【代理对象】发送前日志:准备发送短信,内容:Hello Java Proxy!
【目标对象】发送短信:Hello Java Proxy!
【代理对象】发送后日志:短信发送完成,返回结果:Hello Java Proxy!

2.3 静态代理的优缺点

优点缺点
实现简单,逻辑直观,无需依赖额外框架灵活性差:接口新增方法时,目标类和代理类需同步修改
无反射开销,执行效率高冗余代码多:每个目标类需单独编写代理类(如 UserService、OrderService 需分别写代理)
编译期确定代码,调试方便无法代理无接口的类(只能基于接口实现)

正因为静态代理的局限性,实际开发中几乎不用它 ——动态代理才是代理模式的 “主战场”

三、动态代理:运行时生成代理,灵活扩展的核心

动态代理是指在运行时通过反射或字节码生成技术动态创建代理类,无需手动编写代理代码。它解决了静态代理 “冗余、不灵活” 的问题,是 Spring AOP、RPC 框架(如 Dubbo)的底层核心技术。

Java 中动态代理的主流实现有两种:JDK 动态代理(基于接口)和CGLIB 动态代理(基于继承)。

3.1 JDK 动态代理:基于接口的动态生成

JDK 动态代理是 Java 原生支持的代理方式,仅能代理实现了接口的类,核心依赖java.lang.reflect包下的Proxy类和InvocationHandler接口。

3.1.1 核心原理
  1. Proxy 类:通过newProxyInstance()方法在运行时动态生成代理类实例,该方法需要 3 个参数:
    • ClassLoader loader:目标类的类加载器(用于加载动态生成的代理类);
    • Class<?>[] interfaces:目标类实现的所有接口(代理类需实现这些接口,保证接口一致性);
    • InvocationHandler h:自定义的 “方法拦截器”,代理对象的所有方法调用都会转发到该接口的invoke()方法。
  2. InvocationHandler 接口:定义invoke()方法,是动态代理的 “逻辑增强中心”,参数含义:
    • Object proxy:动态生成的代理对象本身(一般不直接使用,避免循环调用);
    • Method method:当前被调用的目标方法(如send());
    • Object[] args:当前方法的参数列表;
    • 返回值:目标方法的执行结果(需返回给调用者)。
3.1.2 JDK 动态代理实战:短信发送日志增强

基于之前的SmsService接口,用 JDK 动态代理实现日志扩展,无需手动编写代理类:

步骤 1:自定义 InvocationHandler(方法拦截器)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 自定义方法拦截器:实现InvocationHandler
public class DebugInvocationHandler implements InvocationHandler {
    // 注入目标对象(通用Object类型,可代理任意实现接口的类)
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 扩展逻辑:方法执行前打印日志
        System.out.println("【JDK动态代理】方法执行前:" + method.getName() + ",参数:" + args[0]);
        
        // 调用目标对象的核心方法(通过反射)
        Object result = null;
        try {
            result = method.invoke(target, args); // 执行目标方法
        } catch (InvocationTargetException e) {
            // 扩展逻辑:方法异常时处理
            System.out.println("【JDK动态代理】方法执行异常:" + e.getCause().getMessage());
            throw e.getCause(); // 抛出原始异常
        }
        
        // 扩展逻辑:方法执行后打印日志
        System.out.println("【JDK动态代理】方法执行后:" + method.getName() + ",返回值:" + result);
        
        return result;
    }
}
步骤 2:创建代理对象工厂(封装 Proxy.newProxyInstance)
import java.lang.reflect.Proxy;

// 代理对象工厂:封装代理创建逻辑,对外提供getProxy()方法
public class JdkProxyFactory {
    /**
     * 获取目标对象的动态代理实例
     * @param target 目标对象(需实现接口)
     * @return 代理对象
     */
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载器
                target.getClass().getInterfaces(),  // 目标类实现的接口(关键:JDK代理基于接口)
                new DebugInvocationHandler(target)   // 自定义的方法拦截器
        );
    }
}
步骤 3:使用动态代理
public class JdkProxyMain {
    public static void main(String[] args) {
        // 1. 创建目标对象
        SmsService target = new SmsServiceImpl();
        
        // 2. 获取动态代理对象(通过工厂类)
        SmsService proxy = (SmsService) JdkProxyFactory.getProxy(target);
        
        // 3. 通过代理对象调用方法(与目标对象接口一致)
        proxy.send("Hello JDK Dynamic Proxy!");
    }
}
执行结果
【JDK动态代理】方法执行前:send,参数:Hello JDK Dynamic Proxy!
【目标对象】发送短信:Hello JDK Dynamic Proxy!
【JDK动态代理】方法执行后:send,返回值:Hello JDK Dynamic Proxy!
3.1.3 JDK 动态代理的局限性
  • 仅能代理实现接口的类:若目标类没有实现任何接口(如AliSmsService),JDK 动态代理无法生效;
  • 依赖接口一致性:代理对象只能通过接口类型接收(如SmsService proxy = ...),不能通过目标类类型接收(如SmsServiceImpl proxy = ...)。

3.2 CGLIB 动态代理:基于继承的无接口代理

为解决 JDK 动态代理 “必须依赖接口” 的问题,CGLIB(Code Generation Library)应运而生。它是一个基于 ASM 字节码生成框架的第三方库,通过继承目标类生成代理类,即使目标类没有实现接口也能代理。

3.2.1 核心原理
  1. CGLIB 的核心类
    • Enhancer:动态代理的 “增强器”,负责生成代理类,核心方法create()用于创建代理实例;
    • MethodInterceptor:方法拦截器,类似 JDK 的InvocationHandler,通过intercept()方法实现增强逻辑。
  2. 实现逻辑
    • CGLIB 生成的代理类是目标类的子类,并重写目标类的非 final 方法;
    • 当调用代理类的方法时,会被MethodInterceptorintercept()方法拦截,执行增强逻辑后调用父类(目标类)的原始方法;
    • 由于是继承实现,目标类或方法不能被声明为 final(final 类无法继承,final 方法无法重写)。
3.2.2 CGLIB 动态代理实战:无接口类增强

以 “阿里云短信发送类(无接口)” 为例,演示 CGLIB 的使用:

步骤 1:添加 CGLIB 依赖

CGLIB 是第三方库,需在 Maven/Gradle 中添加依赖:

<!-- Maven依赖 -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
步骤 2:定义目标类(无接口)
// 目标类:无接口,直接实现业务逻辑
public class AliSmsService {
    // 非final方法(CGLIB可重写)
    public String send(String message) {
        System.out.println("【阿里云短信】发送内容:" + message);
        return "阿里云发送成功:" + message;
    }

    // final方法(CGLIB无法代理,调用时不会被拦截)
    public final String getPlatform() {
        return "阿里云短信平台";
    }
}
步骤 3:自定义 MethodInterceptor(方法拦截器)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 自定义方法拦截器:实现MethodInterceptor
public class DebugMethodInterceptor implements MethodInterceptor {
    /**
     * 拦截代理类的方法调用
     * @param o 代理对象(目标类的子类实例)
     * @param method 被拦截的目标方法
     * @param args 方法参数
     * @param methodProxy 用于调用目标方法的代理对象(高性能,避免反射开销)
     * @return 目标方法的返回值
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 扩展逻辑:方法执行前打印日志
        System.out.println("【CGLIB动态代理】方法执行前:" + method.getName() + ",参数:" + args[0]);
        
        // 调用目标方法:使用methodProxy.invokeSuper()(而非method.invoke(),避免循环调用)
        Object result = methodProxy.invokeSuper(o, args);
        
        // 扩展逻辑:方法执行后打印日志
        System.out.println("【CGLIB动态代理】方法执行后:" + method.getName() + ",返回值:" + result);
        
        return result;
    }
}
步骤 4:创建 CGLIB 代理工厂
import net.sf.cglib.proxy.Enhancer;

// CGLIB代理工厂:封装代理创建逻辑
public class CglibProxyFactory {
    /**
     * 获取目标类的CGLIB代理实例
     * @param targetClass 目标类的Class对象
     * @return 代理对象(目标类的子类)
     */
    public static Object getProxy(Class<?> targetClass) {
        // 1. 创建增强器(核心类)
        Enhancer enhancer = new Enhancer();
        
        // 2. 设置增强器的核心参数
        enhancer.setClassLoader(targetClass.getClassLoader()); // 目标类的类加载器
        enhancer.setSuperclass(targetClass); // 设置父类(目标类,CGLIB通过继承代理)
        enhancer.setCallback(new DebugMethodInterceptor()); // 绑定方法拦截器
        
        // 3. 生成并返回代理对象
        return enhancer.create();
    }
}
步骤 5:使用 CGLIB 代理
public class CglibProxyMain {
    public static void main(String[] args) {
        // 1. 获取CGLIB代理对象(目标类的子类)
        AliSmsService proxy = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
        
        // 2. 调用代理对象的方法(非final方法会被拦截)
        proxy.send("Hello CGLIB Dynamic Proxy!");
        
        // 3. 调用final方法(不会被拦截,直接执行目标类方法)
        System.out.println("final方法调用:" + proxy.getPlatform());
    }
}
执行结果
【CGLIB动态代理】方法执行前:send,参数:Hello CGLIB Dynamic Proxy!
【阿里云短信】发送内容:Hello CGLIB Dynamic Proxy!
【CGLIB动态代理】方法执行后:send,返回值:阿里云发送成功:Hello CGLIB Dynamic Proxy!
final方法调用:阿里云短信平台
3.2.3 CGLIB 的局限性
  • 不能代理 final 类 / 方法:由于基于继承,final 类无法被继承,final 方法无法被重写;
  • 依赖第三方库:需手动添加 CGLIB 依赖,不像 JDK 动态代理是原生支持;
  • 初始化开销略高:CGLIB 生成代理类时需操作字节码,初始化速度比 JDK 动态代理慢,但运行时效率与 JDK 接近(JDK 8 + 后 JDK 动态代理效率更优)。

四、核心对比:静态代理 vs 动态代理,JDK vs CGLIB

4.1 静态代理与动态代理的对比

对比维度静态代理动态代理(JDK/CGLIB)
代理类生成时机编译期(手动编写,生成.class 文件)运行期(动态生成字节码,无手动代码)
灵活性差:接口新增方法需同步修改代理类优:一个代理工厂可代理任意类
代码冗余度高:每个目标类需单独写代理类低:仅需编写拦截器逻辑
JVM 层面编译期确定类结构运行时动态生成类字节码
适用场景简单场景(几乎不用)框架开发、通用扩展(如 Spring AOP)

4.2 JDK 动态代理与 CGLIB 的对比

对比维度JDK 动态代理CGLIB 动态代理
代理原理基于接口实现基于继承目标类
代理条件目标类必须实现接口目标类不能是 final,方法不能是 final
依赖Java 原生支持(无第三方依赖)需引入 CGLIB 库
初始化速度快(无需操作字节码)慢(需生成子类字节码)
运行时效率JDK 8 + 更优(反射优化)与 JDK 接近,略低
目标类类型限制仅能代理实现接口的类可代理无接口的类

五、代理模式的实际应用:框架中的代理

代理模式不是 “纸上谈兵”,而是众多 Java 框架的底层核心,以下是两个典型场景:

5.1 Spring AOP:基于代理的切面增强

Spring AOP(面向切面编程)的核心就是动态代理:

  • 目标类实现接口:默认使用 JDK 动态代理,将 “切面逻辑”(如@Before@After)封装到InvocationHandler中;
  • 目标类无接口:自动切换为 CGLIB 动态代理,将切面逻辑封装到MethodInterceptor中;
  • 例:@Transactional注解的事务增强 ——Spring 通过动态代理在目标方法执行前开启事务,执行后提交 / 回滚,无需手动编写事务逻辑。

5.2 RPC 框架:基于动态代理的远程调用

Dubbo、Feign 等 RPC 框架的 “远程调用” 依赖动态代理:

  • 客户端调用的 “Service 接口” 实际是 JDK 动态代理对象;
  • 代理对象的invoke()方法中,将 “方法名、参数” 序列化为网络请求,发送到服务端;
  • 服务端处理后返回结果,代理对象反序列化结果并返回给客户端;
  • 开发者无需关注网络通信细节,像调用本地方法一样调用远程服务。

六、总结:代理模式的选择与最佳实践

6.1 不同场景下的代理选择

  1. 简单扩展,无接口依赖:用 CGLIB(如代理无接口的工具类);
  2. 框架开发,需兼容接口:用 JDK 动态代理(原生支持,无依赖,如 RPC 框架);
  3. Spring 生态中:无需手动选择 ——Spring AOP 会自动根据目标类是否有接口切换 JDK/CGLIB;
  4. 几乎不选静态代理:仅在极端简单场景(如一次性扩展单个方法)使用,日常开发优先动态代理。

6.2 代理模式的核心价值

  • 解耦:将 “业务逻辑” 与 “扩展逻辑” 分离(如业务代码只关注 “发短信”,代理关注 “日志”);
  • 无侵入:无需修改目标对象代码,降低维护成本;
  • 可复用:一套扩展逻辑(如日志拦截器)可复用在多个目标类上。

通过本文的学习,相信你已掌握代理模式的核心原理与实战技巧。无论是日常开发中的简单扩展,还是深入理解 Spring、RPC 框架的底层,代理模式都是不可或缺的基础知识 —— 它不仅是一种设计模式,更是 Java 生态中 “无侵入扩展” 的核心思想体现。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐