第06天 静态代理和动态代理

每天学习一个知识点

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉
🍎个人主页:Leo的博客
💞当前专栏:每天一个知识点
✨特色专栏: MySQL学习
🥭本文内容:第06天 静态代理和动态代理
🖥️个人小站 :个人博客,欢迎大家访问
📚个人知识库: 知识库,欢迎大家访问

在了解静态代理和动态代理之前,我们需要先了解一下什么是代理模式

1. 代理模式

代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

  1. 代理模式: 为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能
  2. 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象

举个例子:当我们工作之后需要出去租房子,房东张贴广告带我看房子,最后签合同,但是房东只想坐着签合同并不想到处跑着看房子,于是就找了一个中介专门来宣传广告并且带租户看房子,而房东只负责签合同收钱!中介在这里就可以看作是代理你的代理对象代理的行为(方法)是带租户看房子。

image-20230814112036352

代理模式有静态代理和动态代理两种实现方式,我们 先来看一下静态代理模式的实现。

2.静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。

上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

静态代理实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

下面通过代码来进一步了解静态代理的应用

  1. 定义一个卖房的接口

    public interface UserService {String sell(String message);
    }
    
  2. 实现卖房接口

    public class UserServiceImpl implements UserService{public String sell(String message) {System.out.println("我是房东-->我要卖房,找我签合同");return message;}
    }
    
  3. 创建代理类实现卖房接口

    public class UserServiceProxy implements UserService{private UserService userService;public UserServiceProxy(UserService userService) {this.userService = userService;}@Overridepublic String sell(String message) {System.out.println("我是中介,我可以带你去看房");userService.sell(message);//调用方法之后,我们同样可以添加自己的操作System.out.println("after method send()");return null;}
    }
    
  4. 测试

    @org.junit.Testpublic void name() {UserServiceImpl userService = new UserServiceImpl();UserServiceProxy userServiceProxy = new UserServiceProxy(userService);userServiceProxy.sell("卖房");}
    }
    

    控制台打印 :

    image-20230814114502067

静态代理存在的问题

1. 静态类文件数量过多,不利于项目管理UserServiceImpl  UserServiceProxyOrderServiceImpl OrderServiceProxy
2. 额外功能维护性差代理类中 额外功能修改复杂(麻烦)

3. 动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。

从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。

动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理CGLIB 动态代理等等。

概念:通过代理类为原始类(目标类)增加额外功能,代理类由Spring动态生成。
好处:利于原始类(目标类)的维护

代理运行对象在程序运行的过程中动态的在内存进行构建,可以灵活的进行业务功能的切换。

3.1 JDK动态代理

JDK动态代理是基于 Java 的反射机制实现的。使用 JDK中接口和类实现代理对象的动态创建。JDK的动态代理要求目标对象必须实现接口,而代理对象不必实现业务接口,这是 java 设计上的要求。从 jdk1.3 以来,java 语言通过 java.lang.reflect 包提供三个类和接口支持代理模式,它们分别Proxy, Method和 InvocationHandler。

  • 目标对象必须实现业务接口

  • JDK代理代理对象不需要实现业务接口

  • 动态代理的对象在程序运行中不存在

  • 动态代理灵活的进行业务功能的切换

1. Proxy类

​ 通过JDK的 java.lang.reflect.Proxy 类实现动态代理,会使用其静态方法 newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。

public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces,InvocationHandler handler)loader:目标类的类加载器,通过目标对象的反射可获取
interfaces:目标类实现的接口数组,通过目标对象的反射可获取
handler:调用处理器。

2. Method类

invoke()方法的第二个参数为 Method 类对象,该类有一个方法也叫 invoke(),可以调用目标方法。这两个 invoke()方法,虽然同名,但无关。

public Object invoke ( Object obj, Object... args)obj:表示目标对象
args:表示目标方法参数,就是其上一层 invoke 方法的第三个参数
该方法的作用是:调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定。在代码中,一般的写法为
method.invoke(target, args);
其中,method 为上一层 invoke 方法的第二个参数。这样,即可调用了目标类的目标方法。

3. IocationHandler接口

InvocationHandler 接口叫做调用处理器,负责完成调用目标方法,并增强功能。通过代理对象执行目标接口中的方法 , 会把方法的调用分派给调用处理器(InvocationHandler)的实现类,执行实现类中的 invoke()方法,我们需要把功能代理写在 invoke()方法中 。此接口中只有一个方法。

img

invoke 方法中可以截取对目标方法的调用。在这里进行功能增强。Java 的动态代理是建立在反射机制之上的。实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个方法 invoke(),具体加强的代码逻辑就是定义在该方法中的。通过代理对象执行接口中的方法时,会自动调用 invoke()方法。

4. 实现步骤

  1. 代理对象不需要实现接口。

  2. 代理对象的生成是利用JDK中的proxy类,动态的在内存中构建代理对象。

  3. 代码实现接口

  4. ProxyFactory.java 代理实例生成工厂

    package com.leo.demo02.service.impl;import com.leo.demo02.service.UserService;import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;/*** @author : Leo* @version 1.0* @date 2023/8/14 13:40* @description :*/
    public class ProxyFactory {/**任何的代理对象,都要清楚目标对象,在此得设置一个目标对象*/private UserService userService;//传入目标对象public ProxyFactory(UserService userService){this.userService = userService;}/** 给目标对象生成代理实例 */public Object getProxyInstance(){return Proxy.newProxyInstance(//指定当前目标对象,使用类加载器获得userService.getClass().getClassLoader(),//获得目标对象实现的所有接口userService.getClass().getInterfaces(),//处理代理实例上的方法并返回调用结果new InvocationHandler() {@Overridepublic Object invoke(//代理对象的实例Object proxy,//代理的目标对象的实现方法Method method,//代理的目标对象实现方法的参数Object[] args) throws Throwable {System.out.println("我是中介,我在张贴广告......");System.out.println("我是中介,我在带领租客看房子......");//目标对象执行自己的方法Object returnValue = method.invoke(userService, args);System.out.println("我是中介,我在带领租客办理结束流程");return returnValue;}});}
    }
    
    1. 测试类

       @org.junit.Testpublic void test03() {// 创建代理工厂对象ProxyFactory factory = new ProxyFactory(new UserServiceImpl());UserService service = (UserService) factory.getProxyInstance();service.sell("卖房");}
      

      控制台打印 :

      image-20230814135707682

注意:JDK动态代理中,代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用JDK动态代理。

3.2 CGlib动态代理

Code Generation Library ,又称为子类通过动态的的在内存中构建子类对象,重写父类方法进行代理功能的增强。

想要功能扩展,但目标对象没有实现接口,怎样功能扩展?

解决方案:子类的方式

Class subclass extends UserDao{}

以子类的方式实现(cglib代理),在内存中构建一个子类对象从而实现对目标对象功能的扩展。

1. CGlib动态代理的特点

  1. JDK动态代理有一个限制,就是使用动态代理的目标对象必须实现一个或多个接口。如果想代理没有实现类的接口,就可以使用CGLIB进行代理。
  2. CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception。
  3. CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

2. CGLIB的实现步骤

  1. 需要spring-core-5.2.5.jar依赖即可。

  2. 引入功能包后,就可以在内存中动态构建子类

  3. 被代理的类不能为final, 否则报错。

  4. 目标对象的方法如果为final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。

  5. 编写 ProxyFactory.java

    	package com.leo.demo02.service.impl;import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author : Leo* @version 1.0* @date 2023/8/14 14:00* @description :*/
    public class ProxyFactory2 implements MethodInterceptor {/** 目标对象 */private Object target;/** 传入目标对象 */public ProxyFactory2(Object target){this.target = target;}/** Cglib采用底层的字节码技术,在子类中采用方法拦截的技术,拦截父类指定方法的调用,并顺势植入代理功能的代码 */@Overridepublic Object intercept(Object obj, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {//代理对象的功能System.out.println("我是中介,我在张贴广告......");System.out.println("我是中介,我在带领租客看房子......");//调用目标对象的方法Object returnValue=method.invoke(target, arg2);//代理对象的功能System.out.println("我是中介,我在带领租客办理结束流程");return returnValue;}/** 生成代理对象 */public Object getProxyInstance(){//1.使用工具类Enhancer en=new Enhancer();//2.设置父类en.setSuperclass(target.getClass());//3.设置回调函数en.setCallback(this);//4.创建子类(代理)对象return en.create();}}
    
    1. 测试类

       @org.junit.Testpublic void test04() {UserService service = (UserService)new ProxyFactory2(new UserServiceImpl()).getProxyInstance();service.sell("卖房");
      }
      

      控制台打印:

      image-20230814141003722

3.3 JDK 动态代理和 CGLIB 动态代理对比

JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

4. 静态代理和动态代理的对比

灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!

JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/36746.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ARM Cache 系列文章 9 番外篇 -- ARMv9 系列 Core 介绍】

文章目录 ARMv9 系列CoreARM Cortex-A510 介绍ARM Cortex-A715ARM Cortex-A720 ARMv9 系列Core 2021年5月Arm公布了其最新3款CPU和3款GPU核心设计&#xff0c;三款新CPU分别是旗舰核心Cortex-X2、高性能核心Cortex-A710、高能效核心Cortex-A510 CPU&#xff0c;三款新GPU核心则…

【Unity每日一记】向量操作摄像机的移动(向量加减)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

TCP消息传输可靠性保证

TCP链接与断开 -- 三次握手&四次挥手 三次握手 TCP 提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好两端之间的准备工作。 所谓三次握手是指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在socket编程中&#xff0c;这一…

面试热题(合并K个升序链表)

给定一个链表数组&#xff0c;每个链表都已经按升序排列。 请将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 输入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]] 输出&#xff1a;[1,1,2,3,4,4,5,6] 解释&#xff1a;链表数组如下&#xff1a; [1->4->5,1…

【软件工程】面向对象方法-RUP

RUP&#xff08;Rational Unified Process&#xff0c;统一软件开发过程&#xff09;。 RUP特点 以用况驱动的&#xff0c;以体系结构为中心的&#xff0c;迭代增量式开发 用况驱动 用况是能够向用户提供有价值结果的系统中的一种功能用况获取的是功能需求 在系统的生存周期中…

uni-app日期选择器

写个简单的日期选择器&#xff0c;还没搞样式&#xff0c;所以有点丑 大概长这样吧 首先是这个picker选择器&#xff0c;mode选择日期&#xff0c;end是写一个范围前日期&#xff0c;:end就是这个日期是动态变化的&#xff0c;还有change函数 <template><view>&l…

【pinia】Pinia入门和基本使用:

文章目录 一、 什么是pinia二、 创建空Vue项目并安装Pinia1. 创建空Vue项目2. 安装Pinia并注册 三、 实现counter四、 实现getters五、 异步action六、 storeToRefs保持响应式解构七、基本使用&#xff1a;【1】main.js【2】store》index.js【3】member.ts 一、 什么是pinia P…

安达发APS|生产计划排产软件助力加工制造业智能化转型

随着全球经济一体化的不断深入&#xff0c;市场竞争日益激烈&#xff0c;加工制造企业面临着巨大的生存压力。在这种情况下&#xff0c;企业对于生产计划的精细化管理需求日益迫切。为了适应这一市场需求&#xff0c;安达发推出了专门针对加工企业的APS生产计划排产软件&#x…

新一代构建工具 maven-mvnd

新一代构建工具 maven-mvnd mvnd的前世今生下载安装 mvndIDEA集成 mvnd的前世今生 maven 作为一代经典的构建工具&#xff0c;流行了很多年&#xff0c;知道现在依然是大部分Java项目的构建工具的首选&#xff1b;但随着项目复杂度提高&#xff0c;代码量及依赖库的增多使得ma…

简单易懂的 Postman Runner 参数自增教程

目录 什么是 Postman Runner&#xff1f; Postman Runner 如何实现参数自增&#xff1f; 步骤一&#xff1a;设置全局参数 步骤二&#xff1a;将全局参数带入请求参数 步骤三&#xff1a;实现参数自增 资料获取方法 什么是 Postman Runner&#xff1f; Postman Runner 是…

Python爬虫(1)一次性搞定Selenium(新版)8种find_element元素定位方式

selenium中有8种不错的元素定位方式&#xff0c;每个方式和应用场景都不一样&#xff0c;需要根据自己的使用情况来进行修改 8种find_element元素定位方式 1.id定位2.CSS定位3.XPATH定位4.name定位5.class_name定位6.Link_Text定位7.PARTIAL_LINK_TEXT定位8.TAG_NAME定位总结 …

【第一阶段】kotlin中反引号中的函数名特点

在kotlin中可以直接中文定义函数&#xff0c;使用反引号进行调用 eg: fun main() {2023年8月9日定义的函数(5) }private fun 2023年8月9日定义的函数(num:Int){println("反引号的用法$num") }执行结果 在Java中is,in可以定义方法&#xff0c;但是在kotlin中is,in是…

资料分析(三)—— 基期、现期、人口、增长量

基期 基期值 现期值 - 增长量 增长量/增长率 现期值/1&#xff08;间隔)增长率 化除为乘 &#xff1a;当增长率&#xff5c;r| < 5% 时&#xff0c;&#xff0c; 注&#xff1a;当选项首位相同&#xff0c;第二位也相同时&#xff0c;只能用直除 基期和差 (结合选…

【openwrt学习笔记】dnsmasq源码阅读

目录 一、DHCP(Dynamic Host Configuration Protocol)1.1 前置知识1.2 参考链接1.3 IP地址分配代码分析rfc2131.cdhcp-common.cdhcp.c 1.4 几个小问题1.4.1 连续IP模式&#xff08;sequential_ip&#xff09;1.4.2 重新连接使用IP地址1.4.3 续约租期1.4.4 不同的MAC地址分配到相…

VS+Qt+C++旅游景区地图导航源码实例

程序示例精选 VSQtC旅游景区地图导航 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<VSQtC旅游景区地图导航>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。…

SpringBoot 将项目打包成 jar 包

SpringBoot 将项目打包成 jar 包 一、项目打包成 jar 包 首先在 pom.xml 文件中导入 Springboot 的 maven 依赖 <!-- 将应用打包成一个可以执行的 jar 包 --> <build><plugins><plugin><groupId>org.springframework.boot</groupId><…

【数据结构】单链表OJ题(二)

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;数据结构 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、链表分割 &#x1f4a1;方法一&#xff1a; 二、链表的回文 &#x…

利用python实现激光雷达LAS数据滤波的7种方式,使用laspy读写

激光雷达&#xff08;LiDAR&#xff09;数据在实际应用中可能受到噪声和不完美的测量影响&#xff0c;因此数据去噪和滤波方法变得至关重要&#xff0c;以提高数据质量和准确性。以下是一些常用的激光雷达数据去噪与滤波方法。 原始数据如下&#xff1a; 1. 移动平均滤波&…

kubernetes中PV和PVC

目录 一、PV、PVC简介 二、PV、PVC关系 三、创建静态PV 1.配置nfs存储 2.定义PV 3.定义PVC 4.测试访问 四、 搭建 StorageClass nfs-client-provisioner &#xff0c;实现 NFS 的动态 PV 创建 1. 配置nfs服务 2.创建 Service Account 3.使用 Deployment 来创建 NFS P…

Figma中文社区来啦,云端协作设计你准备好了吗?

Figma是改变产品设计协作方式的重要工具,但由于没有中文社区,对国内设计师的约束较大。而拥有全中文UI 界面、功能齐全的即时设计资源广场,恰好弥补了Figma的这一短板,它也将取代Figma成为设计师新宠。 1、UI组件集 Figma中文社区替代即时设计资源广场,拥有海量丰富的UI设计组…