Skip to content

代理模式

代理模式是一种结构型设计模式。它为目标对象提供一个代理对象,调用方不直接访问目标对象,而是通过代理对象间接访问。代理可以在不修改目标对象的前提下增加日志、权限、事务、缓存、远程调用等能力。

为什么需要代理模式

如果每个业务方法都手写日志、权限、耗时统计,业务代码会被横切逻辑污染。

java
public void pay() {
    checkPermission();
    long start = System.currentTimeMillis();
    try {
        // 支付业务
    } finally {
        logCost(System.currentTimeMillis() - start);
    }
}

问题:

  1. 核心业务和横切逻辑混在一起。
  2. 每个方法都重复写相同代码。
  3. 想统一调整日志或权限逻辑,要改很多地方。

代理模式把增强逻辑放到代理层:

mermaid
flowchart TD
    A["调用方"] --> B["代理对象"]
    B --> C["前置增强<br/>权限 / 日志"]
    C --> D["目标对象"]
    D --> E["后置增强<br/>耗时 / 事务提交"]

静态代理

静态代理需要手写代理类。目标类和代理类实现同一个接口。

mermaid
flowchart TD
    A["Client"] --> B["MerchantProxy"]
    B --> C["before"]
    C --> D["Merchant"]
    D --> E["after"]

目标接口:

java
public interface MerchantAction {
    void buy();
}

目标类:

java
public class Merchant implements MerchantAction {
    @Override
    public void buy() {
        System.out.println("merchant sale");
    }
}

代理类:

java
public class MerchantProxy implements MerchantAction {
    private final MerchantAction target;

    public MerchantProxy(MerchantAction target) {
        this.target = target;
    }

    @Override
    public void buy() {
        System.out.println("before");
        target.buy();
        System.out.println("after");
    }
}

使用:

java
MerchantAction action = new MerchantProxy(new Merchant());
action.buy();

静态代理缺点:每个接口都要手写代理类,方法多时会大量重复。

JDK 动态代理

JDK 动态代理基于接口生成代理对象,目标类必须实现接口。

mermaid
flowchart TD
    A["Proxy.newProxyInstance"] --> B["生成接口代理类"]
    B --> C["InvocationHandler.invoke"]
    C --> D["反射调用目标方法"]

接口:

java
public interface PayService {
    void pay();
}

目标类:

java
public class AliPayService implements PayService {
    @Override
    public void pay() {
        System.out.println("ali pay");
    }
}

处理器:

java
public class LogInvocationHandler implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after: " + method.getName());
        return result;
    }
}

生成代理:

java
PayService proxy = (PayService) Proxy.newProxyInstance(
    PayService.class.getClassLoader(),
    new Class[]{PayService.class},
    new LogInvocationHandler(new AliPayService())
);

proxy.pay();

JDK 动态代理的特点:

  1. 不需要手写代理类。
  2. 目标对象必须实现接口。
  3. 方法调用会进入 InvocationHandler
  4. 常用于 Spring AOP 的接口代理场景。

CGLIB 动态代理

CGLIB 通过生成目标类的子类来代理,不要求目标类实现接口。

mermaid
flowchart TD
    A["目标类"] --> B["CGLIB 生成子类"]
    B --> C["拦截方法调用"]
    C --> D["MethodInterceptor"]
    D --> E["调用目标逻辑"]

目标类:

java
public class PayService {
    public void pay() {
        System.out.println("pay");
    }
}

拦截器:

java
public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("after");
        return result;
    }
}

CGLIB 的限制:

  1. 不能代理 final 类。
  2. 不能增强 final 方法。
  3. 通过继承生成代理,和 JDK 接口代理机制不同。

Spring AOP 和代理

Spring AOP 的底层核心就是代理。

场景常见代理方式
目标类实现接口JDK 动态代理或 CGLIB
目标类没有接口CGLIB
@Transactional通过代理在方法前后开启、提交、回滚事务
@Async通过代理把方法提交到线程池

这也是为什么 Spring 中同类内部方法调用可能导致事务不生效:

java
public class OrderService {
    public void outer() {
        inner(); // this.inner(),没有经过代理对象
    }

    @Transactional
    public void inner() {
        // 事务可能不生效
    }
}

因为代理增强发生在“调用代理对象方法”时,this.inner() 是目标对象内部调用,没有经过代理。

和装饰器模式区别

对比代理模式装饰器模式
目的控制访问或统一增强动态叠加功能
典型场景AOP、权限、事务、远程代理Java IO、功能包装
是否强调访问控制不一定
是否强调多层组合不一定

滥用风险

风险后果
代理层逻辑过重业务行为隐藏,排查困难
不理解自调用Spring AOP 事务、异步不生效
final 类或方法CGLIB 无法增强
接口和实现混乱JDK 动态代理类型转换异常

小结

代理模式适合把日志、权限、事务、缓存、远程调用这类横切逻辑从业务代码中抽离出来。它的核心不是“包一层对象”,而是让调用经过代理入口,从而在目标方法前后统一增强或控制访问。