代理模式(结构型模式)-程序员宅基地

技术标签: 委托模式  cglib动态代理  结构型模式  代理模式  设计模式  

目录

1、概述

2、结构

2.1、角色分类

2.2、类图

3、静态代理

3.1、案例类图

3.2、案例代码

4、JDK 动态代理

4.1、案例代码

4.2、底层原理

4.3、执行流程说明

5、CGLib 动态代理

5.1、案例代码

6、三种代理的对比

6.1、JDK代理和CGLib代理

6.2、动态代理和静态代理

7、优缺点

7.1、优点

7.2、缺点

8、使用场景

8.1、远程(Remote)代理

8.2、防火墙(Firewall)代理

8.3、保护(Project or Access)代理

1、概述

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

代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。

Java 中的代理按照生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则在 Java 运行时动态生成,程序结束时内存会自动释放。动态代理又分为 JDK 动态代理和 CGLib 动态代理。

2、结构

2.1、角色分类

代理(Proxy)模式分为三种角色,分别如下:

(1)抽象主题(Subject)角色:通过接口或抽象类声明真实主题和代理对象实现的业务方法。

(2)具体主题(Real Subject)角色:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象,也叫做被委托角色、被代理角色。

(3)代理(Proxy)角色:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

2.2、类图

3、静态代理

静态代理是由程序创建或者特定工具自动生成源代码,在程序运行前,代理类的.class文件已经存在。

案例:

房东出租房屋,将出租和收回房子的事情都交给中介来完成,中介出租房屋后可以赚取到中介费。

3.1、案例类图

3.2、案例代码
/**
  * @ClassName IHouseOwner
  * @Description 出租房屋的接口-抽象主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public interface IHouseOwner {
 ​
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     void rentHouse();
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     void takeBackHouse();
 }
 ​
 ​
 /**
  * @ClassName HouseOwner
  * @Description 房东-具体主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class HouseOwner implements IHouseOwner {
 ​
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     @Override
     public void rentHouse() {
         System.out.println("房屋闲置,房东要出租房屋");
     }
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     @Override
     public void takeBackHouse() {
         System.out.println("房屋到期,房东要收回房屋");
     }
 }
 ​
 ​
 /**
  * @ClassName HouseOwnerProxy
  * @Description 代理角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class HouseOwnerProxy implements IHouseOwner {
 ​
     private IHouseOwner houseOwner;
 ​
     public HouseOwnerProxy(IHouseOwner houseOwner) {
         this.houseOwner = houseOwner;
     }
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     @Override
     public void rentHouse() {
         this.houseOwner.rentHouse();
         System.out.println("帮房东出租了房子,挣取佣金2000元");
     }
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     @Override
     public void takeBackHouse() {
         this.houseOwner.takeBackHouse();
         System.out.println("帮房东收回了房子");
     }
 }
 ​
 ​
 /**
  * @ClassName Client
  * @Description 业务场景
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class Client {
     public static void main(String[] args) {
         //创建抽象主题角色
         IHouseOwner houseOwner = new HouseOwner();
         //创建代理类,通过代理类来完成具体主题角色需要做的事情
         HouseOwnerProxy proxy = new HouseOwnerProxy(houseOwner);
         proxy.rentHouse();
         proxy.takeBackHouse();
     }
 }

执行结果:

房屋闲置,房东要出租房屋

帮房东出租了房子,挣取佣金2000元

房屋到期,房东要收回房屋

帮房东收回了房子

从上面代码中可以看出业务场景直接访问的是 IHouseOwner 类对象,也就是说 IHouseOwner 作为访问对象和目标对象的中介,同时也对 rentHouse() 和 takeBackHouse() 方法做出了增强。

4、JDK 动态代理

JDK 动态代理要求目标对象实现一个接口。

Java 中提供了一个动态代理类 Proxy,Proxy 不是静态代理中所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance())来获取代理对象。

4.1、案例代码

在JDK动态代理中,不再需要去手动创建一个代理类来完成目标对象的执行方法。

/**
  * @ClassName IHouseOwner
  * @Description 抽象主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public interface IHouseOwner {
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     void rentHouse();
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     void takeBackHouse();
 }
 ​
 /**
  * @ClassName HouseOwner
  * @Description 房东-具体主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class HouseOwner implements IHouseOwner {
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     @Override
     public void rentHouse() {
         System.out.println("房屋闲置,房东要出租房屋");
     }
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     @Override
     public void takeBackHouse() {
         System.out.println("房屋到期,房东要收回房屋");
     }
 }
 ​
 /**
  * @ClassName ProxyFactory
  * @Description  获取代理对象的工厂类
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class ProxyFactory {
     //1、声明目标对象
     private HouseOwner houseOwner = new HouseOwner();
 ​
     /**
      * @Description: 获取代理对象的方法
      * @Author: chengjunyu
      */
     public IHouseOwner getHouseOwner() {
         //2、返回代理对象
         /*
          * ClassLoader loader: 类加载器,用于加载代理类(程序运行中动态的在内存中生成的类),通过目标对象获取
          * Class<?>[] interfaces:代理类实现的接口的字节码对象
          * InvocationHandler h:代理对象的调用处理程序
          */
         IHouseOwner proxyObject = (IHouseOwner) Proxy.newProxyInstance(
                 houseOwner.getClass().getClassLoader(),
                 houseOwner.getClass().getInterfaces(),
                 //匿名内部类,重写 invoke 方法
                 new InvocationHandler() {
                     /*
                      * Object proxy:代理对象,和 proxyObject 是同一个对象,在 invoke 方法中基本上不使用
                      * Method method:对接口中的方法进行封装的 method 对象
                      * Objects[] args:调用方法的实际参数
                      */
                     @Override
                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                         Object object =  method.invoke(houseOwner, args);
                         if ("rentHouse".equals(method.getName()))  {
                             System.out.println("帮房东出租了房子,挣取佣金2000元");
                         }
                         if ("takeBackHouse".equals(method.getName())) {
                             System.out.println("帮房东收回了房子");
                         }
                         return object;
                     }
                 }
         );
         return proxyObject;
     }
 }
 ​
 /**
  * @ClassName Client
  * @Description 业务场景
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class Client {
     public static void main(String[] args) {
         //获取代理对象
         //1、创建代理对象工厂
         ProxyFactory proxyFactory = new ProxyFactory();
         //2、使用factory对象的方法获取代理对象
         IHouseOwner houseOwner = proxyFactory.getHouseOwner();
         //调用租房方法
         houseOwner.rentHouse();
         //调用收回房屋方法
         houseOwner.takeBackHouse();
     }
 }

执行结果:

房屋闲置,房东要出租房屋

帮房东出租了房子,挣取佣金2000元

房屋到期,房东要收回房屋

帮房东收回了房子

4.2、底层原理

在学习JDK动态代理的底层原理之前,可以先考虑一个问题:ProxyFactory是代理类吗?

ProxyFactory 不是代理模式中所说的代理类,代理类是程序在运行过程中动态的在内存中生成的类。

我么可以通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构,操作步骤如下:

1、运行指令 java -jar arhtas-boot.jar 启动 Arthas;

2、运行指令 jad com.sun.proxy.$Proxy0 获取该类源码;

3、优化源码,优化后的源码如下:

 
package com.sun.proxy;
 ​
 import com.design.pattern.proxy.jdkProxy.IHouseOwner;
 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 IHouseOwner {
     private static Method m1;
     private static Method m2;
 ​
     //构造方法提供了有参构造,参数为 InvocationHandler 对象,赋值给了父类 Proxy
     public $Proxy0(InvocationHandler invocationHandler) {
         super(invocationHandler);
     }
 ​
     static {
         //获取IHouseOwner字节码对象后获取方法
         m1 = Class.forName("com.design.pattern.proxy.jdkProxy.IHouseOwner").getMethod("rentHouse", new Class[0]);
         m2 = Class.forName("com.design.pattern.proxy.jdkProxy.IHouseOwner").getMethod("takeBackHouse", new Class[0]);
     }
 ​
     //调用了 InvocationHandler 的子实现类对象的 invoke 方法
     public final void rentHouse() {
         //此处h即为InvocationHandler对象,this表示本类,m1为rentHouse方法
         this.h.invoke(this, m1, null);
     }
 ​
     public final void takeBackHouse() {
         this.h.invoke(this, m2, null);
     }
 }
 ​
 //父类Proxy
 public class Proxy {
     protected InvocationHandler invocationHandler;
 }

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

1、代理类($Proxy0)实现了 IHouseOwner ,这也就印证了我们之前说的真实类和代理类实现同样的接口;

2、代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

4.3、执行流程说明

根据JDK动态代理示例代码和底层原理代码,我们可以分析出JDK动态代理的执行流程如下:

1、在业务场景中通过代理对象调用 rentHouse() 和 takeBackHouse();

2、根据多态的特性,执行的是代理类($Proxy0)中的 rentHouse() 和 takeBackHouse();

3、代理类($Proxy0)中的 rentHouse() 方法和 takeBackHouse() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke() 方法;

4、invoke 方法通过反射执行了真实对象所属类(HouseOwner)中的rentHouse() 和 takeBackHouse()。

5、CGLib 动态代理

有时候目标对象只是一个单独的对象,并没有实现接口,这个时候就可以使用CGLIB代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

CGLIB动态代理基于继承来实现代理,所以无法对 final 类、private 和 static 方法实现代理。

5.1、案例代码

同样是上面的案例,这里我们使用CGLIB代理实现。

如果没有定义IHouseOwner接口,只定义了HouseOwner(房屋所有者类),很显然JDK动态代理就无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

CGLIB是第三方提供的包,所以在Spring框架下需要引入jar包的maven坐标。

<!-- https://mvnrepository.com/artifact/cglib/cglib -->

<dependency>

<groupId>cglib</groupId>

<artifactId>cglib</artifactId>

<version>3.3.0</version>

</dependency>

代码如下:

 
/**
  * @ClassName HouseOwner
  * @Description 房东-具体主题角色
  * @Author chengjunyu
  * @Date 2022/12/8
  * @Version V1.0
  */
 public class HouseOwner {
 ​
     /**
      * @Description: 出租房屋
      * @Author: chengjunyu
      */
     public void rentHouse() {
         System.out.println("房屋闲置,房东要出租房屋");
     }
 ​
     /**
      * @Description: 收回房屋
      * @Author: chengjunyu
      */
     public void takeBackHouse() {
         System.out.println("房屋到期,房东要收回房屋");
     }
 }
 ​
 ​
 /**
  * @ClassName ProxyFactory
  * @Description 代理工厂类
  * @Author chengjunyu
  * @Date 2022/12/10
  * @Version V1.0
  */
 public class ProxyFactory implements MethodInterceptor {
 ​
     private HouseOwner houseOwner = new HouseOwner();
 ​
     public HouseOwner getProxyObject() {
         //1、创建Enhancer对象,类似于JDK动态代理中的Proxy类
         Enhancer enhancer = new Enhancer();
         //2、设置父类的字节码对象,指定父类
         enhancer.setSuperclass(HouseOwner.class);
         //3、设置回调函数
         enhancer.setCallback(this);
         //4、创建代理对象,即目标类对象的子对象
         HouseOwner proxyObject = (HouseOwner) enhancer.create();
         return proxyObject;
     }
 ​
     /**
      * @Description: 方法所属类的回调函数
      * @Author: chengjunyu
      */
     @Override
     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
         System.out.println("调用了回调函数");
         Object object = method.invoke(this.houseOwner, objects);
         return object;
     }
 }
 ​
 ​
 /**
  * @ClassName Client
  * @Description 业务场景类
  * @Author chengjunyu
  * @Date 2022/12/10
  * @Version V1.0
  */
 public class Client {
     public static void main(String[] args) {
         //创建代理工厂对象
         ProxyFactory factory = new ProxyFactory();
         //获取代理对象
         HouseOwner proxyObject = factory.getProxyObject();
         //调用代理对象中的方法
         proxyObject.rentHouse();
         proxyObject.takeBackHouse();
     }
 }

6、三种代理的对比

6.1、JDK代理和CGLib代理

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行处理,因为CGLib原理是动态生成被代理的子类。

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

6.2、动态代理和静态代理

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

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

7、优缺点

7.1、优点

(1)代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

(2)代理对象可以扩展目标对象的功能;

(3)代理模式能够将客户端与目标对象分离,在一定程度上降低了系统的耦合度。

7.2、缺点

(1)增加了系统的复杂度。

8、使用场景

8.1、远程(Remote)代理

本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常,为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个端口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

8.2、防火墙(Firewall)代理

当你将浏览器配置称为使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

8.3、保护(Project or Access)代理

控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的访问权限。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/threelifeadv/article/details/137738467

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法