18.古今成大事者,必以多选替身为第一要义——代理模式详解

“杏市而外,尚有何人可以分统?亦须早早提拔。办大事者以多多选替手为第一义,满意之选不可得,姑节取其次,以待徐徐教育可也。 ——曾国藩·同治元年四月十二日”

在这里插入图片描述


一言

代理模式核心思想是为对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。


概述

古今成大事者,必以多选替身为第一要义。“风云人物”们最容易犯的错误是恋权恋栈。在他们自己看来,总是以为“非我不可”,别人都不如我。曾国藩则恰恰相反,他在血腥镇压了太平天国运动之后,主动推进湘军的裁撤,交权、交人,功成身退。
用他自己的话来说就是“办大事者以多多选替手为第一义”。编程如做人,设计模式是一门哲学。代理模式的思想恰恰就是这包含大智慧的“找替身”。
在这里插入图片描述

我们在实际的开发过程中,常常遇到开销极大的对象、需要安全控制的对象或者一些远程对象,在面临以上场景的时候总会有一种尾大不掉的感觉。在这种时候如果能对代理模式稍加应用真的就会有种“柳暗花明”的感觉。
在这里插入图片描述
我习惯将代理模式分为两种:静态代理、动态代理,当然,很多资料会把动态代理细分为JDK代理和Cglib代理,这次我也会逐一进行拆解。


三叉戟登场

电视剧《三叉戟》真的是我的珍藏剧目了,尤其是预审嫌疑人的桥段,三叉戟分工明确,有的搜集证据,有的旁敲侧击,有的单刀直入…我们不妨以这个场景为例来拆解下代理模式。
在这里插入图片描述


静态代理

设计

在这里插入图片描述

代码实现

审讯能力

public interface InterrogationDao {void interrogation();
}

预审民警

public class Police implements InterrogationDao{@Overridepublic void interrogation() {System.out.println("预审警察正在提审嫌疑人");}
}

预审文员

public class PoliceProxy implements InterrogationDao{private Police police;public PoliceProxy(Police police) {this.police = police;}@Overridepublic void interrogation() {System.out.println("开始代理...预审文员协助梳理卷宗");police.interrogation();System.out.println("代理结束...预审文员协助归纳供词");}
}

测试

public class Client {public static void main(String[] args) {PoliceProxy policeProxy = new PoliceProxy(new Police());policeProxy.interrogation();}
}

在这里插入图片描述

优劣分析

在上例中,文员与预审民警都具有审问能力,而文员更多的是充当一个代理人的角色,预审过程中一些前置和后置的操作都由他来代理完成。预审警察则承担起主要的业务职责。
在不修改目标对象功能的前提下,能够通过代理对象对目标功能进行扩展。但是由于代理对象需要与目标对象实现一样的接口,导致代理类的数量会逐渐膨胀而且一旦接口增加方法,目标对象与代理对象势必要同时进行维护,这其实是很致命的缺陷。


动态代理

相信很多同学看到这里已经想起了一些常用的东西,没错,就是SpringAOP。SpringAOP是代理模式最普遍的应用之一,它正是通过所谓的切面编程实现了极低耦合下的业务嵌入开发。但我们仔细想想,如果是按照上文所描述的静态代理模式,整体架构的耦合度怎么可能低呢?
事实上,SpringAOP的实现并非基于静态代理,而是基于动态代理实现的。


JDK代理

java生态中,泛化关系的实现要么通过类继承、要么通过接口实现。JDK代理在处理动态代理不可避免的泛化问题时,采用了接口实现的这个路线。
在JDK代理模式中,代理对象不需要实现接口,但目标对象需要实现接口,否则就不能实现动态代理。所以说JDK代理也被称为接口代理。
JDK代理基于JDK API,动态的在内存中构建代理对象。

代码实现

审讯能力

public interface InterrogationDao {void interrogation();void sayHello();
}

预审民警

public class Police implements InterrogationDao {@Overridepublic void interrogation() {System.out.println("预审警察正在提审嫌疑人");}@Overridepublic void sayHello() {System.out.println("你这样做,对得起党和人民的信任吗?!");}
}

预审文员筹备处

public class PoliceProxyOffice {private Object target;public PoliceProxyOffice(Object target) {this.target = target;}public Object getProxyInstance(){return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),(proxy,method,args)->{System.out.println("JDK代理开始...预审文员办公室正在梳理卷宗");Object returnVal = method.invoke(target, args);System.out.println("JDK代理提交...预审文员办公室完成供词归纳");return returnVal;});}
}

测试

public class Client {public static void main(String[] args) {InterrogationDao target = new Police();InterrogationDao proxyInstance = (InterrogationDao) new PoliceProxyOffice(target).getProxyInstance();System.out.println("proxyInstance"+proxyInstance);System.out.println("proxyInstanceClass"+proxyInstance.getClass());proxyInstance.sayHello();proxyInstance.interrogation();}
}

在这里插入图片描述

分析

我们可以清晰的看到,通过JDK动态代理,我们直接切断了预审民警和代理类的直接耦合关系,整个过程也更加清爽和优雅。随着业务的扩展,即便业务类中的实现细节快速变化或增长,动态代理的模式下并不需要过度关心代理类的实现。依托于反射机制,无论业务类如何扩充,代理类都会巧妙的自动扩充。


Cglib代理

刚刚有说过,java生态下泛化关系的实现要么通过类继承、要么通过接口实现。
静态代理和JDK代理都要求目标对象去实现接口,但有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候可以使用目标对象子类来实现代理,这就是Cglib代理的底层原理。
在内存中构建一个子类对象从而实现对目标对象功能的扩展,通过类继承的方式实现代理,这也是为什么Cglib代理又被称为子类代理的原因。
Cglib是一个强大的高性能的代码生成包,它可以在运行期间扩展java类。正是因为这一特性,它才得以被广大的AOP框架应用,这其中就包括大名鼎鼎的SpringAOP。

代码实现

首先要注意,在没有其它依赖的情况下要引入cglib的核心包
在这里插入图片描述

预审警察

public class IterrogationDao {public void interrogation(){System.out.println("预审警察正在提审嫌疑人");}public String sayHello(){String msg = "你这样做,对得起党和人民的信任吗?!";System.out.println(msg);return msg;}
}

预审文员筹备处

public class PoliceProxyOffice implements MethodInterceptor {private Object target;public PoliceProxyOffice(Object target) {this.target = target;}public Object getProxyInstance(){Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("Cglib代理开始...预审文员办公室正在梳理卷宗");Object returnVal = method.invoke(target, args);System.out.println("Cglib代理提交...预审文员办公室完成供词归纳");return returnVal;}
}

测试

public class Client {public static void main(String[] args) {IterrogationDao target = new IterrogationDao();IterrogationDao proxyInstance = (IterrogationDao) new PoliceProxyOffice(target).getProxyInstance();proxyInstance.interrogation();String msg = proxyInstance.sayHello();System.out.println("msg:"+msg);}
}

在这里插入图片描述

分析

可以很清晰的看到,在基于cglib实现动态代理的过程中,并没有任何接口的实现。这也是JDK代理与Cglib代理的最大区别。Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。

需要注意的是,cglib既然是基于类继承的方式实现了动态代理,那么目标对象的目标方法就必须能够被继承,这也就要求目标方法必然是不可以用final修饰的。同理对于static修饰的方法,动态代理也自然会跳过。如果违反上述规定就会报错:java.lang.IllegalArgumentException

在不使用AOP框架的前提下,如何选择两种动态代理的实现方式也许是我们最关注的问题,通常情况下:

  • 如果目标对象需要实现接口,用JDK代理
  • 如果目标对象不需要实现接口,用Cglib代理

几种变体

在这里插入图片描述

前面我们花了大量的篇幅来讲静态代理和动态代理,相信大家对于这两种编程中最常见的代理模式都有了自己的理解。
事实上代理思想并不仅仅拘泥于上述的几种场景。
下面几种大家耳熟能详的场景都是代理模式的应用,比如说:

  • 内网通过代理穿透防火墙实现公网访问的防火墙代理;
  • 为了应对各种击穿,大家经常用缓存处理高频数据读取的缓存代理;
  • 多线程编程中的多线程同步用到的同步代理等等;

编程是一门艺术,它使得每个人对于不同的场景都有不同的解读方式,尤其是针对设计模式而言,它不仅能让我们学会一种解决方案,更能让我们从中领悟人生的哲理。
愿我们都能不忘初心,保持热爱,奔赴山海。


关注我,共同进步,我们的世界中万物皆代码,又不只有代码。希望我的文字能澎湃你的思绪。——Wayne

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

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

相关文章

029—pandas 遍历行非向量化修改数据

前言 在 pandas 中,向量化计算是指利用 pandas 对象的内置方法和函数,将操作应用到整个数据结构的每个元素,从而在单个操作中完成大量的计算。 但在一些需求中,我们无法使用向量化计算,就需要迭代操作,本例…

汽车电子零部件(4):行泊一体ADAS

前言: 现阶段智能汽车行业正在大规模力推无限接近于L3的L2++或L2.9自动驾驶量产落地,类似于当初智能手机替换传统手机的行业机会期。智能汽车常见的智能驾驶功能包括: 行车场景:自适应巡航控制ACC;自动变道辅助ALC;交通拥堵辅助TJA;车道居中LCC;领航辅助NOA; 泊车场…

kkview远程控制: 内网远程桌面控制软件

内网远程桌面控制软件:高效、安全的远程管理方案 在信息技术日新月异的今天,内网远程桌面控制软件已成为许多企业和个人用户不可或缺的工具。这类软件允许用户通过内部网络,实现对其他计算机的远程访问和控制,从而大大提高工作效…

出现 Duplicate keys detected: ‘0‘. This may cause an update error 解决方法

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 前端测试的时候,在浏览器的控制台输出如下: [Vue warn]: Duplicate keys detected: 0. This may cause an update error.found in---> <Root>截图如下: 2. 原理分析</

双指针 | 移动零 | 复写零

1.移动零 题目描述&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 示例&#xff1a; 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0]解题思路&#xff1a; right指针一直往后移动&#xff0c;当…

SpringBoot-邮件任务

很多时候的网站都有邮件发送功能&#xff0c;下面我们来看看邮件发送功能结合springboot该怎么实现下面的例子我是用的qq邮箱来完成的 1.导入依赖 我的springboot的版本是2.x.x的&#xff0c;如果发现运行不成功&#xff0c;请将版本降低到2.x.x <!--邮件任务--><depe…

HarmonyOS NEXT应用开发—折叠屏音乐播放器方案

介绍 本示例介绍使用ArkUI中的容器组件FolderStack在折叠屏设备中实现音乐播放器场景。 效果图预览 使用说明 播放器预加载了歌曲&#xff0c;支持播放、暂停、重新播放&#xff0c;在折叠屏上&#xff0c;支持横屏悬停态下的组件自适应动态变更。 实现思路 采用MVVM模式进…

Vue组件中引入jQuery

两种在vue中引入jQuery的方式 1、普通html中使用jQuery 将jQuer的文件导入到项目中&#xff0c;然后直接使用<script src"jQuery.js"></script>即可。 <script src"jQuery.js"></script> 2、vue组件中使用jQuery 安装依赖 c…

【并查集】模版

【模板】并查集 - 洛谷 #include <bits/stdc.h> using namespace std; const int N2e59; int a[N]; int Find(int x) {if(xa[x]){return x;}else{a[x]Find(a[x]);return a[x];} } void push(int x,int y) {a[Find(x)]Find(y);return ; } int main() {int n,m; cin>>…

2000-2021年各省外商直接投资水平面板数据(含原始数据+计算结果)(无缺失)

2000-2021年各省外商直接投资水平面板数据&#xff08;含原始数据计算结果&#xff09;&#xff08;无缺失&#xff09; 1、时间&#xff1a;2000-2021年 2、指标&#xff1a;外商直接投资额&#xff08;万美元&#xff09;、外商直接投资额&#xff08;万元&#xff09;、国…

2.3 HTML5新增的常用标签

2.3.1 HTML5新增文档结构标签 在HTML5版本之前通常直接使用<div>标签进行网页整体布局&#xff0c;常见布局包括页眉、页脚、导航菜单和正文部分。为了区分文档结构中不同的<div>内容&#xff0c;一般会为其配上不同的id名称。例如&#xff1a; <div id"h…

PS学习 - 抠图-通道-主题颜色和背景颜色不能相近

抠出蝴蝶 1.通道抠图 套索工具 这里需要圈住你要的&#xff0c;注意尽量小点 ctrl j 复制 然后去掉背景 点击通道 找到明暗对比最大的通道&#xff0c;这里我理解为颜色反差最大的那个&#xff0c;突出你要抠的东西 搜了下说是一般为蓝色 复制通道 ctrll调出色阶 通过移…