【动态代理详解】

动态代理

知道什么是动态代理以及动态代理能干什么就可以

代理的概述

什么是动态代理

使用jdk的反射机制,创建对象的能力, 创建的是代理类的对象。

  • 动态:在程序执行时,调用jdk提供的方法才能创建代理类的对象。
  • jdk动态代理,必须有接口,目标类必须实现接口, 没有接口时,需要使用cglib动态代理

动态代理能做什么

可以在不改变原来目标方法功能的前提下, 可以在代理中增强自己的功能代码。

​ 例如实际开发中,你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用。你发现这个功能,现在还缺点,不能完全满足我项目的需要。 我需要在gn.print()执行后,需要自己在增加代码。用代理实现 gn.print()调用时, 增加自己代码, 而不用去改原来的 GoNong文件。

// GoNong.class 
GoNong gn = new GoNong();
gn.print();

​ 注意,不知道源代码,不能使用重写,而且你要用写好的功能想要去扩展功能,就只能先把写好的方法执行一边,然后再代理中扩展,重写的话,原来写好的方法也没了

什么是代理

生活中的代理

​ 代理就是中介、代购、商家等。

​ 例如留学中介就是一个代理,我想上美国的一所大学,但是我没办法去这个大学实地考察,也没办法要这个大学的联系方式,并且这个大学拒绝个人去访问它。但是这个大学在国内把招生业务委派给了一个留学中介。这样我就可以去和留学中介交谈,留学中介可以与美国大学联系。留学中介会从我这里收取额外的中介费。

​ 在以上的案例中,学校是目标,留学中介是代理,我是客户。它们具有以下特点:

  • 中介和代理他们要做的事情是一致的: 招生。

  • 中介是学校代理, 学校是目标。

  • 我—中介(学校介绍,办入学手续)----美国学校。

  • 中介是代理,不能白干活,需要收取费用。

  • 代理不让你访问到目标。

为什么要找中介?
  • 中介是专业的,方便

  • 我现在不能自己去找学校。 我没有能力访问学校。 或者美国学校不接收个人来访。

  • 买东西都是商家卖, 商家是某个商品的代理, 你个人买东西, 肯定不会让你接触到厂家的。

开发中的代理模式(代理)

​ 代理模式是指,当一个对象(目标)无法直接使用时,可以在该客户端和目标类之间创建一个中介,这个中介就是代理。

​ 在实际开发中有这样情况,有一个A类,有一个C类,但是C类不允许A类直接访问,这样我们可以创建一个A类和C类之间的B类,C类允许B类访问,这样我们可以在A类中访问B类,然后B类在访问C类,这样就相当于在A类中间接访问到了C类。其中A类是客户类,B类是代理类,C类是目标类。另外,我可以在B类中添加一些内容,意味着功能增强。

使用代理模式的作用
  • 功能增强: 在你原有的功能上,增加了额外的功能。 新增加的功能,叫做功能增强。 (例如,留学中介要收取额外的费用)
  • 控制访问: 代理类不让你访问目标,例如商家不让用户访问厂家。(就跟房屋中介肯定不会给房东电话给你,否则它们怎么赚中介费)

实现代理的方式

实现代理的方式有:静态代理,动态代理

静态代理

什么是静态代理

​ 静态代理:代理类是手工创建的,代理的目标类是固定的。

静态代理的实现步骤

模拟一个用户购买u盘的行为,其中用户是客户端类,商家是代理类,厂家是目标类,商家和厂家都是卖U盘的,应该把卖U盘这个动作抽象为一个接口。

  1. 创建一个接口,定义卖u盘的方法, 表示你的厂家和商家做的事情。

    // 表示功能的,厂家,商家都要完成的功能
    public interface UsbSell {//定义方法 参数 amount:表示一次购买的数量,暂时不用//返回值表示一个u盘的价格。float sell(int amount);//可以多个其它的方法//void print();
    }
    
  2. 创建厂家类,实现1步骤的接口

    //目标类:金士顿厂家, 不接受用户的单独购买。
    public class UsbKingFactory implements UsbSell {@Overridepublic float sell(int amount) {System.out.println("目标类中的方法调用 , UsbKingFactory 中的sell ");//一个128G的u盘是 85元。//后期根据amount ,可以实现不同的价格,例如10000个,单击是80, 50000个75return 85.0f;}
    }                          
    
  3. 创建商家,就是代理类,也需要实现1步骤中的接口。

    // taobao是一个商家,代理金士顿u盘的销售。
    public class TaoBao implements UsbSell {// 声明 商家代理的厂家具体是谁// private修饰为了控制访问,不让客户知道厂家是谁private UsbKingFactory factory = new UsbKingFactory();@Override// 实现销售u盘功能public float sell(int amount) {// 向厂家发送订单,告诉厂家,我买了u盘,厂家发货float price = factory.sell(amount); //厂家的价格。// 商家 需要加价, 也就是代理要增加价格。price = price + 25; //增强功能,代理类在完成目标类方法调用后,增强了功能。// 在目标类的方法调用后,你做的其它功能,都是增强的意思。System.out.println("淘宝商家,给你返一个优惠券,或者红包");// 增加的价格return price;}
    }public class WeiShang implements UsbSell {//代理的是 金士顿,定义目标厂家类private UsbKingFactory factory = new UsbKingFactory();@Overridepublic float sell(int amount) {//调用目标方法float price = factory.sell(amount);//只增加1元price = price + 1;return price;}
    }
    
  4. 创建客户端类,调用商家的方法买一个u盘。

    public class Customer {public static void main(String[] args) {// 通过淘宝卖// 下载了淘宝的app/*TaoBao taoBao = new TaoBao();// 通过代理类,实现购买u盘,增加了优惠券,红包等等float price = taoBao.sell(1);System.out.println("淘宝的价格:" + price);*/// 取得微商的联系方式WeiShang weiShang = new WeiShang();// 通过微商代理,实现购买u盘,增加了优惠券,红包等等float price = weiShang.sell(1);System.out.println("通过微商购买的价格:"+ price);}
    }
    

分析问题:

  • 以上实例,若只有一个目标类,如金士顿厂家,商家只需要和这个厂家联系就行。但是如果又有一个闪迪厂家,那么得重写一套代理类,因为一个代理类中只能指定一个厂家,如以上的淘宝类,制定了厂家是金士顿,就不能在指定闪迪厂家,只能重新新建一个代理类,在这个代理类中指定厂家是闪迪,微商也是一样,要来一个专门代理闪迪的微商。如果有100个厂家,那么就要写200个代理类。
  • 如果UsbSell接口多了个退货功能,那么所有的目标类和代理类都要修改。
静态代理的优缺点
  • 优点
    • 实现简单
    • 容易理解
  • 缺点(当你的项目中,目标类和代理类很多时候,有以下的缺点:)
    • 当目标类增加了,代理类可能也需要成倍的增加。 代理类数量过多。
    • 当你的接口中功能增加了,或者修改了,会影响众多的实现类,厂家类,代理都需要修改。影响比较多。

简而言之,静态代理容易理解,代码好写,但是各个类之间耦合度太高,一旦扩展就有问题

动态代理(重点)

什么是动态代理

​ 动态代理就是在程序执行过程(动态)中,使用jdk的反射机制,创建代理类对象, 并动态的指定要代理目标类。换句话说: 动态代理是一种创建java对象的能力,让你不用自己写代理类的java程序,然后new对象。而是直接用jdk创建代理类对象。这样我不关心代理类是谁,我只知道它能增强功能,并且通过它我可以找到目标类。(类似暗下交易)

动态代理的优点

  • 优点
    • 动态代理中目标类即使很多,但是代理类数量可以很少,当你修改了接口中的方法时,不会影响代理类(压根就不用写代理类了,这个代理类是jdk创建的,只是起到一个中介的功能)。符合OCP原则
    • 不用创建代理类文件,代理的目标类是灵活的,可以随意给不同目标创建代理

动态代理的两种实现方式

  • jdk动态代理(理解): 使用java反射包中的类和接口实现动态代理的功能。
    • 反射包 java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy.
  • cglib动态代理(了解): cglib是第三方的工具库, 创建代理对象。
    • cglib的原理是继承, cglib通过继承目标类,创建它的子类,在子类中重写父类中同名的方法, 实现功能的修改。
    • 因为cglib是继承,重写方法,所以要求目标类不能是final的, 方法也不能是final的。
    • cglib的要求目标类比较宽松, 只要能继承就可以了。cglib在很多的框架中使用,比如 mybatis ,spring框架中都有使用。

注意:jdk动态代理必须有接口,对于无接口类,必须使用cglib来为它创建动态代理

jdk动态代理的实现

需要用到反射包 java.lang.reflect,里面有三个类 : InvocationHandler , Method, Proxy.

InvocationHandler 接口(调用处理器)
  • 该接口中只有一个方法:invoke()方法
  • invoke()方法最后是由代理类调用的,所以代理类完成的功能写在该方法里
    • 代理类完成的功能:调用目标方法、功能增强
public Object invoke(Object proxy, Method method, Object[] args)

invoke方法参数:

  • proxy:代理对象,由jdk创建
  • method:目标方法,因为代理类的功能是调用目标方法,所以这里要给出
  • args:目标方法的参数
如何理解InvocationHandler接口
  • InvocationHandler接口表示你的代理想要干什么,即代理完成的功能写在该接口实现类的invoke方法中

  • 使用方法:

    1. 创建类实现接口InvocationHandler
    2. 重写invoke()方法,把原来静态代理中代理类要完成的功能,写在这。
Method类
  • method表示目标类中的方法
  • method是要发给InvocationHandler实现类的invoke方法的参数,表示代理所调用的目标方法
Proxy类
  • 通过该类可以生成代理对象,代理对象的创建是由jdk提供的,我们只是负责调Proxy类中的方法来拿到这个代理对象
  • 调用Proxy类中的静态方法newProxyInstance()可以创建一个代理对象,具体创建步骤是由jdk完成的
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

newProxyInstance参数说明:

  • loader表示目标对象的类加载器,固定写法:目标对象.getClass().getClassLoader();
  • interfaces表示目标对象所实现的接口,接口多继承,因此是个数组。固定写法:目标对象.getClass().getInterface();
  • h表示代理类所要完成的功能

返回值就是代理类对象,并且在该对象创建时已经说明了,该代理类对象所代理的目标对象是谁,该目标对象完成了那些功能,该代理类所要完成的功能

动态代理的实现步骤

  1. 创建接口,定义目标类要完成的功能

    /*** 目标类要完成的功能*/
    public interface UsbSell {float sell(int amount);
    }
    
  2. 创建目标类实现接口

    //目标类:金士顿厂家, 不接受用户的单独购买。
    public class UsbKingFactory implements UsbSell {@Overridepublic float sell(int amount) {System.out.println("目标类中的方法调用 , UsbKingFactory 中的sell ");//一个128G的u盘是 85元。//后期根据amount ,可以实现不同的价格,例如10000个,单击是80, 50000个75return 85.0f;}
    }
    
  3. 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能
    1.调用目标方法
    2.增强功能

    //必须实现InvocationHandler接口,完成代理类要做的功能(1.调用目标方法,2.功能增强)
    public class MySellHandler implements InvocationHandler {private Object target = null;//动态代理:目标对象是活动的,不是固定的,需要传入进来。//传入是谁,就给谁创建代理。public MySellHandler(Object target) {//给目标对象赋值this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res  = null;//向厂家发送订单,告诉厂家,我买了u盘,厂家发货//float price = factory.sell(amount); //厂家的价格。res =  method.invoke(target,args); //执行目标方法//商家 需要加价, 也就是代理要增加价格。//price = price + 25; //增强功能,代理类在完成目标类方法调用后,增强了功能。if( res != null ){Float price = (Float)res;price = price + 25;res = price;}//在目标类的方法调用后,你做的其它功能,都是增强的意思。System.out.println("淘宝商家,给你返一个优惠券,或者红包");//记录数据库//增加的价格return res;}
    }
    
  4. 使用Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型。

    public class Customer {public static void main(String[] args) {//创建代理对象,使用Proxy//1. 创建目标对象// UsbKingFacotry  factory = new UsbKingFactory();UsbSell factory = new UsbKingFactory();//2.创建InvocationHandler对象InvocationHandler handler = new MySellHandler(factory);//3.创建代理对象UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),factory.getClass().getInterfaces(),handler);//com.sun.proxy.$Proxy0 : 这是jdk动态代理创建的对象类型。System.out.println("proxy:"+proxy.getClass().getName());//4.通过代理执行方法float price = proxy.sell(1);System.out.println("通过动态代理对象,调用方法:"+price);}
    }
    

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

动态代理开发中实例

// 目标完成的功能
public interface HelloService {//打印报告, 报表int print(String name);
}// 目标类
public class GoNeng implements HelloService {@Overridepublic int print(String name) {System.out.println("其它人写好的个功能方法");return 2;}
}// 代理完成的功能
public class MyInvocationHandler implements InvocationHandler {private Object target = null;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//我在项目中记录数据库,//调用目标方法, 执行print()得到 2Object res = method.invoke(target,args); //2// 代理进行“功能增强”//需要乘以 2的结果  if( res != null){Integer num = (Integer)res;res = num * 2;}return res;}
}// 测试类
public class MyApp {public static void main(String[] args) {// GoNeng gn = new GoNeng();//int num = gn.print("销售");// System.out.println("num="+num);// 创建目标类GoNeng goNeng = new GoNeng();// 代理完成的功能InvocationHandler handler = new MyInvocationHandler(goNeng);// 这里表示目标类必须实现一个接口,否则不能用jdk实现动态代理,只能用cglib实现动态代理System.out.println("goNeng.getClass().getInterfaces()="+goNeng.getClass().getInterfaces()[0].getName());HelloService proxy = (HelloService) Proxy.newProxyInstance( goNeng.getClass().getClassLoader(),goNeng.getClass().getInterfaces(),handler);// 代理proxy的print方法实际上是接口中的,因为再创建代理是,把目标类的构造器和实现的接口都给了代理,并且代理完成的功能也给了代理// 代理执行print方法就去handler的invoke方法,把print方法给了method,参数“市场”给了argsint num = proxy.print("市场");System.out.println("我们期望的 num ==" + num);// 总之proxy,handler,目标类之间是相关联的。}
}

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

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

相关文章

GnuCash macos 设置中文的方法

一、环境&#xff1a; macos 13.6.1 gnucash 5.4-2 二、查看系统默认语言&#xff1a; defaults read -g AppleLocale 三、设置gnucash应用语言&#xff1a; defaults write $(mdls -name kMDItemCFBundleIdentifier -raw /Applications/GnuCash.app) AppleLanguages "…

【Oracle】数据库登陆错误:ORA-28000:the account is locked解决方法

问题描述 在连接Oracle数据库的时候出现了ORA-28000:the account is locked报错&#xff0c;登录账号被锁定&#xff0c;出现这种情况就需要将被锁定用户解锁。 解决方法 解锁方法就是通过用system账号登录数据库&#xff0c;然后修改被锁定账户状态&#xff0c;具体如下图所示…

LINQ-123 题外篇之IEnumerable和IQueryable

这两天又学习了2篇&#xff0c;记录下&#xff1a; IEnumerable and IQueryable in C# - Dot Net Tutorials Differences Between IEnumerable and IQueryable in C# - Dot Net Tutorials 实现接口IEnumerable和IQueryable是使用LINQ查询的必要条件。 关于 IEnumerable<…

51爱心流水灯32灯炫酷代码

源代码摘自远眺883的文章&#xff0c;大佬是30个灯的&#xff0c;感兴趣的铁汁们可以去看看哦~&#xff08;已取得原作者的许可&#xff09;&#xff1a;基于STC89C51单片机设计的心形流水灯软件代码部分_单片机流水灯代码_远眺883的博客-CSDN博客 由于博主是个小菜鸡&#xff…

SSM+VUE的增删改查

目录 后端代码 mapper.xml controller 前端代码 api》action.js 后端代码 mapper.xml <!--模糊查询--><select id"selectLike" parameterType"com.zking.spboot.model.Book" resultMap"BaseResultMap">select * from t_book&l…

selenium+python

selenium 八大查找元素 from selenium import webdriver from selenium.webdriver.common.by import By# 创建一个 WebDriver 实例 driver webdriver.Chrome()# 打开网页 driver.get("https://www.baidu.com/")# 使用 find_element 方法查找元素 element driver.…

springboot 整合 Spring Security 上篇

1.创建springBoot 项目工程(spring6.0的底层、JDK17) 1.添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>配置完成启动访问controller会出现登录…

prometheus部署及与grafana结合应用

一、prometheus 介绍 prometheus server 是 Prometheus组件中的核心部分&#xff0c;负责实现对监控数据的获取&#xff0c;存储以及查询。它会定期从静态配置的监控目标或者基于服务发现自动配置的自标中进行拉取数据&#xff0c;当新拉取到的数据大于配置的内存缓存区时&…

【数据结构和算法】无限集中的最小数字

其他系列文章导航 Java基础合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 三、代码 四、总结 前言 这是力扣的2336题&#xff0c;难度为中等&#xff0c;解题方案有很多种&#xff0c;本文讲解我认为…

绘制彩色正多边形-第11届蓝桥杯选拔赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第10讲。 绘制彩色正多边形…

论文解读--Visual Lane Tracking and Prediction for Autonomous Vehicles

自动驾驶汽车视觉车道线跟踪和预测 摘要 我们提出了一种用于自动驾驶汽车跟踪水平道路车道标记位置的可视化方法。我们的方法是基于预测滤波的。预测步骤估计在每个新的图像帧中期望的车道标记位置。它也是基于汽车的运动学模型和嵌入式测程传感器产生的信息。使用适当准备的测…

弱网模拟工具

一、背景 一个人晚上在家通过 Wi-Fi 上网&#xff0c;在线电影播放基本流畅&#xff0c;可一旦在晚间用网高峰期打视频电话就画面糊&#xff0c;这时不仅可能带宽受限了&#xff0c;还可能有较高的丢包率。与有线网络通信相比&#xff0c;无线网络通信受环境影响会更大&#x…

【Java Web学习笔记】 1 - HTML入门

项目代码 https://github.com/yinhai1114/JavaWeb_LearningCode/tree/main/html 零、网页的组成 HTML是网页内容的载体。内容就是网页制作者放在页面上想要让用户浏览的信息&#xff0c;可以包含文字、图片视频等。 CSS样式是表现。就像网页的外衣。比如&#xff0c;标题字体、…

类,封装,包

类&#xff0c;封装&#xff0c;包 一级目录1&#xff1a;下面代码的运行结果是&#xff08;&#xff09;2&#xff1a;以下哪项说法是正确的&#xff1f;3&#xff1a;以下代码在编译和运行过程中会出现什么情况4&#xff1a;在JAVA中&#xff0c;假设A有构造方法A(int a)&…

树基本概念+前中后序遍历二叉树

&#x1f308;一、树的基本概念 ☀️1.树的定义&#xff1a;树是一种非线性结构&#xff0c;看起来像一棵倒挂的树&#xff0c;根朝上&#xff0c;而叶朝下。 ☀️2.相关术语 1.根节点&#xff1a;图中的A&#xff0c;无前驱结点 2.叶节点&#xff08;终端节点&#xff09;&a…

代码随想录 738. 单调递增的数字

题目 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 示例 1: 输入: n 10 输出: 9 示例 2: 输入: n 1234 输出: 1234 示例 3: 输…

第九节HarmonyOS 常用基础组件4-Button

一、Button Button组件主要用来响应点击操作&#xff0c;可以包含子组件。 示例代码&#xff1a; Entry Component struct Index {build() {Row() {Column() {Button(确定, { type: ButtonType.Capsule, stateEffect: true }).width(90%).height(40).fontSize(16).fontWeigh…

Python多线程使用(二)

使用多个线程的时候容易遇到一个场景&#xff1a;多个线程处理一份数据 使用多线程的时候同时处理一份数据&#xff0c;在threading中提供了一个方法&#xff1a;线程锁 Demo&#xff1a;下订单 现在有多笔订单下单&#xff0c;库存减少 from threading import Thread from t…

Java类的初始化顺序

类初始化顺序遵循以下三个原则&#xff08;优先级依次递减&#xff09; 1、静态对象&#xff08;变量&#xff09;优先于非静态对象&#xff08;变量&#xff09;初始化&#xff0c;其中静态对象&#xff08;变量&#xff09;只初始化一次&#xff0c;而非静态对象&…

【海思SS528 | VO】MPP媒体处理软件V5.0 | 视频输出模块——学习笔记

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…