代理模式详解(静态代理和动态代理的区别以及联系)

原文链接:https://www.cnblogs.com/takumicx/p/9285230.html

1. 前言

代理模式可以说是生活中处处可见。比如说在携程上定火车票,携程在这里就起到了一个代理的作用,比起我们在官网上或者直接去柜台订票,携程可以为用户提供更多人性化的选择。再比如代购,我自己的mbp就是委托别人从香港买回来的,那么那个代购人就相当于代理,免去了我来回的车费以及办签证的麻烦。直观的理解,代理是这么一个对象,我们可以把工作委托给它,由它帮我们去执行工作同时解决工作相关的各种麻烦,最后把工作成果交给我们。这样一来我们只需要关注问题的核心,并告知代理,而不需要为其他琐碎的细节操心,这正是代理模式最大的好处,让客户端专注于真正的事务处理。具体而言,代理模式分为静态代理和动态代理,它们的设计思想类似,实现却大相径庭。它们的实现方式以及它们的区别是面试时经常会被问到的。下面我们就来详细介绍它们。

2. 代理模式详解

2.1 定义

为另一个对象提供一个替身或占位符以控制对这个对象的访问
定义简单明了。但还是有些没有解释清楚的地方,控制对对象的访问是为了干什么?其实是为了对真实的业务方法作某种形式的增强,比如在业务方法调用前作前置处理,在方法调用后作后置处理,而这些对客户端都是透明的。

2.2 普通代理模式类结构

1422237-20180709182022400-1435731281.png

被代理类和代理类实现同一接口,根据面向接口编程原则,可以使用被代理类的地方,统统可以用代理类代替,同时类内部持有被代理类的引用,真正的业务处理逻辑可以交给被代理类去作。

2.3 普通代理模式的实现(静态代理)

假设我要写一个日志代理,在真实的业务方法调用前后各记一条日志,看看静态代理是怎么做的。

  • 业务接口
public interface IService {void service1();void service2();
}

接口含有两个抽象业务方法

  • 被代理类
public class RealService implements IService {@Overridepublic void service1() {System.out.println("service1");}@Overridepublic void service2() {System.out.println("service2");}
}

被代理类实现业务接口,并且重写了业务方法。

  • 日志代理
public class StaticLogProxy implements IService {private IService iService;public StaticLogProxy(IService iService) {this.iService = iService;}@Overridepublic void service1() {System.out.println("service1 start!");iService.service1();System.out.println("service1 end!");}@Overridepublic void service2() {System.out.println("service2 start!");iService.service2();System.out.println("service2 end!");}}
  • 运行结果
    1422237-20180709182033744-1542782971.png

如上图所示,在业务方法执行前后分别记了一条日志,表示业务方法执行的开始和结束。我们的代理类成功对原有业务方法做了增强。但是静态代理存在以下问题:
1.代理类和被代理类耦合,适用性差。试想如果我希望为所有的业务类添加日志增强逻辑,那么岂不是要为几乎每个业务类编写代理类?这是不现实也是开发时无法接受的。
2.代理类的增强逻辑和业务逻辑过于耦合,不利于后期维护和扩展。从service1和service2中就可以看出,除了中间的业务处理不一样,代理类的处理逻辑是一样的,而我们竟然没有将其分离。
以上两点其实反映的是同一个问题:我们希望自己编写的代理类对所有业务类,所有业务方法都适用,而静态代理的泛用性太差了。问题的关键在于编写代理逻辑和业务逻辑分离的代理类,运行时才将其和具体的业务类绑定,对其业务方法做增强。为此,我们需要动态代理。动态代理的实现方式很多,下面以jdk自带的代理方式做说明。

3. JDK动态代理详解

3.1 JDK动态代理实现

  • 方法调用处理器
public class LogHandler implements InvocationHandler {//被代理对象private Object target;public LogHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(method.getName() + " start!");//前置处理Object res = method.invoke(this.target, args);//执行业务方法System.out.println(method.getName() + " end!");//后置处理return res;}
}

每一个代理对象都有一个与之关联的方法调用处理器,该处理器实现了InvocationHandler接口并重写了invoke方法。当我们调用代理对象的方法的时候,对该方法的调用会转交给方法调用处理器的invoke方法来执行,所以方法调用处理器的invoke方法是动态代理的核心。该方法内是通用的代理逻辑。在我们通过反射的方式通过被代理对象target执行业务逻辑的前后,可以对其作前置和后置增强。

  • 客户端代码
public class Client {public static void main(String[] args) {//1.创建被代理对象RealService realService = new RealService();//2.创建动态代理的方法调用处理器LogHandler logHandler = new LogHandler(realService);//3.创建动态代理对象IService service=(IService)Proxy.newProxyInstance(logHandler.getClass().getClassLoader(),realService.getClass().getInterfaces(),logHandler);service.service1();System.out.println("---------------");service.service2();}
}

第一步我们创建了被代理对象realService;第二步我们创建了动态代理的核心:方法调用处理器。因为处理器内部需要委托被代理对象去执行真正的业务方法,所以需要传入被代理对象作参数。第三步我们通过调用反射包下的Proxy类的静态方法去生成真正的代理对象。该方法的方法签名如下

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException

该方法含有三个参数
ClassLoader loader:指定加载的代理类的类加载器
Class<?>[] interfaces:指定代理类要实现的接口
InvocationHandler h:指定方法调用处理器
关于第二个参数可能要说明下,因为jdk的动态代理是针对接口的动态代理,代理类需要实现指定的业务接口,这里就是被代理类实现的那些接口。这样就能充分利用java多态特性,代码中所有能使用被代理对象的地方都能用代理对象进行替换。

  • 运行结果
    1422237-20180709182045615-1927386387.png

和静态代理的运行结果一样。如果我们要对其他业务类使用日志代理,只需要修改下客户端代码就行,这是静态代理办不到的。具有良好的扩展性是动态代理相比静态代理最大的优势。

3.2 方法调用流程图

1422237-20180709182053338-91387803.png

  • 1.客户端调用代理对象的业务方法,创建代理对象的时候传入了接口数组参数,故而代理对象也实现了业务接口。
  • 2.代理对象将请求转发给方法调用处理器的invoke方法
  • 3.方法调用处理器在invoke方法内部通过反射的方式调用被代理对象的业务方法

3.3 动态代理和静态代理的区别

  • 静态代理编译期生成代理类;动态代理运行期生成代理类。
  • 静态代理和被代理类及其业务逻辑耦合,适用性较差且代理逻辑难以扩展;动态代理可以在不知道被代理类的前提下编写代理逻辑,运行时才决定被代理对象,适用性好且代理逻辑易于扩展。

3.4 其他实现动态代理的方式

  • cglib面向类的动态代理
  • javaassist字节码操作库实现
  • asm

4. 总结

代理用以控制对对象的访问,本质上是对其功能提供某种形式的增强。按实现又可分为静态代理和动态代理。动态代理因其代理逻辑和业务逻辑相分离的特点,具有良好的适用性和可扩展性,是Spring中AOP的底层实现。

转载于:https://www.cnblogs.com/LoveShare/p/10796966.html

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

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

相关文章

一个简单的HelloWorld程序

/* * 编译器:  VC6.0 * 类 型:  C语言 */ 1 #include <stdio.h>//#includes代表是C预处理指令,stdio.h代表是在此行位置键入了库文件stdio.h的完整内容,是标准输入输出头文件,< and >代表是直接从库文件加载stdio.h文件。2 3 intmain(void)//int代表此main…

sass 安装配置和使用

一、什么是SASSSASS在CSS的基础上做了一些扩展&#xff0c;使用SASS你可以使用一些简单的编程思想进来编写CSS。比如&#xff0c;SASS中可以定义变量、混合、嵌套以及 函数等功能。只不过SASS不像CSS&#xff0c;可以直接运用到项目中&#xff0c;如果你需要将样式运用到项目中…

为什么我的对象被 IntelliJ IDEA 悄悄修改了?

背景 最近&#xff0c;在复习JUC的时候调试了一把ConcurrentLinkedQueue的offer方法&#xff0c;意外的发现Idea在debug模式下竟然会 “自动修改” 已经创建的Java对象&#xff0c;当时觉得这个现象很是奇怪&#xff0c;现在把问题的原因以及解决过程记录下来&#xff0c;希望你…

​std::multimap

2019独角兽企业重金招聘Python工程师标准>>> std::multimap multimap,是一个关联性容器,用于存放这样的元素,这些元素是由键以及关联的值组成.容器内容将根据元素的键进行排序.并且容器可以插入多个具有相同键的元素.接口 pair<const_iterator,const_iterator>…

容器部署解决方案Docker

一、Docker简介 1.1 虚拟化 【什么是虚拟化】 在计算机中&#xff0c;虚拟化&#xff08;英语&#xff1a;Virtualization&#xff09;是一种资源管理技术&#xff0c;是将计算机的各种实体资源&#xff0c;如服务器、网络、内存及存储等&#xff0c;予以抽象、转换后呈现出来&…

BREW做的第一个程序--Hello world!

这几天开始做BREW开发了&#xff0c;刚开始挺晕的。又是C指针&#xff0c;又是BREW的SDK文档&#xff0c;还有环境配置&#xff0c;一大堆东东&#xff0c;真是让人手忙脚乱。好不容易配好了环境&#xff0c;写出了第一个Hello world!程序。感觉还不错&#xff0c;就把代码和想…

careercup-链表 2.1

2.1 编写代码&#xff0c;移除未排序链表中的重复节点。 不使用临时缓存&#xff1a; 如果不允许使用临时的缓存(即不能使用额外的存储空间)&#xff0c;那需要两个指针&#xff0c; 当第一个指针指向某个元素时&#xff0c;第二个指针把该元素后面与它相同的元素删除&#xff…

随机排列_“按字母顺序排列”其实是种随机顺序

闲话之前有聊过&#xff0c;微信公众号这边接的广告不多&#xff0c;主要收益来自于微信自带的中插广告。后来同学们还开玩笑说“研究半天没发现这个图片哪里没品了&#xff0c;才发现是广告。”另外还有一部分收益&#xff0c;来自于各位的打赏。鉴于大部分人都是打赏一两块钱…

android 获取应用的资源id和uri

2019独角兽企业重金招聘Python工程师标准>>> 在某些应用中&#xff0c;为了实现应用apk资源放入重复利用&#xff0c;或者使用反射得到本应用的资源&#xff0c;需要使用反射反射方式获得&#xff0c;但Resources类中也自带了这种获取方式&#xff0c;并且功能更加强…

(SQL语句)按指定时间段分组统计

我现在有一张表&#xff1a; 列名1 时间 03174190188 2009-11-01 07:17:39.217 015224486575 2009-11-01 08:01:17.153 013593006926 2009-11-12 08:04:46.560 013599584239 2009-11-22 08:53:27.763 013911693526 2009-11-23 08:53:51.683 013846472440 2009…

数据库迁移_数据库迁移了解一下

mongodb数据迁移因服务器到期&#xff0c;需要将之前机器上面的数据进行数据迁移&#xff0c;并将服务全部docker化备份首先需要将现有即将到期的服务器上面的mongo数据进行备份mongodump -h dbhost -d dbname -o dbdirectory-h&#xff1a;mongodb所在服务器地址&#xff0c;可…

人脸颜值评分软件_在线算个颜值,特科学的那种 | 知多少

用 AI&#xff0c;科学的为颜值打个分。用 AI&#xff0c;打造科学颜值打分器https://www.zhihu.com/video/1185672892095848448图文版本送给不方便打开的朋友 (●u●)」如何科学的为颜值打个分&#xff1f;三庭五眼、四高三低&#xff1f;脸部是否对称&#xff1f;是否与本民族…

图片翻转

图片翻转 原文:图片翻转本人录制技术视频地址&#xff1a;https://edu.csdn.net/lecturer/1899 欢迎观看。这一节继续为大家介绍CSS3的动画效果: 图片翻转。 在iOS中的章节中&#xff0c;我也介绍过类似的效果&#xff0c;如果感兴趣的话&#xff0c;请点击这里查看&#xff1a…

【原】页面跳转以及表单提交中有中文的解决办法

这两天一直碰到一个郁闷的问题&#xff0c;在对表单进行提交的时候&#xff0c;用户名是中文的&#xff0c;怎么测试都不通过, 今天上午突然想起来是不是因为中文字符编码的问题!经过测试&#xff0c;果然是因为这个问题&#xff01; 现在把解决方法贴出来&#xff01;呵呵&…

实验吧之NSCTF misc250

下载的是一个流&#xff0c;用wireshark打开&#xff0c;由于原题是这样的&#xff1a;小绿在学习了wireshark后&#xff0c;在局域网内抓到了室友下载的小东东0.0 你能帮他找到吗&#xff1f;说明我们应该重点关注http传送的东西&#xff1a; 这里面一共有四个http文件&#x…

西澳大学商科专业排名_澳洲西澳大学优势专业排名多少

澳洲西澳大学优势专业排名多少西澳大学农业和林业专业在2018年QS世界排名中排名第32西澳大学解剖学和生理学专业在2018年QS世界排名中排名第13西澳大学地球与海洋科学专业在2018年QS世界排名中排名第32西澳大学土木结构工程专业在2018年QS世界排名中排名第37西澳大学矿产和采矿…

基于SOUI开发的应用展示

本页面列出基于SOUI开发的产品 欢迎使用SOUI的朋友提供资源&#xff1a;setoutsoft#qq.com #-> U大师 http://www.udashi.com EiisysIM: 是一款为工作场景而设计的企业即时通讯软件, &#xff0c;含PC版和手机版。具有完善的即时通讯、文件传输、语音通话等功能。通讯录由企…

供应商寄售库存管理_【论文解读】物流联合外包下库存管理模式对供应链运作的影响...

物流联合外包下库存管理模式对供应链运作的影响作者&#xff1a;冯颖&#xff0c;林晴&#xff0c;张景雄&#xff0c;张炎治目录 1 引言2 问题描述3 数学模型4 协调模型5 数值算例6 结论1 引言传统库存管理模式下&#xff0c;供应链中各节点企业的库存管理各自为政&#xff0c…

SQLserver2000 实例管理工具

1、企业管理器是microsoft管理控制台(Microsoft management console)的一个插件。插件是运行在MMC中的部件、他不能独立运行&#xff0c;但必须包含着MMC中。企业管理器提供MMC形式的界面。像IIS、MTS也是MMC插件 2、查询分析器右边有模板选项卡&#xff0c;可以创建现成的模板…

linux设置nexus开机自启动_CentOS7配置nexus开机自启动

CentOS7配置nexus开机自启动新建nexus启动脚本进入/etc/init.d目录&#xff0c;新建脚本文件nexus// 进入/etc/init.d[rootlinux_maven etc]# cd /etc/init.d/// 新建脚本文件nexus[rootlinux_maven init.d]# vim nexus脚本内容:#!/bin/bash#chkconfig:2345 20 90#description:…