代理模式:设计与实践
一、什么是代理模式
1. 基本定义
代理模式(Proxy Pattern)是一种结构型设计模式,由《设计模式:可复用面向对象软件的基础》(GOF著作)定义为:为其他对象提供一种代理以控制对这个对象的访问。
该模式通过引入一个代理对象,在客户端与目标对象之间建立一层中间层,代理对象负责控制对目标对象的访问,并可以在访问前后添加额外操作,从而实现对目标对象的功能增强或访问限制。
2. 核心思想
代理模式的核心在于通过代理对象间接访问目标对象,将目标对象的访问控制和功能增强从目标对象本身剥离出来,实现了访问者与目标对象的解耦。代理对象与目标对象实现相同的接口,使得客户端无需修改代码即可通过代理访问目标对象,同时代理可以灵活地添加额外逻辑。
二、代理模式的特点
1. 接口一致性
代理对象与目标对象实现相同的接口,客户端可以透明地切换使用代理对象或目标对象,无需修改调用代码。
2. 间接访问
客户端不直接访问目标对象,而是通过代理对象间接访问,代理成为客户端与目标对象之间的中间层。
3. 功能增强
代理对象可以在调用目标对象的方法前后添加额外操作(如日志记录、安全验证),实现对目标对象的功能增强。
4. 访问控制
代理可以控制对目标对象的访问权限,如限制某些客户端的访问、实现懒加载或缓存结果等。
5. 职责分离
将非核心业务逻辑(如日志、安全)从目标对象中剥离,由代理对象负责,使目标对象专注于核心业务,符合单一职责原则。
| 特点 | 说明 |
|---|---|
| 接口一致性 | 代理与目标对象实现相同接口,客户端透明访问 |
| 间接访问 | 客户端通过代理间接访问目标对象,代理作为中间层 |
| 功能增强 | 代理可在调用前后添加额外操作,增强目标对象功能 |
| 访问控制 | 代理可限制访问权限,实现懒加载、缓存等控制逻辑 |
| 职责分离 | 非核心逻辑由代理负责,目标对象专注核心业务 |
三、代理模式的标准代码实现
1. 模式结构
代理模式包含三个核心角色:
- 抽象主题(Subject):代理与目标对象共同实现的接口
- 真实主题(RealSubject):被代理的目标对象,实现核心业务逻辑
- 代理(Proxy):实现抽象主题接口,包含对真实主题的引用,控制访问并增强功能
2. 代码实现示例
2.1 抽象主题接口
/**
* 抽象主题接口
* 定义代理与目标对象的共同接口
*/
public interface Subject {
void doOperation();
String getResult();
}
2.2 真实主题实现
/**
* 真实主题类
* 实现核心业务逻辑
*/
public class RealSubject implements Subject {
@Override
public void doOperation() {
// 模拟核心业务操作
System.out.println("执行真实业务逻辑...");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public String getResult() {
return "业务处理结果";
}
}
2.3 静态代理实现
/**
* 静态代理类
* 在编译期确定代理关系,直接实现抽象主题接口
*/
public class StaticProxy implements Subject {
// 持有真实主题的引用
private final Subject realSubject;
// 通过构造函数注入真实主题
public StaticProxy(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public void doOperation() {
// 前置增强:调用前操作
System.out.println("静态代理 - 开始执行操作");
long startTime = System.currentTimeMillis();
// 调用真实主题的方法
realSubject.doOperation();
// 后置增强:调用后操作
long endTime = System.currentTimeMillis();
System.out.println("静态代理 - 操作执行完成,耗时:" + (endTime - startTime) + "ms");
}
@Override
public String getResult() {
// 前置处理
System.out.println("静态代理 - 获取结果");
// 调用真实主题
String result = realSubject.getResult();
// 后置处理:增强结果
return "静态代理处理后:" + result;
}
}
2.4 动态代理实现(JDK动态代理)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 动态代理处理器
* 在运行期动态生成代理对象
*/
public class DynamicProxyHandler implements InvocationHandler {
// 持有真实主题的引用
private final Object realSubject;
public DynamicProxyHandler(Object realSubject) {
this.realSubject = realSubject;
}
/**
* 动态代理核心方法
* @param proxy 代理对象
* @param method 被调用的方法
* @param args 方法参数
* @return 方法返回值
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("动态代理 - 开始执行方法:" + method.getName());
long startTime = System.currentTimeMillis();
// 调用真实主题的方法
Object result = method.invoke(realSubject, args);
// 后置增强
long endTime = System.currentTimeMillis();
System.out.println("动态代理 - 方法执行完成,耗时:" + (endTime - startTime) + "ms");
// 增强返回结果(如果是String类型)
if (result instanceof String) {
return "动态代理处理后:" + result;
}
return result;
}
/**
* 创建代理对象
* @return 动态生成的代理对象
*/
public static Subject createProxy(Subject realSubject) {
return (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
new DynamicProxyHandler(realSubject)
);
}
}
2.5 客户端使用示例
/**
* 客户端示例
* 演示静态代理和动态代理的使用
*/
public class ProxyClient {
public static void main(String[] args) {
// 创建真实主题
Subject realSubject = new RealSubject();
// 使用静态代理
System.out.println("=== 静态代理调用 ===");
Subject staticProxy = new StaticProxy(realSubject);
staticProxy.doOperation();
System.out.println(staticProxy.getResult());
// 使用动态代理
System.out.println("\n=== 动态代理调用 ===");
Subject dynamicProxy = DynamicProxyHandler.createProxy(realSubject);
dynamicProxy.doOperation();
System.out.println(dynamicProxy.getResult());
}
}
3. 代码实现特点总结
| 代理类型 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 静态代理 | 编译期手动编写代理类,实现与目标相同接口 | 实现简单,性能好,可直观控制增强逻辑 | 代理类与目标类一一对应,代码冗余,维护成本高 | 代理类少、逻辑简单的场景 |
| 动态代理 | 运行期通过反射动态生成代理类,无需手动编写 | 无需编写代理类,减少代码冗余,通用性强 | 反射调用有性能损耗,增强逻辑集中在invoke方法 | 代理类多、逻辑复杂的场景,如AOP框架 |
四、支付框架设计中代理模式的运用
以批支付接口安全代理为例,说明代理模式在支付系统中的具体实现:
1. 场景分析
批支付接口用于处理商户的批量付款请求(如薪资代发、供应商结算),需要在请求处理前后进行多重安全校验(签名验证、权限检查、敏感信息加密)和日志记录,同时要适配不同支付渠道的接口规范。使用代理模式可以将这些非业务逻辑与核心支付处理分离,提高代码可维护性。
2. 设计实现
2.1 批支付接口(抽象主题)
import java.util.List;
/**
* 批支付接口(抽象主题)
* 定义批支付的核心操作
*/
public interface BatchPaymentService {
/**
* 提交批量支付请求
* @param request 批量支付请求
* @return 支付结果
*/
BatchPaymentResult submit(BatchPaymentRequest request);
}
2.2 批支付请求与结果类
import java.math.BigDecimal;
import java.util.List;
/**
* 批量支付请求
*/
public class BatchPaymentRequest {
private String batchNo; // 批次号
private String merchantId; // 商户ID
private List<PaymentItem> items; // 支付明细
private String sign; // 签名
private String notifyUrl; // 回调地址
// getter和setter ...
}
/**
* 支付明细项
*/
public class PaymentItem {
private String orderId; // 订单号
private String accountNo; // 收款账号
private String accountName; // 收款人姓名
private BigDecimal amount; // 金额
private String bankCode; // 银行编码
}
/**
* 批量支付结果
*/
public class BatchPaymentResult {
private String batchNo;
private String status; // 处理状态
private int totalCount; // 总笔数
private int successCount; // 成功笔数
private int failCount; // 失败笔数
private String resultMsg; // 结果描述
// getter和setter ...
}
2.3 核心批支付服务(真实主题)
/**
* 核心批支付服务(真实主题)
* 实现批支付的核心业务逻辑
*/
public class CoreBatchPaymentService implements BatchPaymentService {
private final PaymentChannelManager channelManager;
public CoreBatchPaymentService(PaymentChannelManager channelManager) {
this.channelManager = channelManager;
}
@Override
public BatchPaymentResult submit(BatchPaymentRequest request) {
// 1. 获取商户对应的支付渠道
PaymentChannel channel = channelManager.getChannel(request.getMerchantId());
// 2. 转换请求格式为渠道要求的格式
Object channelRequest = convertToChannelRequest(request, channel);
// 3. 调用渠道接口提交批支付
Object channelResult = channel.submitBatchPayment(channelRequest);
// 4. 转换渠道结果为统一格式
return convertToBatchResult(channelResult, request.getBatchNo());
}
// 转换为渠道请求格式
private Object convertToChannelRequest(BatchPaymentRequest request, PaymentChannel channel) {
// 实际实现中会根据渠道类型转换请求格式
System.out.println("转换批支付请求为[" + channel.getCode() + "]格式");
return new Object(); // 示例返回
}
// 转换为统一结果格式
private BatchPaymentResult convertToBatchResult(Object channelResult, String batchNo) {
// 实际实现中会解析渠道返回结果并转换
BatchPaymentResult result = new BatchPaymentResult();
result.setBatchNo(batchNo);
result.setStatus("PROCESSING");
result.setTotalCount(10); // 示例值
result.setSuccessCount(0);
result.setFailCount(0);
result.setResultMsg("批支付已提交,处理中");
return result;
}
}
2.4 安全代理(静态代理)
/**
* 批支付安全代理
* 处理签名验证、敏感信息加密等安全逻辑
*/
public class BatchPaymentSecurityProxy implements BatchPaymentService {
private final BatchPaymentService target;
private final SignatureService signatureService;
private final EncryptionService encryptionService;
public BatchPaymentSecurityProxy(BatchPaymentService target) {
this.target = target;
this.signatureService = new SignatureService();
this.encryptionService = new EncryptionService();
}
@Override
public BatchPaymentResult submit(BatchPaymentRequest request) {
// 1. 前置增强:验证签名
boolean signValid = signatureService.verify(
buildSignContent(request),
request.getSign(),
request.getMerchantId()
);
if (!signValid) {
throw new SecurityException("批支付请求签名无效,批次号:" + request.getBatchNo());
}
// 2. 前置增强:加密敏感信息(如收款账号、姓名)
encryptSensitiveInfo(request);
// 3. 调用核心服务
BatchPaymentResult result = target.submit(request);
// 4. 后置增强:记录安全日志
securityLogService.log(
request.getMerchantId(),
request.getBatchNo(),
"批支付安全处理完成",
System.currentTimeMillis()
);
return result;
}
// 构建签名内容
private String buildSignContent(BatchPaymentRequest request) {
// 实际实现中会按规则拼接需要签名的字段
return "batchNo=" + request.getBatchNo() +
"&merchantId=" + request.getMerchantId() +
"&totalCount=" + request.getItems().size();
}
// 加密敏感信息
private void encryptSensitiveInfo(BatchPaymentRequest request) {
// 加密收款账号、姓名等敏感信息
request.getItems().forEach(item -> {
item.setAccountNo(encryptionService.encrypt(item.getAccountNo()));
item.setAccountName(encryptionService.encrypt(item.getAccountName()));
});
}
}
2.5 日志代理(动态代理)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.LocalDateTime;
/**
* 批支付日志代理(动态代理)
* 记录批支付的详细日志
*/
public class BatchPaymentLogProxy implements InvocationHandler {
private final Object target;
private final PaymentLogService logService;
public BatchPaymentLogProxy(Object target) {
this.target = target;
this.logService = new PaymentLogService();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("submit".equals(method.getName()) && args.length > 0 && args[0] instanceof BatchPaymentRequest) {
BatchPaymentRequest request = (BatchPaymentRequest) args[0];
String batchNo = request.getBatchNo();
String merchantId = request.getMerchantId();
// 前置日志:记录请求开始
logService.info("批支付开始,批次号:" + batchNo + ",商户:" + merchantId);
long startTime = System.currentTimeMillis();
try {
// 调用目标方法
Object result = method.invoke(target, args);
// 后置日志:记录请求成功
long costTime = System.currentTimeMillis() - startTime;
logService.info("批支付完成,批次号:" + batchNo + ",耗时:" + costTime + "ms");
return result;
} catch (Exception e) {
// 异常日志:记录请求失败
logService.error("批支付失败,批次号:" + batchNo + ",错误:" + e.getMessage(), e);
throw e;
}
}
// 非submit方法直接调用
return method.invoke(target, args);
}
// 创建代理对象
public static BatchPaymentService createProxy(BatchPaymentService target) {
return (BatchPaymentService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new BatchPaymentLogProxy(target)
);
}
}
2.6 代理链组合使用
/**
* 批支付服务工厂
* 组合多个代理形成代理链
*/
public class BatchPaymentServiceFactory {
public static BatchPaymentService createService() {
// 1. 创建核心服务
PaymentChannelManager channelManager = new PaymentChannelManager();
BatchPaymentService coreService = new CoreBatchPaymentService(channelManager);
// 2. 包装安全代理
BatchPaymentService securityService = new BatchPaymentSecurityProxy(coreService);
// 3. 包装日志代理
BatchPaymentService logService = BatchPaymentLogProxy.createProxy(securityService);
// 4. 可以继续添加其他代理(如权限代理、限流代理)
return logService;
}
}
// 客户端使用
public class BatchPaymentClient {
public static void main(String[] args) {
// 获取代理链包装后的服务
BatchPaymentService service = BatchPaymentServiceFactory.createService();
// 创建批支付请求
BatchPaymentRequest request = new BatchPaymentRequest();
request.setBatchNo("BATCH20231001001");
request.setMerchantId("MCH001");
// 设置其他请求参数...
// 提交批支付
BatchPaymentResult result = service.submit(request);
System.out.println("批支付处理结果:" + result.getStatus());
}
}
3. 模式价值体现
- 职责清晰:核心服务专注于支付处理,安全代理专注于安全验证,日志代理专注于日志记录,符合单一职责原则
- 可扩展性:新增功能(如限流、权限控制)只需添加新代理,无需修改核心服务代码
- 可配置性:通过代理链组合,可以根据不同环境(开发/测试/生产)灵活配置代理,如生产环境启用安全代理,测试环境禁用
- 安全性:安全逻辑集中管理,便于统一升级加密算法、签名规则,符合支付行业安全规范
五、开源框架中代理模式的运用
以Spring AOP为例,说明代理模式在框架级别的应用:
1. 核心实现分析
Spring AOP(面向切面编程)的底层实现依赖动态代理模式,用于将日志、事务、安全等横切关注点与业务逻辑分离。
1.1 Spring AOP中的代理实现
Spring AOP根据目标对象是否实现接口,选择不同的代理方式:
- 若目标对象实现接口,使用JDK动态代理(与前面的动态代理示例原理相同)
- 若目标对象未实现接口,使用CGLIB代理(通过继承目标类生成代理子类)
1.2 事务管理中的代理应用
Spring的声明式事务管理是代理模式的典型应用:
// 业务服务接口
public interface OrderService {
void createOrder(Order order);
}
// 业务服务实现
public class OrderServiceImpl implements OrderService {
@Override
public void createOrder(Order order) {
// 保存订单
orderDao.save(order);
// 扣减库存
inventoryService.reduce(order.getProductId(), order.getQuantity());
}
}
// 配置类(启用事务)
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean
public OrderService orderService() {
return new OrderServiceImpl();
}
// 其他Bean定义...
}
当使用@Transactional注解标记createOrder方法时,Spring会为OrderService创建代理对象:
- 代理对象在调用
createOrder前开启事务 - 调用目标方法执行业务逻辑
- 若方法正常返回,代理对象提交事务
- 若方法抛出异常,代理对象回滚事务
1.3 代理模式在Spring AOP中的价值
- 代码解耦:事务管理逻辑与业务逻辑分离,开发者无需手动编写事务控制代码
- 灵活性:通过注解即可控制事务,无需修改业务代码
- 一致性:统一的事务管理逻辑,避免重复编码和遗漏
- 可扩展性:除事务外,还可通过AOP实现日志、安全等功能,均基于代理模式
六、总结
1. 代理模式的适用场景
- 当需要为对象添加日志、安全、事务等横切关注点时
- 当需要控制对敏感对象的访问(如权限控制)时
- 当需要实现懒加载(如延迟初始化重量级对象)时
- 当需要对目标对象进行功能增强(如缓存结果)时
- 当需要适配不同接口(如适配器模式与代理模式结合)时
2. 代理模式与其他模式的区别
- 与装饰器模式:两者都实现相同接口,都可以增强对象功能。但代理模式侧重于控制访问,装饰器模式侧重于动态添加功能;代理通常持有目标对象的引用,装饰器通常通过构造函数传递目标对象并可以嵌套使用。
- 与适配器模式:两者都作为中间层。但代理模式的代理与目标对象实现相同接口,适配器模式的适配器与适配者实现不同接口;代理模式用于增强功能,适配器模式用于接口转换。
3. 支付系统中的实践价值
- 安全增强:通过代理统一实现签名验证、加密解密,确保支付安全
- 可维护性:横切逻辑集中管理,便于调试和升级,降低代码耦合度
- 合规性:支付行业有严格的合规要求(如PCI DSS),通过代理可以统一实现敏感信息脱敏、审计日志等合规功能
- 可扩展性:支付系统需要不断接入新渠道、新功能,代理模式使扩展更加灵活,不影响现有业务
4. 实践建议
- 优先使用动态代理减少代码冗余,尤其当需要代理多个类时
- 代理链不宜过长,否则会影响性能和调试难度
- 对于核心支付链路,考虑使用静态代理减少反射带来的性能损耗
- 结合依赖注入框架(如Spring)管理代理对象,简化代理链的创建和使用
代理模式通过引入中间层,实现了对目标对象的访问控制和功能增强,在支付系统中是处理横切关注点的理想方案,能够有效提高代码质量和系统可维护性。