GoF23:Proxy-代理

1、代理模式

1.1、场景

场景:客户不能或不想直接访问目标对象,需要一个代理来实现间接引用

说明 示例
客户端
(Client)
通过访问代理,间接访问真实主题的业务方法 找房子的人
抽象主题
(Subject)
定义一个接口或抽象类,声明业务方法 租房业务
真实主题
(RealSubject)
客户端实际访问的对象 房东
代理
(Proxy)
引用真实主题,对其访问、控制和扩展 房屋中介

1.2、模式定义

Proxy Pattern

给某个对象提供一个代理,以控制对该对象的访问

  • 优点

    1. 保护、扩展真实主题。
    2. 解耦:将客户端与真实主题分离,面向抽象编程。
  • 缺点

    1. 增加系统复杂度
    2. 降低请求处理速度

类型

根据代理类的创建时期,分为静态代理和动态代理。

静态代理(Static) 动态代理(Dynamic)
创建时期 程序运行前 程序运行时
被代理对象 真实主题(类) 抽象主题(接口)
创建方式 给每个真实主题,手动创建对应的代理类 反射机制,运行时自动创建代理类
实现接口 抽象主题接口 InvocationHandler 接口
获取方式 new 实例化(或工厂模式) InvocationHandler 的 getProxy()

2、静态代理

Static Proxy

程序运行前创建代理对象,代理一个真实主题类

2.1、类图

image-20220213152952569

  1. Subject(抽象主题)
    • 作为 Proxy 和 RealSubject 的抽象类型,定义业务方法
      • 由于实现同一个抽象类型,Proxy 可以代替 RealSubject 的使用。
  2. RealSubject(真实主题)
    • 实现抽象主题,是客户最终真实访问的目标对象。
      • 被 Proxy 代理,控制访问。
  3. Proxy(代理主题)
    • 实现抽象主题,需要手动为真实主题类创建对应的代理类。
      • 持有 RealSubject 的引用,控制其访问,也可负责其创建和销毁
      • request() 中调用真实主题的方法,并进行增强

2.2、实现

image-20230324134900732

抽象主题

定义客户需要的业务方法

public interface Subject {
    void request();
}

真实主题

实现 Subject 接口的业务方法

public class RealSubject implements Subject{
    @Override
    public void request() {
        System.out.println("真实对象:访问真实主题的业务方法...");
    }
}

代理类

实现 Subject 接口,引用真实对象

  • request():内部调用真实主题的业务方法,在调用前后可进行扩展。

  • preRequest():扩展功能——预处理(如验证)。

  • postRequest():扩展功能——后置处理(如日志)。

    public class Proxy implements Subject {
    
        private Subject realSubject;
    
        @Override
        public void request() {
            setRealSubject();	// 设置真实对象
    		
            preRequest();
            realSubject.request();	// 真实主题的业务方法
            postRequest();
        }
    
        private void setRealSubject() {
            if (realSubject == null) {
                this.realSubject = new RealSubject();
            }
        }
    
        private void preRequest() {
            System.out.println("代理对象:预处理...");
        }
    
        private void postRequest() {
            System.out.println("代理对象:后续处理...");
        }
    }
    

客户端

创建代理对象,调用业务方法

public class Client {
    public static void main(String[] args) {
        // 创建代理对象
        Proxy proxy = new Proxy();

        // 调用业务方法
        proxy.request();
    }
}

3、动态代理

分析:静态代理的缺点

  1. 类的数量过多:需要为每个真实主题定义一个代理类,导致类的数量翻倍。
  2. 使用不灵活:必须先有真实主题,才能创建代理类。

Dynamic Proxy

程序运行时动态创建代理类,代替一个抽象主题

  1. 底层技术:👉 反射

  2. 应用:Spring AOP

  3. 实现方式

    1. 若被代理对象有实现接口,则默认使用 JDK 代理,此时可强制使用 CGLIB 代理。

    2. 若被代理对象没有实现接口,则必须使用 CGLIB 代理。

      JDK 代理 CGLIB 代理
      使用前提 目标对象有实现接口 目标对象非 final 修饰
      技术 反射机制,生成实现类 asm 开源包,修改字节码创建子类
      代理实现 实现接口 继承类并重写方法

3.1、类图

相比静态代理,区别在于 Proxy 和 InvocationHandler。

image-20220215231307666

  1. Proxy(代理主题):

    1. 无需手动创建,由 InvocationHandler 利用反射机制创建。

    2. 不再持有 RealSubject 的引用。

    3. request() 的方法调用,由 InvocationHandler 的 invoke() 响应处理

      image-20220216162051808

  2. InvocationHandler(调用处理器):

    1. 持有 RealSubject 的引用。
    2. 利用反射机制,运行时、动态地为真实主题创建代理类
    3. 响应代理类的方法调用

3.2、实现

抽象主题

定义客户需要的业务方法

public interface Subject {
    void request();
}

真实主题

实现 Subject 接口的业务方法

public class RealSubject1 implements Subject {
    @Override
    public void request() {
        System.out.println("真实对象1:访问真实主题的业务方法...");
    }
}

InvocationHandler

创建一个类,实现 InnovationHandler 接口。

  1. 成员变量:定义要代理的抽象主题,通过构造器或 setter 注入。

  2. invoke():负责响应代理类的方法调用,方法参数如下

    • proxy:代理类
    • method:代理类要调用的方法
    • args:待调用方法的参数
  3. getProxy():提供获取代理对象的方法。

  4. 扩展功能

    public class DynamicProxyHandler implements InvocationHandler {
        // 抽象主题
        private Subject subject;
    
        public void setSubject(Subject subject) {
            this.subject = subject;
        }
    
        // 响应调用
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            preRequest();
    
            Object result = method.invoke(subject, args);
    
            postRequest();
            return result;
        }
    
        // 获取代理对象
        public Subject getProxy() {
            return (Subject) Proxy
                .newProxyInstance(object.getClass().getClassLoader(),
                                  object.getClass().getInterfaces(),
                                  this);
        }
    
        // 扩展功能
        private void preRequest() {
            System.out.println("代理:预处理");
        }
        private void postRequest() {
            System.out.println("代理:后续处理");
        }
    }
    

客户端

创建调用处理器,设置真实主题;

获取代理对象,调用业务方法。

public class Client {
    public static void main(String[] args) {
        // 创建调用处理器,设置真实主题
        DynamicProxyHandler handler = new DynamicProxyHandler();
        handler.setObject(new RealSubject1());

        // 获取代理对象
        Subject proxy = (Subject) handler.getProxy();
        
        // 调用业务方法
        proxy.request();
    }
}

4、代理工厂

代理模式结合工厂模式

可以将代理类的创建和使用分离。

示例:针对上文动态代理的案例,引入简单工厂。

  1. DynamicProxyHandler 类:去掉 getProxy() 方法。

    public class DynamicProxyHandler implements InvocationHandler {
        // 抽象主题
        private Subject subject;
    
        public void setSubject(Subject subject) {...}
    
        // 响应调用
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...}
    
        // 获取代理对象
        // public Subject getProxy() {
        //     return (Subject) Proxy
        //         .newProxyInstance(object.getClass().getClassLoader(),
        //                           object.getClass().getInterfaces(),
        //                           this);
        // }
    
        // 扩展功能
        private void preRequest() {...}
        private void postRequest() {...}
    }
    
  2. 创建简单工厂:即原来 getProxy() 的逻辑。

    public class DynamicProxyFactory {
        public Subject getProxy(Subject subject) {
            return (Subject) Proxy
                .newProxyInstance(subject.getClass().getClassLoader(),
                                  subject.getClass().getInterfaces(),
                                  new DynamicProxyHandler(subject));
        }
    }
    
  3. 客户端:不依赖于 InvocationHandler,直接通过工厂获取代理对象。

    public class Client {
        public static void main(String[] args) {
            // 从工厂中获取代理对象
            RealSubject subject = new RealSubject();
            Subject proxy = getProxy(subject);
            
            proxy.request();
        }
    }
    

5、常见应用场景

以下是代理模式的不同场景,本质都是为了保护和增强真实主题。

XX 代理 说明 示例
远程 控制访问远程对象 图片代理
虚拟 控制访问创建开销大的资源
保护 基于权限控制对资源的访问
防火墙 控制访问网络资源,避免侵害 防火墙系统
智能引用 主题被引用时进行额外动作 计算引用次数
同步 多线程下为主题提供安全访问
缓存代理 为开销大的运算结果提供暂时缓存
允许多个客户共享结果,以减少计算或延迟。
Web 服务器代理
Copy-On-Write 延迟对象的复制,直到客户真正需要 Java 1.5 的 CopyOnWriteArrayList
复杂隐藏代理
(aka 外观代理)
隐藏一个类的复杂集合的复杂度,并控制访问

相关链接

  1. 远程代理
  2. 虚拟代理
  3. 保护代理
posted @ 2021-12-01 21:38  Jaywee  阅读(175)  评论(0编辑  收藏  举报

👇