【深入理解设计模式】代理设计模式

news/2024/7/6 6:03:21 标签: 设计模式, 代理设计模式, 代理模式

在这里插入图片描述

代理设计模式

代理设计模式是一种结构型设计模式,它允许你提供一个替代物或占位符来控制对其他对象的访问。在代理模式中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式,因为该模式涉及类和对象的组合。

概述

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

Java中的代理按照代理类生成时机不同又分为静态代理动态代理静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

结构

代理(Proxy)模式分为三种角色:

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式的一些常见用途包括:

  • 远程代理(Remote Proxy):在一个不同的地址空间中代表一个对象。这种方式可以隐藏对象存在于不同地址空间的事实。

  • 虚拟代理(Virtual Proxy):根据需要创建昂贵的对象。比如,当一个图像文件非常大时,可以使用虚拟代理来延迟加载(Lazy Loading)图像,只有当真正需要显示图像时才加载。

  • 保护代理(Protection Proxy):控制对对象的访问权限。这种方式可以根据调用者的身份来限制对某些方法的访问,比如检查调用者是否有足够的权限。

  • 缓存代理(Caching Proxy):为昂贵操作的结果提供缓存,以便在后续请求中直接使用缓存结果,提高性能。

静态代理

静态代理是代理模式的一种形式,它在编译时就已经确定了代理对象和真实对象的关系,即在程序运行前就已经确定了代理对象和真实对象的关系,因此称为静态代理。

在静态代理中,代理类和真实类通常实现同一个接口或继承同一个父类,并且代理类持有真实类的引用。代理类负责将调用转发给真实类,同时可以在调用前后执行一些额外的逻辑,如权限验证、日志记录、性能监控等。

// 抽象主题
interface Subject {
    void request();
}

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

// 代理
class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        // 在调用真实对象前后可以添加一些额外的逻辑
        System.out.println("Proxy: Before requesting.");
        realSubject.request();
        System.out.println("Proxy: After requesting.");
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Proxy proxy = new Proxy(realSubject);
        
        // 通过代理对象调用真实对象的方法
        proxy.request();
    }
}

静态代理的优点在于:

  • 简单易理解:代理对象可以在不修改真实对象的情况下添加额外的功能,易于理解和实现。
  • 对真实对象的访问进行控制:代理对象可以控制对真实对象的访问权限。

但静态代理也存在一些缺点:

  • 静态代理每次新增功能都需要编写新的代理类,导致类的数量增加。
  • 在代理类中硬编码了对真实对象的引用,一旦真实对象发生变化,代理类也需要相应修改。

静态代理是代理模式的一种简单实现方式,适用于代理对象相对固定且功能不复杂的场景。

动态代理

JDK动态代理

动态代理是一种在运行时生成代理对象的机制,与静态代理不同,它不需要在编译时就确定代理对象和真实对象的关系,而是在程序运行时动态创建代理对象。Java 中的动态代理主要依赖于 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。
以下是一个基于JDK的动态代理的示例:

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

// 定义接口
interface Subject {
    void request();
}

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

// 实现 InvocationHandler 接口
class DynamicProxy implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用真实对象的方法前后添加额外的逻辑
        System.out.println("DynamicProxy: Before requesting.");
        Object result = method.invoke(target, args);
        System.out.println("DynamicProxy: After requesting.");
        return result;
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        // 创建真实对象
        RealSubject realSubject = new RealSubject();
        
        // 创建动态代理对象
        Subject proxy = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                new DynamicProxy(realSubject));
        
        // 通过代理对象调用真实对象的方法
        proxy.request();
    }
}

在上面的示例中,DynamicProxy 类实现了 InvocationHandler 接口,在 invoke 方法中实现了代理逻辑。通过 Proxy.newProxyInstance 方法创建了代理对象,并传入了 DynamicProxy 对象作为 InvocationHandler 的实现。最后,通过代理对象调用真实对象的方法。
示例二:

/**
 * @author OldGj 2024/02/22
 * @version v1.0
 * @apiNote 抽象主题 - 买票接口
 */
public interface SellTicket {
    void sell();
}
/**
 * @author OldGj 2024/02/22
 * @version v1.0
 * @apiNote 具体主题(被代理对象) - 火车站类
 */
public class TrainStation implements SellTicket {

    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}
/**
 * @author OldGj 2024/02/22
 * @version v1.0
 * @apiNote 代理对象工厂,用来创建代理对象
 */
public class ProxyFactory {

    // 目标代理对象
    private Object target = new TrainStation();

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

    public SellTicket getProxy() {
        /*
            newProxyInstance方法的三个参数:
            1.ClassLoader loader,  类加载器,用于加载代理类,使用真实对象的类加载器即可
            2.Class<?>[] interfaces,真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
            3.InvocationHandler h  代理对象的调用处理程序
         */
        //使用Proxy方法获取代理对象
        return (SellTicket) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                        InvocationHandler中invoke方法参数说明:
                            proxy : 代理对象
                            method : 对应于在代理对象上调用的接口方法的 Method 实例
                            args : 代理对象调用接口方法时传递的实际参数
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("DynamicProxy: Before requesting.");
                        Object result = method.invoke(target, args);
                        System.out.println("DynamicProxy: After requesting.");
                        return result;
                    }
                }
        );
    }
}
/**
 * @author OldGj 2024/02/22
 * @version v1.0
 * @apiNote 客户端 - 测试类
 */
public class Client {
    public static void main(String[] args) {
        // 创建目标代理对象
        TrainStation station = new TrainStation();
        // 将目标代理对象传入代理工厂,获取代理工厂对象
        ProxyFactory proxyFactory = new ProxyFactory(station);
        // 根据代理工厂对象获取代理对象
        SellTicket proxy = proxyFactory.getProxy();
        // 使用代理对象对原方法进行扩展和增强
        proxy.sell();
    }
}

使用了动态代理,我们思考下面问题:

  • ProxyFactory是代理类吗?

    ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:

    package com.sun.proxy;
    
    import com.itheima.proxy.dynamic.jdk.SellTickets;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                return;
            }
            catch (NoSuchMethodException noSuchMethodException) {
                throw new NoSuchMethodError(noSuchMethodException.getMessage());
            }
            catch (ClassNotFoundException classNotFoundException) {
                throw new NoClassDefFoundError(classNotFoundException.getMessage());
            }
        }
    
        public final boolean equals(Object object) {
            try {
                return (Boolean)this.h.invoke(this, m1, new Object[]{object});
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final String toString() {
            try {
                return (String)this.h.invoke(this, m2, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final int hashCode() {
            try {
                return (Integer)this.h.invoke(this, m0, null);
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final void sell() {
            try {
                this.h.invoke(this, m3, null);
                return;
            }
            catch (Error | RuntimeException throwable) {
                throw throwable;
            }
            catch (Throwable throwable) {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    }
    

    从上面的类中,我们可以看到以下几个信息:

    • 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
    • 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。
  • 动态代理的执行流程是什么样?

    下面是摘取的重点代码:

    //程序运行过程中动态生成的代理类
    public final class $Proxy0 extends Proxy implements SellTickets {
        private static Method m3;
    
        public $Proxy0(InvocationHandler invocationHandler) {
            super(invocationHandler);
        }
    
        static {
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
        }
    
        public final void sell() {
            this.h.invoke(this, m3, null);
        }
    }
    
    //Java提供的动态代理相关类
    public class Proxy implements java.io.Serializable {
    	protected InvocationHandler h;
    	 
    	protected Proxy(InvocationHandler h) {
            this.h = h;
        }
    }
    
    //代理工厂类
    public class ProxyFactory {
    
       	 // 目标代理对象
      	private Object target = new TrainStation();
    
     	 public ProxyFactory(Object target) {
        	  this.target = target;
    	  }
    
        public SellTickets getProxyObject() {
            SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(
                	station.getClass().getClassLoader(),
                    station.getClass().getInterfaces(),
                    new InvocationHandler() {
                        
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                            System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
                            Object result = method.invoke(station, args);
                            return result;
                        }
                    });
            return sellTickets;
        }
    }
    
    
    //测试访问类
    public class Client {
        public static void main(String[] args) {
        	TrainStation station = new TrainStation();
            //获取代理对象
            ProxyFactory factory = new ProxyFactory(station );
            SellTickets proxyObject = factory.getProxyObject();
            proxyObject.sell();
        }
    }
    

执行流程如下:

  1. 在测试类中通过代理对象调用sell()方法
  2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
  3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
  4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
CGLIB动态代理

CGLIB(Code Generation Library)是一个强大的开源字节码生成类库,它可以在运行时动态生成指定类的子类,并覆盖其中的方法,从而实现动态代理。相比于基于接口的动态代理,CGLIB 动态代理不要求目标对象必须实现接口,因此更加灵活。

CGLIB 动态代理的核心思想是利用字节码生成技术,通过生成目标类的子类,并在子类中重写需要代理的方法,从而达到对目标类的代理控制。

CGLIB是第三方提供的包,所以需要引入jar包的坐标:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

以下是一个使用 CGLIB 动态代理的简单示例:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

// 目标类
class RealSubject {
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

// 实现 MethodInterceptor 接口
class DynamicProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在调用真实对象的方法前后添加额外的逻辑
        System.out.println("DynamicProxy: Before requesting.");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("DynamicProxy: After requesting.");
        return result;
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        // 创建 Enhancer 对象,用于生成代理类
        Enhancer enhancer = new Enhancer();
        
        // 设置目标类的父类,即需要代理的类
        enhancer.setSuperclass(RealSubject.class);
        
        // 设置回调对象,即实现 MethodInterceptor 接口的对象
        enhancer.setCallback(new DynamicProxy());
        
        // 创建代理对象
        RealSubject proxy = (RealSubject) enhancer.create();
        
        // 通过代理对象调用目标类的方法
        proxy.request();
    }
}

在上面的示例中,我们使用了 CGLIB 提供的 Enhancer 类来生成代理类。通过 setSuperclass 方法设置目标类的父类,即需要代理的类;通过 setCallback 方法设置回调对象,即实现了 MethodInterceptor接口的对象,用于在代理类中增加额外逻辑;最后通过 create 方法创建代理对象。

示例二:

/**
 * @author OldGj 2024/02/22
 * @version v1.0
 * @apiNote 具体主题(被代理对象) - 火车站类
 */
public class TrainStation {

    public void sell() {
        System.out.println("火车站卖票");
    }
}
/**
 * @author OldGj 2024/02/22
 * @version v1.0
 * @apiNote 生成代理对象
 */
public class ProxyFactory implements MethodInterceptor {

	private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }
    
    public TrainStation getProxy(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return (TrainStation) enhancer.create();
    }

    /**
     * intercept方法参数说明:
     * @param o 代理对象
     * @param method 真实对象中的方法的Method实例
     * @param objects 实际参数
     * @param methodProxy 代理对象中的方法的method实例
     * @return 真实对象中的方法的Method返回值
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("DynamicProxy: Before requesting.");	
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("DynamicProxy: After requesting.");
        return object;
    }
}
/**
 * @author OldGj 2024/02/22
 * @version v1.0
 * @apiNote 客户端  - 测试类
 */
public class Client {
   public static void main(String[] args) {
        TrainStation station = new TrainStation();
        //创建代理工厂对象
        ProxyFactory factory = new ProxyFactory(station);
        //获取代理对象
        TrainStation proxyObject = (TrainStation) factory.getProxy();

        proxyObject.sell();
    }
}

CGLIB 动态代理相比于基于接口的动态代理更加灵活,不要求目标类必须实现接口,因此更适合对没有实现接口的类进行代理。但是需要注意的是,CGLIB 动态代理生成的代理类是目标类的子类,如果目标类是 final 类,则无法生成子类,因此无法使用 CGLIB 动态代理。

三种代理的对比

  • jdk代理和CGLIB代理

    使用CGLib实现动态代理,CGLIB 动态代理的核心思想是利用字节码生成技术,通过生成目标类的子类,并在子类中重写需要代理的方法,从而达到对目标类的代理控制。,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果(目标类)被代理类有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

  • 动态代理和静态代理

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

代理模式的一些常见用途包括:

远程代理(Remote Proxy):在一个不同的地址空间中代表一个对象。这种方式可以隐藏对象存在于不同地址空间的事实。

虚拟代理(Virtual Proxy):根据需要创建昂贵的对象。比如,当一个图像文件非常大时,可以使用虚拟代理来延迟加载(Lazy Loading)图像,只有当真正需要显示图像时才加载。

保护代理(Protection Proxy):控制对对象的访问权限。这种方式可以根据调用者的身份来限制对某些方法的访问,比如检查调用者是否有足够的权限。

缓存代理(Caching Proxy):为昂贵操作的结果提供缓存,以便在后续请求中直接使用缓存结果,提高性能。

优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

缺点:

  • 增加了系统的复杂度;

http://www.niftyadmin.cn/n/5391569.html

相关文章

信号处理与 signal.h 库

C 语言中的 signal.h 头文件提供了一种处理程序执行期间报告的不同信号的机制。它定义了一些变量类型、宏和函数&#xff0c;让程序能够更灵活地响应来自操作系统或其他进程的信号。 sig_atomic_t 类型 sig_atomic_t 类型是一种在信号处理程序中使用的整数类型。它保证在信号…

计算机网络-无线通信网

1.各种移动通信标准 1G&#xff1a;第一代模拟蜂窝&#xff1a;频分双工FDD。2G&#xff1a;第二代数字蜂窝 I.GDM&#xff08;全球移动通信&#xff09;采用TDMA。II.CDMA&#xff08;码分多址通信&#xff09;。2.5G&#xff1a;第2.5代通用分组无线业务GPRS。2.75G&#xf…

ArcgisForJS如何将ArcGIS Server发布的点要素渲染为热力图?

文章目录 0.引言1.ArcGIS创建点要素2.ArcGIS Server发布点要素3.ArcgisForJS将ArcGIS创建的点要素渲染为热力图 0.引言 ArcGIS For JS 是一个强大的地理信息系统&#xff08;GIS&#xff09;工具&#xff0c;它允许开发者使用 JavaScript 语言来创建各种 GIS 应用。ArcGIS Ser…

Spark大数据分析与实战笔记(第三章 Spark RDD 弹性分布式数据集-05)

文章目录 每日一句正能量第3章 Spark RDD弹性分布式数据集章节概要3.7 Spark的任务调度3.7.1 DAG的概念3.7.2 RDD在Spark中的运行流程 总结 每日一句正能量 成功的速度一定要超过父母老去的速度&#xff0c;努力吧。做事不必与俗同&#xff0c;亦不与俗异&#xff1b;做事不必令…

评估购买ChatGPT Plus的价值:是否值得投资?

title: “评估购买ChatGPT Plus的价值&#xff1a;是否值得投资&#xff1f;” date: 2024-02-24 随着人工智能技术的飞速发展&#xff0c;ChatGPT Plus作为OpenAI的优先服务&#xff0c;在市场上引起了不小的波澜。但是&#xff0c;许多潜在用户在决定是否升级到Plus版本之前…

LeetCode 0235.二叉搜索树的最近公共祖先:用搜索树性质(不遍历全部节点)

【LetMeFly】235.二叉搜索树的最近公共祖先&#xff1a;用搜索树性质&#xff08;不遍历全部节点&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/ 给定一个二叉搜索树, 找到该树中两个指定节点的最近公…

Dear ImGui的UE5.3集成实践

Dear ImGui一直较为火热&#xff0c;这是一个调试使用并且可以响应快速迭代的Gui库&#xff0c;甚至可以做到在任何代码块中调用API即显示。如果你想更多的了解一下可访问其官方网站&#xff1a;https://www.dearimgui.org/ 那么本文就来在UE5中尝试踩坑使用它。 UE4.26版本 …

【k8s资源调度-HPA(自动扩缩容)】

1、HPA可以做什么&#xff1f; 通过观察pod的cpu、内存使用率或自定义metrics指标进行自动的扩容或缩容pod的数量。通常用于Deployment&#xff0c;不适用于无法扩/缩容的对象&#xff0c;如DaemonSet。控制管理器每隔30s(可以通过-horizontal-pod-autoscaler–sync-period修改…