代理模式:设计与实践

25 阅读13分钟

代理模式:设计与实践

一、什么是代理模式

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)管理代理对象,简化代理链的创建和使用

代理模式通过引入中间层,实现了对目标对象的访问控制和功能增强,在支付系统中是处理横切关注点的理想方案,能够有效提高代码质量和系统可维护性。