设计模式:代理模式

欢迎你来读这篇博客,这篇博客主要是关于代理模式
其中包括代理模式的核心思想、适用场景、静态代理、JDK 动态代理、CGLIB 代理、Spring AOP 代理、优缺点,以及 Java 后端开发中订单服务增强的完整案例。

序言

在 Java 后端开发中,我们经常会遇到这样的问题:

我不想直接访问某个对象,而是希望在访问它之前或之后做一些控制。

比如调用订单服务:

1
orderService.createOrder(command);

现在希望在真正创建订单之前或之后增加一些逻辑:

  • 权限校验;
  • 参数校验;
  • 日志记录;
  • 耗时统计;
  • 事务控制;
  • 缓存处理;
  • 远程调用封装;
  • 限流保护;
  • 异常转换;
  • 监控埋点。

最简单的方式是直接修改 OrderService

但是这样做会让业务代码越来越臃肿。

订单服务本来只应该关心“怎么创建订单”,现在却开始关心日志、权限、事务、监控、缓存。最后一个类里塞满了各种横切逻辑,业务方法像穿了十层棉袄,跑都跑不动。

代理模式就是为了解决这类问题:

给目标对象提供一个代理对象,由代理对象控制对目标对象的访问,并可以在访问前后增加额外逻辑。

简单说:

代理模式就是不直接找本人办事,而是先找代理人。

代理人可以帮你做很多事:

  • 判断你有没有资格访问;
  • 记录你什么时候访问;
  • 控制访问频率;
  • 延迟创建真实对象;
  • 把本地调用转成远程调用;
  • 在调用前后加增强逻辑。

这也是 Spring AOP、MyBatis Mapper、RPC 框架、事务管理等技术背后非常重要的思想基础。

正文

chapter 1:什么是代理模式

代理模式,英文是 Proxy Pattern,属于结构型设计模式。

它的定义是:

为其他对象提供一种代理,以控制对这个对象的访问。

代理模式一般包含三个角色:

  1. Subject 抽象主题:定义目标对象和代理对象的共同接口。
  2. RealSubject 真实主题:真正执行业务逻辑的对象。
  3. Proxy 代理对象:持有真实主题对象,在调用真实对象前后增加控制逻辑。

结构如下:

1
2
3
4
5
6
7
8
9
10
11
Client


Subject


├── RealSubject

└── Proxy

└── RealSubject

客户端调用的是 Subject 接口。

它可以拿到真实对象,也可以拿到代理对象。

代理对象内部再调用真实对象。

chapter 2:代理模式的核心思想

代理模式的核心思想是:

客户端不直接访问目标对象,而是通过代理对象间接访问目标对象。

代理对象可以在中间做控制。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class OrderServiceProxy implements OrderService {

private final OrderService target;

public OrderServiceProxy(OrderService target) {
this.target = target;
}

@Override
public Long createOrder(CreateOrderCommand command) {
System.out.println("权限校验");

Long orderId = target.createOrder(command);

System.out.println("记录日志");

return orderId;
}
}

真正创建订单的是:

1
target.createOrder(command)

代理对象做的是:

  • 调用前增强;
  • 调用后增强;
  • 异常处理;
  • 访问控制;
  • 延迟加载;
  • 远程转发。

代理模式不是替换目标对象,而是控制访问目标对象。

chapter 3:为什么需要代理模式

代理模式常见价值有几个。

1. 控制访问

例如权限代理。

调用真实对象前先判断当前用户是否有权限。

1
2
3
if (!hasPermission()) {
throw new AccessDeniedException();
}

2. 增强功能

例如日志、监控、事务。

1
2
3
4
5
6
7
long start = System.currentTimeMillis();

try {
return target.doSomething();
} finally {
logCost(System.currentTimeMillis() - start);
}

3. 延迟加载

真实对象创建成本很高,只有真正使用时才创建。

1
2
3
if (target == null) {
target = new RealSubject();
}

4. 远程调用

客户端以为调用的是本地对象,代理对象内部把调用转成网络请求。

这就是 RPC 的核心思想之一。

5. 屏蔽复杂细节

代理对象可以隐藏真实对象的创建、连接、重试、序列化、反序列化、网络通信等细节。

chapter 4:代理模式的常见类型

代理模式有很多分类,常见包括:

  1. 静态代理
  2. 动态代理
  3. 远程代理
  4. 虚拟代理
  5. 保护代理
  6. 缓存代理
  7. 智能引用代理

在 Java 开发中,最常讨论的是:

  • 静态代理;
  • JDK 动态代理;
  • CGLIB 动态代理;
  • Spring AOP 代理。

下面逐个讲。

chapter 5:静态代理

静态代理是最容易理解的一种代理。

它的特点是:

代理类在编译期就已经写好了。

也就是说,你需要手动写一个代理类。

假设有一个订单服务接口。

1
2
3
4
public interface OrderService {

Long createOrder(CreateOrderCommand command);
}

真实订单服务:

1
2
3
4
5
6
7
8
9
10
public class RealOrderService implements OrderService {

@Override
public Long createOrder(CreateOrderCommand command) {
System.out.println("创建订单,userId = " + command.getUserId()
+ ",productId = " + command.getProductId());

return 10001L;
}
}

静态代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class OrderServiceStaticProxy implements OrderService {

private final OrderService target;

public OrderServiceStaticProxy(OrderService target) {
this.target = target;
}

@Override
public Long createOrder(CreateOrderCommand command) {
System.out.println("[静态代理] 权限校验");

long start = System.currentTimeMillis();

try {
Long orderId = target.createOrder(command);

System.out.println("[静态代理] 创建订单成功,orderId = " + orderId);

return orderId;
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[静态代理] 方法耗时:" + cost + " ms");
}
}
}

客户端调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StaticProxyDemo {

public static void main(String[] args) {
OrderService target = new RealOrderService();

OrderService proxy = new OrderServiceStaticProxy(target);

CreateOrderCommand command = new CreateOrderCommand(1L, 100L, 2);

Long orderId = proxy.createOrder(command);

System.out.println("返回订单 ID:" + orderId);
}
}

这里客户端调用的是代理对象。

代理对象内部再调用真实对象。

chapter 6:静态代理的优缺点

1. 优点

静态代理的优点是:

  • 简单直观;
  • 不需要反射;
  • 代码容易理解;
  • 编译期就能检查类型;
  • 适合少量接口增强。

2. 缺点

静态代理的问题也很明显:

  • 每个接口都要写代理类;
  • 方法多时代码重复;
  • 目标类多时代理类爆炸;
  • 不够灵活;
  • 不适合通用横切逻辑。

比如系统里有 100 个 Service 都要加日志。

如果用静态代理,就要写很多代理类。

这显然不现实。

这时就需要动态代理。

chapter 7:JDK 动态代理

JDK 动态代理是 Java 原生提供的动态代理能力。

它的特点是:

在运行时动态生成代理对象,不需要手写代理类。

JDK 动态代理基于接口实现。

也就是说,目标对象必须实现接口。

核心类是:

1
2
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler

chapter 8:JDK 动态代理案例

继续使用订单服务接口。

1
2
3
4
5
6
public interface OrderService {

Long createOrder(CreateOrderCommand command);

void cancelOrder(Long orderId);
}

真实实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RealOrderService implements OrderService {

@Override
public Long createOrder(CreateOrderCommand command) {
System.out.println("创建订单,userId = " + command.getUserId()
+ ",productId = " + command.getProductId());

return 10001L;
}

@Override
public void cancelOrder(Long orderId) {
System.out.println("取消订单,orderId = " + orderId);
}
}

定义动态代理处理器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LoggingInvocationHandler implements InvocationHandler {

private final Object target;

public LoggingInvocationHandler(Object target) {
if (target == null) {
throw new IllegalArgumentException("target can not be null");
}

this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK 动态代理] 调用方法:" + method.getName());

long start = System.currentTimeMillis();

try {
Object result = method.invoke(target, args);

System.out.println("[JDK 动态代理] 方法调用成功:" + method.getName());

return result;
} catch (Exception e) {
System.out.println("[JDK 动态代理] 方法调用失败:" + method.getName()
+ ",error = " + e.getMessage());

throw e;
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[JDK 动态代理] 方法耗时:" + cost + " ms");
}
}
}

创建代理对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.reflect.Proxy;

public class JdkDynamicProxyDemo {

public static void main(String[] args) {
OrderService target = new RealOrderService();

OrderService proxy = (OrderService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{OrderService.class},
new LoggingInvocationHandler(target)
);

CreateOrderCommand command = new CreateOrderCommand(1L, 100L, 2);

Long orderId = proxy.createOrder(command);

proxy.cancelOrder(orderId);
}
}

这段代码里,代理对象是在运行时生成的。

1
Proxy.newProxyInstance(...)

三个参数分别是:

  1. 类加载器;
  2. 代理对象要实现的接口列表;
  3. 方法调用处理器。

chapter 9:JDK 动态代理执行流程

JDK 动态代理的执行流程大概是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
客户端调用 proxy.createOrder(command)


代理对象拦截方法调用


调用 InvocationHandler.invoke()


在 invoke() 中执行增强逻辑


通过 method.invoke(target, args) 调用真实对象


返回结果

核心就在 invoke() 方法:

1
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

它可以拦截接口中的所有方法。

所以日志、耗时统计、权限校验、事务控制都可以在这里统一处理。

chapter 10:JDK 动态代理的优缺点

1. 优点

  • Java 原生支持;
  • 不需要手写代理类;
  • 可以统一增强多个方法;
  • 适合基于接口的编程;
  • Spring AOP 可以使用 JDK 动态代理。

2. 缺点

  • 目标类必须实现接口;
  • 只能代理接口方法;
  • 反射调用有一定性能成本;
  • 代码理解成本比静态代理高;
  • 对 final 方法无能为力,因为接口代理不涉及类方法覆盖。

如果你的类没有接口,JDK 动态代理就不适合。

这时可以使用 CGLIB 代理。

chapter 11:CGLIB 动态代理

CGLIB 是一种基于继承的动态代理方式。

它的特点是:

通过生成目标类的子类来创建代理对象。

也就是说,CGLIB 不要求目标类实现接口。

它会在运行时生成一个子类,并重写目标类的方法,在方法前后加入增强逻辑。

结构类似:

1
2
3
4
RealOrderService


CglibProxySubclass

CGLIB 底层依赖字节码生成技术。

在 Spring AOP 中,如果目标对象没有实现接口,通常可以使用 CGLIB 方式创建代理。

chapter 12:CGLIB 代理案例

注意:下面代码需要引入 CGLIB 依赖或使用 Spring 内部相关能力。这里主要演示思想。

目标类没有实现接口:

1
2
3
4
5
6
7
8
public class OrderQueryService {

public String queryOrder(Long orderId) {
System.out.println("查询订单,orderId = " + orderId);

return "ORDER-" + orderId;
}
}

CGLIB 方法拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class LoggingMethodInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object obj,
Method method,
Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("[CGLIB 代理] 调用方法:" + method.getName());

long start = System.currentTimeMillis();

try {
Object result = proxy.invokeSuper(obj, args);

System.out.println("[CGLIB 代理] 方法调用成功:" + method.getName());

return result;
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[CGLIB 代理] 方法耗时:" + cost + " ms");
}
}
}

创建代理对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyDemo {

public static void main(String[] args) {
Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(OrderQueryService.class);

enhancer.setCallback(new LoggingMethodInterceptor());

OrderQueryService proxy = (OrderQueryService) enhancer.create();

String result = proxy.queryOrder(10001L);

System.out.println(result);
}
}

CGLIB 代理会生成 OrderQueryService 的子类。

调用 queryOrder() 时,会先进入拦截器。

chapter 13:CGLIB 代理的限制

CGLIB 虽然不要求接口,但它也有限制。

1. 不能代理 final 类

因为 CGLIB 通过继承生成子类。

如果类是 final,不能被继承。

1
2
public final class OrderQueryService {
}

这种类无法被 CGLIB 代理。

2. 不能代理 final 方法

因为 final 方法不能被子类重写。

1
2
public final String queryOrder(Long orderId) {
}

CGLIB 无法拦截这个方法。

3. 对构造方法和类结构有一定要求

CGLIB 需要创建子类对象,构造方法、可见性等因素都可能影响代理生成。

4. 调试成本更高

运行时生成子类,堆栈信息和普通调用不完全一样。

chapter 14:JDK 动态代理和 CGLIB 代理对比

对比项 JDK 动态代理 CGLIB 动态代理
代理方式 基于接口 基于继承
是否要求接口
是否能代理类 不能,只代理接口 可以代理类
是否能代理 final 类 不涉及 不能
是否能代理 final 方法 不涉及接口方法实现 不能
生成方式 JDK Proxy 字节码生成子类
常见场景 接口型 Service 无接口类
Spring AOP 是否支持 支持 支持

简单记:

有接口,可以用 JDK 动态代理。

没接口,可以用 CGLIB。

不过在 Spring Boot 项目中,我们通常不需要手动创建这些代理对象,Spring 会根据配置和目标对象情况帮我们处理。

chapter 15:Spring AOP 和代理模式

Spring AOP 的核心实现思想就是代理模式。

例如你写了一个 Service:

1
2
3
4
5
6
7
8
@Service
public class OrderService {

@Transactional
public void createOrder() {
System.out.println("创建订单");
}
}

你以为你注入的是原始 OrderService

但很多情况下,Spring 注入给你的其实是一个代理对象。

这个代理对象在调用 createOrder() 前后做事务控制:

1
2
3
4
5
6
开启事务
调用真实 createOrder()
提交事务

如果异常:
回滚事务

这就是代理模式的典型应用。

chapter 16:Spring AOP 日志案例

先定义一个注解。

1
2
3
4
5
6
7
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogCost {
}

定义切面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogCostAspect {

@Around("@annotation(LogCost)")
public Object logCost(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();

String methodName = joinPoint.getSignature().toShortString();

try {
System.out.println("[AOP] 开始调用方法:" + methodName);

Object result = joinPoint.proceed();

System.out.println("[AOP] 方法调用成功:" + methodName);

return result;
} catch (Throwable e) {
System.out.println("[AOP] 方法调用失败:" + methodName + ",error = " + e.getMessage());

throw e;
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[AOP] 方法耗时:" + cost + " ms");
}
}
}

业务服务:

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.stereotype.Service;

@Service
public class OrderApplicationService {

@LogCost
public Long createOrder(CreateOrderCommand command) {
System.out.println("执行业务逻辑:创建订单");

return 10001L;
}
}

Controller 或其他服务调用:

1
Long orderId = orderApplicationService.createOrder(command);

调用时会经过 Spring AOP 代理。

业务方法中不需要写日志和耗时代码。

这就是代理模式在 Spring 中的典型体现。

chapter 17:事务为什么也是代理思想

Spring 声明式事务也是基于代理思想实现的。

1
2
3
4
@Transactional
public void createOrder() {
orderRepository.save(order);
}

大致流程是:

1
2
3
4
5
6
7
8
9
10
11
客户端调用代理对象 createOrder()


事务拦截器开启事务


调用真实对象 createOrder()


成功则提交事务
失败则回滚事务

所以事务不是“魔法”。

它本质上是代理对象在方法前后做了增强。

这也解释了一个经典问题:

为什么同一个类内部方法调用可能导致 @Transactional 失效?

例如:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class OrderService {

public void outer() {
inner();
}

@Transactional
public void inner() {
// 数据库操作
}
}

outer() 内部直接调用 inner(),没有经过 Spring 代理对象。

所以事务增强可能不会生效。

这就是代理模式在 Spring 中非常重要的实践影响。

chapter 18:代理模式案例背景:订单服务增强

下面设计一个完整 Java 后端案例。

需求如下:

  1. 有一个订单服务,负责创建订单;
  2. 不希望在订单服务里写权限、日志、耗时统计;
  3. 使用代理对象增强订单服务;
  4. 分别演示静态代理和 JDK 动态代理;
  5. 让业务层仍然依赖统一接口。

chapter 19:定义请求对象 CreateOrderCommand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CreateOrderCommand {

private final Long userId;

private final Long productId;

private final Integer quantity;

public CreateOrderCommand(Long userId, Long productId, Integer quantity) {
this.userId = userId;
this.productId = productId;
this.quantity = quantity;
}

public Long getUserId() {
return userId;
}

public Long getProductId() {
return productId;
}

public Integer getQuantity() {
return quantity;
}
}

chapter 20:定义订单服务接口

1
2
3
4
5
6
public interface OrderService {

Long createOrder(CreateOrderCommand command);

void cancelOrder(Long orderId);
}

这个接口就是代理模式中的 Subject。

真实对象和代理对象都会实现这个接口。

chapter 21:定义真实订单服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class RealOrderService implements OrderService {

@Override
public Long createOrder(CreateOrderCommand command) {
validate(command);

System.out.println("创建订单,userId = " + command.getUserId()
+ ",productId = " + command.getProductId()
+ ",quantity = " + command.getQuantity());

return 10001L;
}

@Override
public void cancelOrder(Long orderId) {
if (orderId == null) {
throw new IllegalArgumentException("orderId can not be null");
}

System.out.println("取消订单,orderId = " + orderId);
}

private void validate(CreateOrderCommand command) {
if (command == null) {
throw new IllegalArgumentException("command can not be null");
}

if (command.getUserId() == null) {
throw new IllegalArgumentException("userId can not be null");
}

if (command.getProductId() == null) {
throw new IllegalArgumentException("productId can not be null");
}

if (command.getQuantity() == null || command.getQuantity() <= 0) {
throw new IllegalArgumentException("quantity must be greater than 0");
}
}
}

真实服务只关心订单业务本身。

权限、日志、耗时统计不放在这里。

chapter 22:静态代理实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class OrderServiceProxy implements OrderService {

private final OrderService target;

public OrderServiceProxy(OrderService target) {
this.target = target;
}

@Override
public Long createOrder(CreateOrderCommand command) {
checkPermission(command.getUserId(), "order:create");

long start = System.currentTimeMillis();

try {
System.out.println("[静态代理] 开始创建订单");

Long orderId = target.createOrder(command);

System.out.println("[静态代理] 创建订单成功,orderId = " + orderId);

return orderId;
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[静态代理] createOrder 耗时:" + cost + " ms");
}
}

@Override
public void cancelOrder(Long orderId) {
checkPermission(null, "order:cancel");

long start = System.currentTimeMillis();

try {
System.out.println("[静态代理] 开始取消订单");

target.cancelOrder(orderId);

System.out.println("[静态代理] 取消订单成功");
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[静态代理] cancelOrder 耗时:" + cost + " ms");
}
}

private void checkPermission(Long userId, String permissionCode) {
System.out.println("[静态代理] 权限校验,userId = " + userId
+ ",permissionCode = " + permissionCode);
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StaticProxyClient {

public static void main(String[] args) {
OrderService target = new RealOrderService();

OrderService proxy = new OrderServiceProxy(target);

CreateOrderCommand command = new CreateOrderCommand(1L, 100L, 2);

Long orderId = proxy.createOrder(command);

proxy.cancelOrder(orderId);
}
}

静态代理简单,但每个接口都要手写代理类。

如果系统里很多 Service 都要增强,就会很累。

chapter 23:JDK 动态代理实现通用增强

定义一个通用 InvocationHandler。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ServiceInvocationHandler implements InvocationHandler {

private final Object target;

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

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
checkPermission(method);

long start = System.currentTimeMillis();

try {
System.out.println("[动态代理] 开始调用:" + method.getName());

Object result = method.invoke(target, args);

System.out.println("[动态代理] 调用成功:" + method.getName());

return result;
} catch (Exception e) {
System.out.println("[动态代理] 调用失败:" + method.getName()
+ ",error = " + e.getMessage());

throw e;
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[动态代理] 方法耗时:" + cost + " ms");
}
}

private void checkPermission(Method method) {
System.out.println("[动态代理] 权限校验,method = " + method.getName());
}
}

定义代理工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.lang.reflect.Proxy;

public class ProxyFactory {

@SuppressWarnings("unchecked")
public static <T> T createProxy(T target, Class<T> interfaceType) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{interfaceType},
new ServiceInvocationHandler(target)
);
}
}

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DynamicProxyClient {

public static void main(String[] args) {
OrderService target = new RealOrderService();

OrderService proxy = ProxyFactory.createProxy(target, OrderService.class);

CreateOrderCommand command = new CreateOrderCommand(1L, 100L, 2);

Long orderId = proxy.createOrder(command);

proxy.cancelOrder(orderId);
}
}

这样,代理逻辑可以复用。

只要目标对象实现接口,就可以用同一个代理工厂增强。

chapter 24:代理模式和装饰器模式的区别

代理模式和装饰器模式很像。

它们通常都:

  • 实现同一个接口;
  • 持有一个目标对象;
  • 在目标对象调用前后做一些事情。

但目的不同。

对比项 代理模式 装饰器模式
核心目的 控制访问 动态增强功能
关注点 是否允许访问、如何访问 给对象叠加额外职责
目标对象 通常由代理控制 通常由客户端传入并自由组合
使用场景 权限、事务、远程代理、懒加载 日志、重试、限流、缓存、缓冲
客户端感知 通常不关心代理存在 可能主动组合装饰器
示例 Spring AOP 事务代理 BufferedInputStream 包装 FileInputStream

一句话区分:

代理模式更强调“控制访问”。

装饰器模式更强调“增强功能”。

当然,在真实项目中,两者经常重叠。

例如日志代理既可以被称为代理,也可以被看作装饰器。

不要为了名字打架,重点看设计意图。

chapter 25:代理模式和适配器模式的区别

对比项 代理模式 适配器模式
目的 控制访问目标对象 转换不兼容接口
接口关系 通常和目标对象接口一致 通常接口不一致
关注点 访问控制、增强、转发 接口兼容
示例 OrderServiceProxy AliPayClientAdapter

代理模式说:

你可以访问目标对象,但要先经过我。

适配器模式说:

你的接口和我想要的不一样,我帮你转一下。

chapter 26:代理模式和外观模式的区别

对比项 代理模式 外观模式
目的 控制访问某个对象 简化复杂子系统调用
包装对象数量 通常一个目标对象 通常多个子系统
接口关系 通常和目标对象相同 通常提供更高层接口
关注点 访问控制 简化使用
示例 TransactionProxy CheckoutFacade

代理像“代办人”。

外观像“服务大厅”。

代理通常代表一个对象,外观通常封装一组子系统。

chapter 27:代理模式和责任链模式的区别

对比项 代理模式 责任链模式
结构 代理对象持有目标对象 多个处理器形成链
目的 控制对目标对象访问 请求沿链传递处理
调用目标 通常最终调用一个真实对象 可能由链上任意节点处理
示例 AOP 代理 Service FilterChain、审批链

责任链强调请求在多个处理器之间流转。

代理强调通过中间对象控制目标对象访问。

chapter 28:代理模式的优点

1. 隔离客户端和真实对象

客户端通过代理访问真实对象,真实对象可以被保护起来。

2. 可以增强目标对象

代理可以在调用前后加权限、日志、事务、监控等逻辑。

3. 符合开闭原则

不修改目标类,就能增加控制逻辑。

4. 支持延迟加载

真实对象可以等到真正使用时再创建。

5. 支持远程调用透明化

RPC 框架可以让客户端像调用本地接口一样调用远程服务。

6. 支持横切逻辑复用

动态代理可以统一处理很多类的通用逻辑。

chapter 29:代理模式的缺点

1. 增加系统复杂度

代理对象会增加一层调用结构。

2. 调试链路变长

尤其是 Spring AOP、事务、RPC、MyBatis 代理叠加时,调用链会比较复杂。

3. 动态代理有一定性能成本

反射、字节码生成、方法拦截都会有成本。

大多数业务系统中这不是瓶颈,但高性能场景要注意。

4. JDK 动态代理要求接口

没有接口时不能直接使用 JDK 动态代理。

5. CGLIB 不能代理 final 类和 final 方法

因为它基于继承和方法重写。

6. 容易产生代理失效问题

例如 Spring 中同类内部方法调用可能绕过代理,导致事务、AOP 不生效。

chapter 30:适用场景

代理模式适合以下场景。

1. 权限控制

调用目标对象前先检查权限。

2. 事务控制

方法执行前开启事务,成功提交,失败回滚。

3. 日志和监控

统一记录方法调用日志、耗时、异常。

4. 缓存代理

调用目标方法前先查缓存。

5. 远程代理

把本地接口调用转成远程网络调用。

例如 RPC、Feign、Dubbo、gRPC 客户端代理。

6. 虚拟代理

真实对象创建成本高,延迟到需要时再创建。

7. MyBatis Mapper

MyBatis 的 Mapper 接口背后也是代理对象在工作。

你没有写实现类,但可以调用接口方法。

1
userMapper.selectById(1L);

背后就是框架生成代理对象,解析 Mapper 方法并执行 SQL。

chapter 31:不适合使用代理模式的场景

以下场景不建议使用代理模式。

1. 只是简单方法调用

如果没有访问控制或增强需求,没必要加代理。

2. 业务逻辑应该直接写在领域对象里

不要把核心业务规则藏在代理里。

代理适合横切逻辑,不适合承载核心业务。

3. 代理链太复杂

如果一个对象被代理、装饰、AOP、RPC、过滤器层层包裹,排查问题会很痛苦。

4. 对性能极端敏感且调用频率极高

高频底层方法可能不适合使用反射代理。

5. 团队不理解代理机制

Spring AOP、事务失效、动态代理类型转换等问题都需要理解代理机制。

否则出问题时很容易玄学调试。

chapter 32:真实项目中的实践建议

1. 业务代码优先面向接口

JDK 动态代理依赖接口。

即使使用 CGLIB,面向接口编程也更利于解耦。

1
2
public interface OrderService {
}

2. 不要在代理里写核心业务

代理适合写:

  • 权限;
  • 日志;
  • 事务;
  • 缓存;
  • 限流;
  • 监控。

不适合写:

  • 创建订单核心逻辑;
  • 扣库存规则;
  • 价格计算;
  • 支付状态流转。

3. Spring 中注意自调用问题

同一个类内部方法调用不会经过代理。

如果需要事务或 AOP 生效,要通过代理对象调用,或者调整方法边界。

4. 注意 final 类和 final 方法

如果你希望 Spring AOP 或 CGLIB 代理生效,不要随意给类和方法加 final

5. 区分代理和装饰器

如果重点是访问控制、事务、RPC,偏代理。

如果重点是自由叠加日志、重试、限流,偏装饰器。

6. 日志代理要避免打印敏感数据

比如密码、token、身份证号、手机号等。

代理层很容易统一打印参数,但别把敏感信息也统一送上日志广场。

7. 代理异常要处理好

反射调用目标方法时,异常可能被包装。

JDK 反射常见 InvocationTargetException

真实项目中要做好异常解包和转换。

8. 不要迷信动态代理

动态代理很强,但不是所有场景都要上。

有时候一个显式的装饰器、模板方法或拦截器链更清晰。

chapter 33:完整案例代码汇总

请求对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CreateOrderCommand {

private final Long userId;

private final Long productId;

private final Integer quantity;

public CreateOrderCommand(Long userId, Long productId, Integer quantity) {
this.userId = userId;
this.productId = productId;
this.quantity = quantity;
}

public Long getUserId() {
return userId;
}

public Long getProductId() {
return productId;
}

public Integer getQuantity() {
return quantity;
}
}

抽象主题

1
2
3
4
5
6
public interface OrderService {

Long createOrder(CreateOrderCommand command);

void cancelOrder(Long orderId);
}

真实主题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class RealOrderService implements OrderService {

@Override
public Long createOrder(CreateOrderCommand command) {
validate(command);

System.out.println("创建订单,userId = " + command.getUserId()
+ ",productId = " + command.getProductId()
+ ",quantity = " + command.getQuantity());

return 10001L;
}

@Override
public void cancelOrder(Long orderId) {
if (orderId == null) {
throw new IllegalArgumentException("orderId can not be null");
}

System.out.println("取消订单,orderId = " + orderId);
}

private void validate(CreateOrderCommand command) {
if (command == null) {
throw new IllegalArgumentException("command can not be null");
}

if (command.getUserId() == null) {
throw new IllegalArgumentException("userId can not be null");
}

if (command.getProductId() == null) {
throw new IllegalArgumentException("productId can not be null");
}

if (command.getQuantity() == null || command.getQuantity() <= 0) {
throw new IllegalArgumentException("quantity must be greater than 0");
}
}
}

静态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class OrderServiceProxy implements OrderService {

private final OrderService target;

public OrderServiceProxy(OrderService target) {
this.target = target;
}

@Override
public Long createOrder(CreateOrderCommand command) {
checkPermission(command.getUserId(), "order:create");

long start = System.currentTimeMillis();

try {
System.out.println("[静态代理] 开始创建订单");

Long orderId = target.createOrder(command);

System.out.println("[静态代理] 创建订单成功,orderId = " + orderId);

return orderId;
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[静态代理] createOrder 耗时:" + cost + " ms");
}
}

@Override
public void cancelOrder(Long orderId) {
checkPermission(null, "order:cancel");

long start = System.currentTimeMillis();

try {
System.out.println("[静态代理] 开始取消订单");

target.cancelOrder(orderId);

System.out.println("[静态代理] 取消订单成功");
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[静态代理] cancelOrder 耗时:" + cost + " ms");
}
}

private void checkPermission(Long userId, String permissionCode) {
System.out.println("[静态代理] 权限校验,userId = " + userId
+ ",permissionCode = " + permissionCode);
}
}

JDK 动态代理处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ServiceInvocationHandler implements InvocationHandler {

private final Object target;

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

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
checkPermission(method);

long start = System.currentTimeMillis();

try {
System.out.println("[动态代理] 开始调用:" + method.getName());

Object result = method.invoke(target, args);

System.out.println("[动态代理] 调用成功:" + method.getName());

return result;
} finally {
long cost = System.currentTimeMillis() - start;

System.out.println("[动态代理] 方法耗时:" + cost + " ms");
}
}

private void checkPermission(Method method) {
System.out.println("[动态代理] 权限校验,method = " + method.getName());
}
}

代理工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.lang.reflect.Proxy;

public class ProxyFactory {

@SuppressWarnings("unchecked")
public static <T> T createProxy(T target, Class<T> interfaceType) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{interfaceType},
new ServiceInvocationHandler(target)
);
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ProxyPatternDemo {

public static void main(String[] args) {
OrderService target = new RealOrderService();

OrderService staticProxy = new OrderServiceProxy(target);

CreateOrderCommand command = new CreateOrderCommand(1L, 100L, 2);

Long orderId = staticProxy.createOrder(command);

staticProxy.cancelOrder(orderId);

System.out.println("----------");

OrderService dynamicProxy = ProxyFactory.createProxy(target, OrderService.class);

Long dynamicOrderId = dynamicProxy.createOrder(command);

dynamicProxy.cancelOrder(dynamicOrderId);
}
}

chapter 34:一句话总结

代理模式的本质是:

不直接访问目标对象,而是通过代理对象控制对目标对象的访问,并在访问前后加入必要的控制或增强逻辑。

它特别适合:

  • 权限校验;
  • 事务控制;
  • 日志监控;
  • 缓存处理;
  • 远程调用;
  • 延迟加载;
  • 方法增强;
  • 框架拦截。

在 Java 后端开发中,代理模式非常重要。

Spring AOP、声明式事务、MyBatis Mapper、RPC 客户端、Feign、Dubbo 等技术背后都能看到代理思想。

静态代理适合理解原理。

JDK 动态代理适合基于接口的通用增强。

CGLIB 适合没有接口的类代理。

Spring AOP 则是代理模式在企业级开发中的工程化落地。

代理模式最关键的不是“会不会写 Proxy.newProxyInstance”,而是理解:

为什么要让访问经过一层代理?

如果这层代理让访问更安全、更可控、更可扩展,它就是好设计。

如果这层代理只是让调用链更长、更难查、更玄学,那它就是给代码戴了一顶没用的帽子。

参考资料

  • Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
  • Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices.
  • Joshua Bloch. Effective Java.
  • Brian Goetz. Java Concurrency in Practice.
  • Oracle Java Documentation: java.lang.reflect.Proxy.
  • Spring Framework Documentation: AOP.
  • Spring Framework Documentation: Declarative Transaction Management.
  • Refactoring Guru: Proxy Pattern.
  • SourceMaking: Proxy Design Pattern.

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


设计模式:代理模式
https://allendericdalexander.github.io/2026/04/01/java/design/13proxy-pattern-blog/
作者
AtLuoFu
发布于
2026年4月1日
许可协议