代理模式
代理模式是一种结构型设计模式。它为目标对象提供一个代理对象,调用方不直接访问目标对象,而是通过代理对象间接访问。代理可以在不修改目标对象的前提下增加日志、权限、事务、缓存、远程调用等能力。
为什么需要代理模式
如果每个业务方法都手写日志、权限、耗时统计,业务代码会被横切逻辑污染。
java
public void pay() {
checkPermission();
long start = System.currentTimeMillis();
try {
// 支付业务
} finally {
logCost(System.currentTimeMillis() - start);
}
}问题:
- 核心业务和横切逻辑混在一起。
- 每个方法都重复写相同代码。
- 想统一调整日志或权限逻辑,要改很多地方。
代理模式把增强逻辑放到代理层:
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 动态代理的特点:
- 不需要手写代理类。
- 目标对象必须实现接口。
- 方法调用会进入
InvocationHandler。 - 常用于 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 的限制:
- 不能代理
final类。 - 不能增强
final方法。 - 通过继承生成代理,和 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 动态代理类型转换异常 |
小结
代理模式适合把日志、权限、事务、缓存、远程调用这类横切逻辑从业务代码中抽离出来。它的核心不是“包一层对象”,而是让调用经过代理入口,从而在目标方法前后统一增强或控制访问。
