摘要

本文介绍了代理设计模式(Proxy Pattern),这是一种结构型设计模式,通过引入代理对象来控制对原对象的访问。文章详细阐述了代理模式的定义、作用、优点、缺点,并提供了类图实现。此外,还探讨了代理模式的多种使用场景,包括远程代理、虚拟代理、保护代理等,并以图片延迟加载为例,展示了虚拟代理的实际应用。

1. 代理设计模式是什么

1.1. 代理设计模式

代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做 Proxy 或 Surrogate,它是一种对象结构型模式。

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。如上面的示例中,Tony 因为不在家,所以不能亲自接收包裹,但他可以叫 Wendy 来代他接收,这里 Wendy 就是代理,她代理了 Tony 的身份去接收快递。

1.2. 代理设计模式作用

代理对象可以在客户端和目标对象之间起到中间调和的作用,并且可以通过代理对象隐藏不希望被客户端看到的内容和服务,或者添加客户需要的额外服务。

在实现生活中能找到非常的代理模式的模型:火车票/机票的代售点;银行支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制;代表公司出席一些商务会议。

1.2.1. 优点

  • 增强功能:在不修改目标对象的情况下添加额外功能(如权限校验、缓存)。
  • 解耦目标对象:代理对象可以屏蔽底层实现细节,让客户端专注于业务逻辑。
  • 控制访问:通过代理可以限制对目标对象的直接访问。

1.2.2. 缺点

  • 增加了系统复杂性。
  • 可能引入额外的性能开销(特别是动态代理)。

2. 代理设计模式类图实现

2.1. 代理设计类模型

代理模式有三个关键要素,它们分别是:

  1. 主题(Subject):定义“操作/活动/任务”的接口类。
  2. 真实主题(RealSubject):真正完成“操作/活动/任务”的具体类。
  3. 代理主题(ProxySubject):代替真实主题完成“操作/活动/任务”的代理类。
// 定义主题接口
interface Subject {
    void request();
}

// 真实主题类
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject todo something...");
    }
}

// 代理主题类
class ProxySubject implements Subject {
    private RealSubject realSubject;

    // 构造函数,传入真实主题对象
    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        preRequest();
        if (realSubject != null) {
            realSubject.request();
        }
        afterRequest();
    }

    private void preRequest() {
        System.out.println("preRequest");
    }

    private void afterRequest() {
        System.out.println("afterRequest");
    }
}

// 客户端调用类
public class Client {
    public static void main(String[] args) {
        RealSubject realObj = new RealSubject(); // 创建真实主题对象
        ProxySubject proxyObj = new ProxySubject(realObj); // 创建代理主题对象
        proxyObj.request(); // 调用代理对象的方法
    }
}

2.2. 代理和装饰器的区别

特征

代理模式(Proxy)

装饰器模式(Decorator)

目的

控制对目标对象的访问

动态地增加目标对象的功能

核心关注点

控制访问权限、延迟加载、远程调用等

增强对象的功能,不改变原有对象的接口和实现

对象的关系

代理对象和目标对象通常实现相同的接口或继承自同一类

装饰器通过组合关系“装饰”目标对象

实现方式

代理对象替代目标对象,拦截并转发请求

装饰器在目标对象上增加功能,通过组合增强目标对象的行为

功能扩展

主要关注访问控制、代理行为(例如延迟加载、权限控制等)

主要关注扩展目标对象的功能(例如日志记录、性能监控等)

目标对象的修改

目标对象通常保持不变,通过代理访问

目标对象不修改,通过装饰器增加额外功能

多个实例组合

代理一般不会多个叠加使用(每个目标通常只有一个代理)

可以有多个装饰器叠加使用,逐步增加功能

2.2.1. 使用场景

  • 代理模式
    • 用于控制对目标对象的访问,常见场景如远程代理、虚拟代理、安全代理(权限控制)等。
    • 适用于需要控制访问权限、做性能优化(如延迟加载)或者进行远程调用的情况。
  • 装饰器模式
    • 用于动态地扩展对象的功能,常见场景如日志记录、权限检查、缓存、性能监控等。
    • 适用于需要在运行时为对象添加行为的情况,可以通过组合多个装饰器来实现功能扩展。

2.2.2. 总结:

  • 代理模式关注的是对目标对象的“控制”,通常用于控制访问、权限验证等。
  • 装饰器模式关注的是对目标对象的“功能扩展”,可以通过多个装饰器组合,增强对象的行为。

两者的核心差异在于目的:代理模式用于控制访问,而装饰器模式用于增强功能。在设计时,需要根据需求选择适合的模式。

3. 代理设计模式使用场景

3.1. 远程代理(Remote Proxy)

  • 场景:需要访问位于远程的对象(如跨网络的服务)。
  • 示例:RMI (Remote Method Invocation),访问远程服务器上的对象时,客户端不直接调用目标对象,而是通过代理对象与服务器通信。

典型应用

  • 分布式系统中的服务调用。
  • 数据库访问代理(如通过代理实现负载均衡)。

3.2. 虚拟代理(Virtual Proxy)

  • 场景:当需要一个对象的高成本初始化或对象比较耗资源时,使用代理对象代替真正的对象,只有在真正需要时才创建目标对象。
  • 示例:图片加载延迟加载机制。

典型应用

  • 图片、视频等大文件的延迟加载。
  • 大型复杂对象的延迟初始化。

3.3. 保护代理(Protection Proxy)

  • 场景:用于控制对目标对象的访问权限,通过代理限制某些功能的使用。
  • 示例:文件系统操作代理,仅允许某些用户访问特定文件。

典型应用

  • 权限控制。
  • 方法调用的访问限制。

3.4. 缓存代理(Cache Proxy)

  • 场景:当目标对象返回结果可被缓存时,代理对象缓存结果,从而减少目标对象的计算或数据获取开销。
  • 示例:Web 页面缓存代理,避免重复访问相同的页面。

典型应用

  • 数据缓存(如 API 数据、数据库查询结果)。
  • 文件系统的读写优化。

3.5. 防火墙代理(Firewall Proxy)

  • 场景:保护目标对象不受外部访问,通过代理限制某些 IP 或请求的访问。
  • 示例:网络访问防火墙代理。

典型应用

  • 网络流量过滤。
  • 系统安全防护。

3.6. 智能引用代理(Smart Proxy)

  • 场景:在目标对象调用前后增加额外的逻辑,例如统计目标对象的方法调用次数或记录日志。
  • 示例:动态代理机制中的日志记录。

典型应用

  • 方法调用统计。
  • 日志记录或监控系统。

3.7. 动态代理(Dynamic Proxy)

  • 场景:运行时动态创建代理对象,适合需要为大量接口提供统一代理逻辑的场景。
  • 示例:Java 的 java.lang.reflect.Proxy

典型应用

  • 面向切面编程(AOP)。
  • 框架中动态增强对象功能(如 Spring 的事务管理、权限校验)。

3.8. 图片延迟加载(虚拟代理)

  • 问题:在图形界面应用中,加载高清图片耗时较长。
  • 解决方案:使用代理对象先展示占位图片,待图片加载完成后再替换。

3.9. 用户权限验证(保护代理)

  • 问题:系统中不同用户角色访问权限不同。
  • 解决方案:代理对象在调用目标对象之前进行权限检查,确保只有有权限的用户才能调用方法。

3.10. Web 缓存代理

  • 问题:频繁访问同一 API 会造成资源浪费。
  • 解决方案:代理缓存 API 返回结果,减少重复请求。

3.11. 数据库连接池代理

  • 问题:数据库连接昂贵,频繁创建销毁连接性能低。
  • 解决方案:代理通过连接池复用现有连接,提高性能。

4. 代理设计模式示例(spring)

4.1. 通过Spring AOP实现代理设计模式

首先,在 pom.xml 中添加 Spring AOP 和 Spring Context 相关的依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.23</version> <!-- 使用适合的版本 -->
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.23</version> <!-- 使用适合的版本 -->
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.23</version> <!-- 使用适合的版本 -->
    </dependency>
</dependencies>

创建一个 UserService 类,模拟执行某些操作(如修改用户信息)。

package com.zhuangxiaoyan.hyxftest.proxy;

import org.springframework.stereotype.Service;

/**
 * UserService
 *
 * @author xjl
 * @version 2024/12/01 21:35
 **/
@Service
public interface UserService {
    void addUser(String name);
    void deleteUser(String name);
}
package com.zhuangxiaoyan.hyxftest.proxy;

import org.springframework.stereotype.Service;

/**
 * UserServiceImpl
 *
 * @author xjl
 * @version 2024/12/01 23:18
 **/
@Service
public class UserServiceImpl implements UserService {

    @Override
    public void addUser(String name) {
        System.out.println("Adding user: " + name);
    }

    @Override
    public void deleteUser(String name) {
        System.out.println("Deleting user: " + name);
    }
}

创建 AOP 切面类:AOP 切面类用于定义我们需要增强的功能(如日志记录、权限校验等)。Spring AOP 会在 UserService 方法执行前后插入增强逻辑。

package com.zhuangxiaoyan.hyxftest.proxy;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * LoggingAspect
 *
 * @author xjl
 * @version 2024/12/01 21:36
 **/
@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* com.zhuangxiaoyan.hyxftest.proxy.UserService.addUser(..))")
    public void addUserMethod() {
    }

    @Pointcut("execution(* com.zhuangxiaoyan.hyxftest.proxy.UserService.deleteUser(..))")
    public void deleteUserMethod() {
    }

    @Before("addUserMethod()")
    public void logBeforeAddUser() {
        System.out.println("Logging before adding a user.");
    }

    @After("addUserMethod()")
    public void logAfterAddUser() {
        System.out.println("Logging after adding a user.");
    }

    @Before("deleteUserMethod()")
    public void logBeforeDeleteUser() {
        System.out.println("Logging before deleting a user.");
    }

    @After("deleteUserMethod()")
    public void logAfterDeleteUser() {
        System.out.println("Logging after deleting a user.");
    }
}

代理模式测试类

package com.zhuangxiaoyan.hyxftest.test;

import com.zhuangxiaoyan.hyxftest.HyxfTestApplication;
import com.zhuangxiaoyan.hyxftest.facade.OrderFacade;
import com.zhuangxiaoyan.hyxftest.proxy.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest(classes = HyxfTestApplication.class)
public class HyxfTestApplicationTests {


    @Autowired
    private UserService userService;

    @Test
    public void run() {
        // 模拟处理订单
        String userId = "user123";
        String productId = "product456";
        double amount = 99.99;

        boolean result = orderFacade.processOrder(userId, productId, amount);
        if (result) {
            System.out.println("Order processed successfully!");
        } else {
            System.out.println("Order processing failed!");
        }
    }
    @Test
    public void proxyTest() {
        userService.addUser("Alice");
        userService.deleteUser("Bob");
    }
}

4.2. 通过JDK动态代理实现代理设计模式

如果你希望手动实现代理(例如JDK动态代理),可以使用java.lang.reflect.Proxy类。

定义接口和实现类

public interface UserService {
    void addUser(String name);
    void deleteUser(String name);
}
public class UserServiceImpl implements UserService {

    @Override
    public void addUser(String name) {
        System.out.println("Adding user: " + name);
    }

    @Override
    public void deleteUser(String name) {
        System.out.println("Deleting user: " + name);
    }
}

创建代理类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class UserServiceProxy implements InvocationHandler {

    private final UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    public UserService createProxy() {
        return (UserService) Proxy.newProxyInstance(
            userService.getClass().getClassLoader(),
            userService.getClass().getInterfaces(),
            this
        );
    }

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

使用代理

public class MainApplication {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy(userService);

        // 获取代理对象
        UserService userServiceProxy = proxy.createProxy();

        // 调用代理对象的方法
        userServiceProxy.addUser("Alice");
        userServiceProxy.deleteUser("Bob");
    }
}

输出结果

Logging before method: addUser
Adding user: Alice
Logging after method: addUser
Logging before method: deleteUser
Deleting user: Bob
Logging after method: deleteUser

4.3. 两种实现方式总结

  • 通过Spring AOP:这种方式是Spring Boot中实现代理设计模式的推荐方式。通过@Aspect和切面通知,你可以在不修改目标类的情况下增强其功能,适用于日志、权限校验等横切关注点。
  • 通过JDK动态代理:这种方式适用于手动实现代理模式,适合需要完全控制代理逻辑的情况。使用java.lang.reflect.Proxy类,你可以在运行时动态创建代理对象。

Spring AOP的优势在于,代码更加简洁,容易与Spring管理的对象一起使用,而JDK动态代理的优势在于提供了更高的灵活性,能够在运行时创建代理。

博文参考

《软件设计模式》

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐