From 8c1094ec43179455307a6fdc9d5945c1c2acddce Mon Sep 17 00:00:00 2001 From: wangzipai <38389763+wangzipai@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:22:44 +0800 Subject: [PATCH] [PUBLISHER] Merge #15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PUSH NOTE : 动态代理.md * PUSH NOTE : 反射机制.md --- content/Obsidian/后端/动态代理.md | 306 ++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 content/Obsidian/后端/动态代理.md diff --git a/content/Obsidian/后端/动态代理.md b/content/Obsidian/后端/动态代理.md new file mode 100644 index 000000000..8a5a46e76 --- /dev/null +++ b/content/Obsidian/后端/动态代理.md @@ -0,0 +1,306 @@ +--- +date created: 2024-10-30 18:12 +date updated: 2024-10-31 16:22 +share: "true" +link: "false" +--- + +# 关于代理 + +## 代理的概述 + +### 什么是动态代理 + +==使用 jdk 的[[./反射机制|反射机制]],创建对象的能力, 创建的是代理类的对象==。 + +- 动态:在程序执行时,调用 jdk 提供的方法才能创建代理类的对象。 +- jdk 动态代理,必须有接口,目标类必须实现接口, 没有接口时,需要使用 cglib 动态代理 + +### 动态代理能做什么 + +==可以在不改变原来目标方法功能的前提下, 可以在代理中增强自己的功能代码。== + +​例如实际开发中,你所在的项目中,有一个功能是其他人(公司的其它部门,其它小组的人)写好的,你可以使用。你发现这个功能,现在还缺点,不能完全满足我项目的需要。 我需要在gn.print()执行后,需要自己在增加代码。用代理实现 gn.print()调用时, 增加自己代码, 而不用去改原来的 GoNong文件。 + +```java +// 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盘的方法, 表示你的厂家和商家做的事情。 + +```java +// 表示功能的,厂家,商家都要完成的功能 +public interface UsbSell { + //定义方法 参数 amount:表示一次购买的数量,暂时不用 + //返回值表示一个u盘的价格。 + float sell(int amount); + + //可以多个其它的方法 + //void print(); +} +``` + +2.创建厂家类,实现1步骤的接口 + +```java +//目标类:金士顿厂家, 不接受用户的单独购买。 +public class UsbKingFactory implements UsbSell { + @Override + public float sell(int amount) { + System.out.println("目标类中的方法调用 , UsbKingFactory 中的sell "); + //一个128G的u盘是 85元。 + //后期根据amount ,可以实现不同的价格,例如10000个,单击是80, 50000个75 + return 85.0f; + } +} +``` + +3.创建商家,就是代理类,也需要实现1步骤中的接口。 + +```c +// 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(); + + @Override + public float sell(int amount) { + //调用目标方法 + float price = factory.sell(amount); + //只增加1元 + price = price + 1; + return price; + } +} +``` + +4.创建客户端类,调用商家的方法买一个u盘。 + +```java +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原则 +- 不用创建代理类文件,代理的目标类是灵活的,可以随意给不同目标创建代理 + +## 动态代理的两种实现方式 + +1. jdk动态代理(理解): 使用java反射包中的类和接口实现动态代理的功能。 + - 反射包 java.lang.reflect , 里面有三个类 : InvocationHandler , Method, Proxy. +2. cglib动态代理(了解): cglib是第三方的工具库, 创建代理对象。 + - cglib的原理是继承, cglib通过继承目标类,创建它的子类,在子类中重写父类中同名的方法, 实现功能的修改。 + - 因为cglib是继承,重写方法,所以要求目标类不能是final的, 方法也不能是final的。 + - cglib的要求目标类比较宽松, 只要能继承就可以了。cglib在很多的框架中使用,比如 mybatis ,spring框架中都有使用。 + +注意:**jdk动态代理必须有接口,对于无接口类,必须使用cglib来为它创建动态代理** + +## 动态代理开发中实例 + +```java +// 目标完成的功能 +public interface HelloService { + + //打印报告, 报表 + int print(String name); +} + + +// 目标类 +public class GoNeng implements HelloService { + @Override + public 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; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + //我在项目中记录数据库, + //调用目标方法, 执行print()得到 2 + Object 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,参数“市场”给了args + int num = proxy.print("市场"); + System.out.println("我们期望的 num ==" + num); + + // 总之proxy,handler,目标类之间是相关联的。 + } +} +``` + +## 实现代理的步骤 + +- 定义业务接口和实现:习惯性地首先定义一个接口,然后提供一个或多个实现。 +- 创建调用处理器:实现`InvocationHandler`接口的类,它定义了代理实例的调用处理程序。 +- 生成代理实例:通过调用`Proxy.newProxyInstance()`,传入目标类的类加载器、接口数组和调用处理器来创建。 + +# 动态代理与静态代理的比较 + +两者的核心差异在于:静态代理的代理类是==编译期就确定==下来的,而动态代理的代理类是在==运行时动态创建==的。 + +#### 静态代理 + +**静态代理通常要求程序员手动编写代理类**。这就意味着,每增加一个代理目标就得增加一个代理类,工作量大,且不具备通用性。静态代理的优势在于可以直观地看到代理逻辑,也有利于错误的排查。 + +#### 动态代理 + +**动态代理不需要显示地编写代理类**,系统通过反射等机制在运行时创建代理对象,极大地提高了代码的灵活性和可复用性。动态代理的使用使得单一代理类可以代理多种类型的主体。