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  阅读(227)  评论(0)    收藏  举报

☝

👇