Skip to content

代理模式

为什么需要代理?

代理的本质是在不修改原代码的前提下,增强或控制对目标对象的访问。AOP(面向切面编程)的底层就是代理,Spring 事务、日志、权限控制都依赖它。

常见场景:

  • 日志记录:记录方法调用的参数和返回值
  • 性能监控:统计方法执行耗时
  • 事务管理:方法执行前后开启/提交/回滚事务
  • 权限校验:调用方法前检查用户权限
  • 缓存:方法调用前检查缓存,有则直接返回
  • 远程调用:RPC 框架通过代理隐藏网络通信细节

静态代理

手动编写代理类,代理类和目标类实现同一接口。

基本实现

java
// 共同接口
interface UserService {
    void save(String name);
    void delete(int id);
    User findById(int id);
}

// 目标类
class UserServiceImpl implements UserService {
    public void save(String name) {
        System.out.println("保存用户: " + name);
    }
    public void delete(int id) {
        System.out.println("删除用户: " + id);
    }
    public User findById(int id) {
        System.out.println("查询用户: " + id);
        return new User(id, "用户" + id);
    }
}

// 静态代理类
class UserServiceProxy implements UserService {
    private UserService target;

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

    public void save(String name) {
        System.out.println("=== 事务开始 ===");
        try {
            target.save(name);
            System.out.println("=== 事务提交 ===");
        } catch (Exception e) {
            System.out.println("=== 事务回滚 ===");
            throw e;
        }
    }

    public void delete(int id) {
        System.out.println("=== 事务开始 ===");
        try {
            target.delete(id);
            System.out.println("=== 事务提交 ===");
        } catch (Exception e) {
            System.out.println("=== 事务回滚 ===");
            throw e;
        }
    }

    public User findById(int id) {
        // 每个方法都要重复相同逻辑!
        System.out.println("=== 事务开始 ===");
        try {
            return target.findById(id);
        } finally {
            System.out.println("=== 事务结束 ===");
        }
    }
}

静态代理的缺点

  1. 代码冗余:每个方法都要写相同的增强逻辑
  2. 维护成本高:接口变更时,代理类也要同步修改
  3. 数量爆炸:N 个接口 x M 种增强 = NxM 个代理类

JDK 动态代理

JDK 动态代理在运行时动态生成代理类,无需手动编写。核心是两个组件:

  • java.lang.reflect.Proxy — 生成代理对象
  • java.lang.reflect.InvocationHandler — 拦截所有方法调用

基本实现

java
interface UserService {
    void save(String name);
    void delete(int id);
}

class UserServiceImpl implements UserService {
    public void save(String name) {
        System.out.println("保存用户: " + name);
    }
    public void delete(int id) {
        System.out.println("删除用户: " + id);
    }
}

// 动态代理 — 一个 InvocationHandler 搞定所有方法
class LogInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>> 调用方法: " + method.getName());
        long start = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println("<<< 方法结束: " + method.getName() + " 耗时: " + (end - start) + "ms");
        return result;
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();

        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),  // 类加载器
            target.getClass().getInterfaces(),   // 目标接口
            new LogInvocationHandler(target)     // 调用处理器
        );

        proxy.save("张三");
        proxy.delete(1);
    }
}

输出:

>>> 调用方法: save
保存用户: 张三
<<< 方法结束: save 耗时: 0ms
>>> 调用方法: delete
删除用户: 1
<<< 方法结束: delete 耗时: 0ms

JDK 动态代理原理

Proxy.newProxyInstance() 底层做了三件事:

1. 动态生成字节码

JVM 在运行时用 ProxyGenerator.generateProxyClass() 生成一个 $Proxy0.class

java
// 可以把生成的代理类 dump 到磁盘查看
// JDK 8:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// JDK 11+:
// System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

生成的反编译代码大致如下:

java
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m3; // save
    private static Method m4; // delete
    private static Method m5; // findById

    static {
        m3 = Class.forName("UserService").getMethod("save", String.class);
        m4 = Class.forName("UserService").getMethod("delete", int.class);
        m5 = Class.forName("UserService").getMethod("findById", int.class);
    }

    public $Proxy0(InvocationHandler h) {
        super(h);
    }

    public final void save(String name) {
        super.h.invoke(this, m3, new Object[]{name});
    }

    public final void delete(int id) {
        super.h.invoke(this, m4, new Object[]{id});
    }

    public final User findById(int id) {
        return (User) super.h.invoke(this, m5, new Object[]{id});
    }
}

super.h 就是传入的 InvocationHandler,所有方法调用全部汇聚到 invoke() 里。

2. 动态加载类

用传入的 ClassLoader 把生成的字节码加载进 JVM。

3. 反射创建实例

Constructor.newInstance(handler) 反射创建代理对象,把 InvocationHandler 传进去。

JDK 动态代理的局限性

  1. 必须基于接口:目标类必须实现至少一个接口
  2. 性能开销:每个方法调用都有反射开销
  3. 无法代理 equalshashCodetoString 之外的 Object 方法

CGLIB 代理

CGLIB(Code Generation Library)通过继承目标类生成子类来代理,不需要接口。底层使用 ASM 字节码框架。

基本实现

java
// 目标类 — 不需要接口
class UserService {
    public void save(String name) {
        System.out.println("保存用户: " + name);
    }

    public final void delete(int id) {
        System.out.println("删除用户: " + id);
    }
}

// CGLIB 代理
class CglibProxy implements MethodInterceptor {
    private Object target;

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

    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        System.out.println(">>> 调用方法: " + method.getName());
        // 注意:用 proxy.invokeSuper() 而不是 method.invoke()
        // MethodProxy 使用 FastClass 机制,比反射快
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("<<< 方法结束: " + method.getName());
        return result;
    }

    @SuppressWarnings("unchecked")
    public static <T> T createProxy(Class<T> clazz, MethodInterceptor interceptor) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(interceptor);
        return (T) enhancer.create();
    }
}

// 使用
UserService proxy = CglibProxy.createProxy(UserService.class,
    new CglibProxy(new UserService()));
proxy.save("张三");

CGLIB 的 FastClass 机制

CGLIB 的 MethodProxy.invokeSuper() 不是用反射,而是用 FastClass 机制:

  1. 为目标类和代理类各生成一个 FastClass
  2. FastClass 为每个方法分配一个 index
  3. 调用时通过 index 直接定位方法,无需反射查找

这就是 CGLIB 比 JDK 动态代理快的原因。

CGLIB 的局限性

  1. 不能代理 final 类:final 类无法被继承
  2. 不能代理 final 方法:final 方法无法被重写,但不会报错,只是不增强
  3. 不能代理 private 方法:私有方法无法被子类访问
  4. 需要额外依赖:需要引入 cglib 或 spring-core(Spring 内置了 cglib)

JDK 动态代理 vs CGLIB

维度JDK 动态代理CGLIB
机制实现接口继承目标类
要求必须有接口不能是 final 类
生成时机运行时运行时
底层反射 + 动态字节码ASM 字节码增强
方法调用Method.invoke() 反射MethodProxy.invokeSuper() FastClass
性能创建快,调用慢创建慢,调用快
final 方法不受影响无法代理
private 方法无法代理无法代理
依赖JDK 内置需要 cglib 或 spring-core
代理类命名$Proxy0TargetClass$$EnhancerByCGLIB$$xxxx

Spring AOP 的选择策略

Spring AOP 默认优先使用 JDK 动态代理,以下情况使用 CGLIB:

  1. 目标类没有实现任何接口
  2. 配置了 proxy-target-class="true"(XML)或 @EnableAspectJAutoProxy(proxyTargetClass = true)
  3. Spring Boot 从 2.0 开始默认使用 CGLIB(spring.aop.proxy-target-class=true
java
// 强制使用 CGLIB
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig { }

代理的实际应用

简易 RPC 框架中的动态代理

java
// 客户端只需要接口,不需要实现类
interface HelloService {
    String sayHello(String name);
}

// 通用 RPC 代理处理器
class RpcInvocationHandler implements InvocationHandler {
    private String host;
    private int port;

    public RpcInvocationHandler(String host, int port) {
        this.host = host;
        this.port = port;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 通过网络发送方法名 + 参数,等待远程返回结果
        RpcRequest request = new RpcRequest(method.getDeclaringClass().getName(),
                method.getName(), method.getParameterTypes(), args);
        return sendRequest(host, port, request);
    }

    private Object sendRequest(String host, int port, RpcRequest request) {
        // 省略网络通信细节...
        return null;
    }
}

// 使用
HelloService service = (HelloService) Proxy.newProxyInstance(
    HelloService.class.getClassLoader(),
    new Class[]{HelloService.class},
    new RpcInvocationHandler("127.0.0.1", 8080)
);
String result = service.sayHello("World"); // 实际通过网络调用

常见面试题

Q1: JDK 动态代理和 CGLIB 的区别?

JDK 动态代理基于接口,通过实现接口生成代理类;CGLIB 基于继承,通过生成目标类的子类来代理。JDK 代理创建快但调用慢(反射),CGLIB 创建慢但调用快(FastClass)。Spring AOP 默认使用 JDK 动态代理,无接口时使用 CGLIB。

Q2: JDK 动态代理为什么必须基于接口?

生成的代理类 $Proxy0 已经继承了 java.lang.reflect.Proxy,而 Java 是单继承,无法再继承目标类,只能通过实现接口来代理。

Q3: CGLIB 能代理 final 方法吗?

不能。final 方法无法被子类重写,CGLIB 生成的代理类不会覆盖 final 方法,调用时直接执行原方法逻辑,不会触发代理增强。

Q4: 静态代理和动态代理的优缺点?

静态代理在编译期确定代理关系,性能好但代码冗余、维护成本高。动态代理在运行时生成代理类,灵活、通用,但有性能开销。

Q5: 同一个代理类能否同时代理多个接口?

JDK 动态代理可以,newProxyInstance 的第二个参数是 Class<?>[],可以传入多个接口。生成的代理类会实现所有传入的接口。

Q6: Method.invoke()MethodProxy.invokeSuper() 的区别?

Method.invoke() 是标准反射调用,每次调用都需要权限检查和方法查找。MethodProxy.invokeSuper() 是 CGLIB 的 FastClass 机制,通过方法 index 直接调用,避免了反射开销。

Q7: 如何让 Spring Boot 强制使用 JDK 动态代理?

配置 spring.aop.proxy-target-class=false,并确保目标类实现了接口。Spring Boot 2.0+ 默认 proxy-target-class 为 true(即默认 CGLIB)。