代理模式 (Proxy Pattern)

定义:

代理模式(Proxy Pattern)是一种结构型设计模式,它通过提供一个代理(或称代表)对象来控制对另一个对象的访问。这种模式创建了一个代理对象,用来代表实际对象的功能,从而可以在不直接访问实际对象的情况下进行操作。

代理模式的核心思想是将客户端与实际执行功能的对象分离,引入一个中介层(代理层),由代理对象来决定是否和如何访问实际对象。这种间接层可以用于多种目的,比如控制访问、延迟初始化、实现安全检查、添加额外的功能(例如日志记录、事务处理等)。

代理模式主要包含以下三个角色:

  1. 主题(Subject)接口:定义了代理和真实对象共同实现的接口,这样在任何使用真实对象的地方都可以使用代理对象。
  2. 真实主题(Real Subject)类:实际执行逻辑的类,代理模式的目的是控制对这个类的访问。
  3. 代理(Proxy)类:包含对真实主题的引用,它实现了与真实主题相同的接口,并可能在调用真实主题对象之前或之后执行额外的处理。

代理模式的常见变体包括:

  • 虚拟代理:在需要时才创建或初始化昂贵的对象。
  • 远程代理:为远程对象(如网络服务)提供本地代表。
  • 保护代理:控制对原始对象的访问,常用于权限管理。
  • 智能引用代理:在访问对象时执行额外的动作,例如计数引用次数、检查是否锁定等。

总的来说,代理模式增强了对原始对象的控制,同时保持了代码的灵活性和可扩展性。

解决的问题:
  • 控制对对象的访问:有时直接访问对象可能会带来各种问题,如安全性问题、对象正在执行耗时操作、对象需要远程访问等。代理模式允许在对象和访问者之间放置一个中介(代理),从而控制对这个对象的访问。
  • 延迟初始化(懒加载):如果对象的创建和初始化是资源密集型的,那么代理模式可以推迟这些开销大的操作直到真正需要对象时才进行。代理对象会在客户端首次请求时才实例化实际对象。
  • 实现安全控制:代理可以控制对实际对象的访问权限,进行身份验证或其他安全措施,确保只有具有适当权限的客户端才能访问对象。
  • 增加或修改原有对象的功能:代理可以在调用实际对象的方法前后添加额外的功能,比如添加日志记录、事务处理等,而不改变原有对象的代码。
  • 远程对象访问:在远程通信中,代理可以作为本地的代表来代理远程对象,隐藏实际对象位于远程位置的事实。
  • 引用计数和资源管理:代理可以跟踪对实际对象的引用次数,以便在没有引用时进行资源释放,从而实现有效的资源管理。

通过这些功能,代理模式提供了一种强大的方式来增强、控制或者以其他方式改变对对象的访问,同时保持对客户端的透明性。

使用场景:
  • 远程代理
    • 当对象位于不同的地址空间(如在远程服务器上)时,可以使用远程代理来在本地代表这个远程对象。这在分布式系统或网络服务(如远程方法调用RMI)中非常常见。
  • 虚拟代理
    • 当对象的创建开销很大时,可以使用虚拟代理进行懒加载,即在真正需要对象时才创建它。这适用于大型对象的加载和初始化,如图形重的图像或文件。
  • 保护代理
    • 用于控制对原始对象的访问权限。保护代理可以在执行操作之前进行权限检查,确保满足特定条件的客户端才能访问对象。这适用于安全敏感的操作。
  • 智能引用代理
    • 在对象被访问时执行额外的动作,例如引用计数、线程安全检查、锁定资源或者记录日志等。
  • 缓存代理
    • 用于为昂贵的操作结果提供临时存储(缓存),以便在后续请求中快速访问。这在数据密集型操作或网络请求中特别有用。
  • 界面代理
    • 当需要对用户界面元素进行控制或管理时,可以使用界面代理。比如,在UI组件完全加载之前提供占位符。
  • 日志记录代理
    • 用于记录对某个对象的操作或方法调用,这对于调试、监控或审计非常有用。
  • 事务代理
    • 在执行操作时自动开始事务,并在操作完成后提交或回滚事务,常见于数据库操作。

这些场景展示了代理模式如何在不同的环境和需求中提供灵活的解决方案,以增强或控制对对象的访问。

示例代码 1 - 简单代理:
java 复制代码
// 步骤1:定义接口
public interface Subject {
    void performAction();
}

// 步骤2:创建实现接口的真实类
public class RealSubject implements Subject {
    @Override
    public void performAction() {
        System.out.println("RealSubject performAction() executed.");
    }
}

// 步骤3:创建代理类,也实现同一个接口
public class ProxySubject implements Subject {
    private RealSubject realSubject;

    @Override
    public void performAction() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }

        preAction();
        realSubject.performAction();
        postAction();
    }

    private void preAction() {
        System.out.println("ProxySubject preAction() executed.");
    }

    private void postAction() {
        System.out.println("ProxySubject postAction() executed.");
    }
}

// 步骤4:客户端使用代理
public class Client {
    public static void main(String[] args) {
        Subject subject = new ProxySubject();
        subject.performAction(); // 客户端通过代理间接地访问真实对象的方法
    }
}

在这个示例中:

  • Subject 接口定义了一个操作,这是客户端调用的目标。
  • RealSubject 类是实现了 Subject 接口的真实对象类,执行实际的业务逻辑。
  • ProxySubject 类同样实现了 Subject 接口,但它持有一个 RealSubject 对象的引用。它控制对 RealSubject 的访问,并可以在调用真实对象之前或之后执行一些操作(如权限检查、日志记录等)。
  • 在客户端 Client 中,客户端通过 ProxySubject 访问 RealSubject,而不是直接访问,这样就可以在不更改 RealSubject 代码的情况下,添加额外的操作。
示例代码 2 - 动态代理:

动态代理是一种在运行时创建代理对象的方式,Java中的动态代理通过实现InvocationHandler接口来实现。以下是一个简单的动态代理的示例实现:

java 复制代码
// 首先,定义一个接口,代理对象和真实对象都将实现这个接口
public interface Subject {
    void performAction();
}

// 接下来,创建一个真实对象类,它实现了上述接口
public class RealSubject implements Subject {
    @Override
    public void performAction() {
        System.out.println("Performing action in RealSubject");
    }
}

// 然后,定义一个`InvocationHandler`,这个处理器负责定义代理对象的行为:、
public class DynamicProxyHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoking " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("After invoking " + method.getName());
        return result;
    }
}

// 最后,创建代理实例并使用它
public class Client {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                RealSubject.class.getClassLoader(),
                new Class<?>[]{Subject.class},
                new DynamicProxyHandler(realSubject)
        );

        proxySubject.performAction(); // 调用动态代理对象的方法
    }
}

在这个示例中,DynamicProxyHandler是一个InvocationHandler,它定义了如何响应代理对象上的方法调用。在调用任何方法之前和之后,都会执行DynamicProxyHandler中的代码。Proxy.newProxyInstance方法用于在运行时动态创建代理对象,它需要类加载器、一组接口以及一个InvocationHandler实例。

主要符合的设计原则:
  1. 开闭原则(Open-Closed Principle) :
    • 代理模式允许在不修改原始对象代码的情况下扩展其功能。通过引入代理类,可以在不改变客户端代码的前提下增加额外的行为(如访问控制、日志记录等),从而保持原始类的开闭原则。
  2. 单一职责原则(Single Responsibility Principle) :
    • 代理类专注于一个任务:控制对原始对象的访问。这样的设计意味着原始对象可以专注于其核心功能,而将控制逻辑委托给代理对象,从而遵循单一职责原则。
  3. 接口隔离原则(Interface Segregation Principle) :
    • 代理类和实际对象通常实现相同的接口。这样的设计确保了代理对象可以在不影响客户端代码的情况下代替实际对象。客户端依赖于抽象接口,而不是具体实现,符合接口隔离原则。
在JDK中的应用:
  • Java反射中的动态代理(java.lang.reflect.Proxy :
    • Proxy 类和 InvocationHandler 接口支持动态创建代理实例。这种机制允许在运行时创建代理对象,用于拦截对目标对象方法的调用。动态代理广泛应用于拦截、日志记录、事务处理等场景。
  • 远程方法调用(RMI) :
    • Java RMI(Remote Method Invocation)技术使用代理模式来使客户端能够透明地调用远程对象上的方法。RMI框架在客户端创建远程对象的本地代理(Stub),所有对远程对象的调用都通过这个本地代理进行。
  • java.util.Collections 中的不可修改集合 :
    • Collections 类提供了如 unmodifiableListunmodifiableSetunmodifiableMap 等方法,这些方法返回给定集合的不可修改视图。这些不可修改的集合实际上是使用代理模式实现的,代理对象会拦截所有修改集合的操作并抛出异常。
  • java.lang.reflect.Proxy中的 Proxy :
    • Java的反射包中的 Proxy 类提供了创建动态代理的能力,允许开发者在运行时创建接口的代理实例,这对于实现如AOP(面向切面编程)等功能非常有用。
在Spring中的应用:
  • 面向切面编程(AOP) :
    • Spring AOP 使用代理模式来实现切面的功能。它创建目标对象的代理,并在代理中织入横切关注点(例如日志、事务管理、安全等)。这样做可以将这些关注点模块化,而不需要修改实际业务逻辑代码。
    • 根据目标对象是接口还是类,Spring AOP 可以使用 JDK 动态代理或 CGLIB 代理。JDK 动态代理用于接口,而 CGLIB 代理用于没有接口的类。
  • 事务管理 :
    • 在声明式事务管理中,Spring通过使用代理包装方法调用,自动地开始和结束事务。这使得开发者可以通过声明性方式管理事务,而不必在每个方法中显式地处理事务逻辑。
  • Spring Security :
    • 在Spring Security中,代理模式用于在方法调用前后进行安全检查。这允许开发者在不修改业务逻辑代码的情况下,添加或删除安全性检查。
  • 远程调用(Remote Invocation) :
    • Spring还提供了对RMI(远程方法调用)的支持,允许通过代理访问远程服务。这种代理透明地处理网络通信和服务定位。

相关推荐
纪莫25 分钟前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide1 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户3721574261351 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源1 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
晨米酱2 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
Java中文社群2 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心2 小时前
从零开始学Flink:数据源
java·大数据·后端·flink
间彧2 小时前
Spring Boot项目中如何自定义线程池
java
间彧2 小时前
Java线程池详解与实战指南
java
用户298698530143 小时前
Java 使用 Spire.PDF 将PDF文档转换为Word格式
java·后端