动态代理

动态代理凭借其灵活性在框架中有着广泛的应用,下面简单记录一下我的学习理解;

代理模式:

基本概念

在百度百科中没有动态代理的概念,但是动态代理其实就是代理模式中的一种,所以在了解动态代理前最好先了解一下代理模式;

代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式;

代理模式就是为其他对象提供一种代理以控制对这个对象的访问;在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

通过加粗的字可以了解到,代理模式应用到什么地方,这种情况在框架中是很常见的,这也是为什么代理模式在框架中广泛应用的原因;

代理模式的组成:

  • 抽象角色:通过接口或抽象类声明真实角色实现的业务方法;

  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作;

  • 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用;

抽象角色可以理解为是真实角色的一种行为,真实角色其实就是需要被代理的角色,而代理角色就是连接真实角色的一个桥梁;

在下面的代码演示中一定一定要清楚它们三者间的关系,不然很容易就被绕晕;

代理模式的优点:

  • 职责清晰,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰;

  • 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用;

  • 高扩展性

代理模式的分类:

代理模式分为两种:

  1. 静态代理
  2. 动态代理

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了;

动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象,这也就意味着你不再需要关心代理类如何实现,也不再需要写代理类了;


这样一看就能看出来动态代理显然比静态代理更加灵活;但是动态代理和静态代理的原理依旧是相同的,所以学习代理模式最好从简单的静态代理开始;

静态代理实现

静态代理实现其实很简单,无非就是三部分:

  1. 被代理类的行为(接口)
  2. 被代理类or目标类(实现接口)
  3. 代理类(实现接口)

可以用简单用一张图表示它们三者间的关系:

下面我将使用租房子的例子来实现静态代理;

租房子案例

假设一种情景:有一群房东想要出租房子,但是出租房子需要办手续、发布租赁信息什么的非常麻烦,所以他们就可以找到中介公司,让他们来负责房子的出租以及手续的办理,这样只要有人想要租房子,就需要通过中介公司来完成租房子了,所以租户没有办法直接去找房东租房子

在这里面就是一个典型的代理模式,房东就是目标类,中介公司是代理类,房东有租房子的行为,而中介公司也是办理的租房子的业务,所以他们两个的行为是相同的,都是租房子,这就对应上上图的三种关系了;

一定要分的清这几种关系,不要到代码里糊涂了;

代码实现

下面就用代码来实现一下(代码有详细注释):

租房子行为

RentHouse.java

// 代理类和被代理类都有的行为
// 被代理类:各种房东要出租房子
// 代理类:中介公司帮助房东出租房子,也相当于有出租房子这个行为
public interface RentHouse {// 出租房子方法void rent();
}

房东(这里就定了两个):实现RentHouse.java接口

HostJack.java

import com.yang.RentHouse;// 一个名为Jack的房东要出租房子
public class HostJack implements RentHouse {@Overridepublic void rent() {System.out.println("我Jack今天要出租房子!!!");}
}

HostBill.java

import com.yang.RentHouse;// 一个名为Bill的房东要出租房子
public class HostBill implements RentHouse {@Overridepublic void rent() {System.out.println("我Bill今天要出租房子!!!");}
}

中介公司:实现RentHouse.java接口

因为不同的房东的房子不同,所以他们不能使用相同的代理,即一个房东对应着一个代理,一个代理类对应着一个被代理类

ProxyCompanyForJack.java

import com.yang.RentHouse;
import com.yang.byproxy.HostJack;// 中介公司为Jack服务
public class ProxyCompanyForJack implements RentHouse {// 声明被代理类对象(出租房子的人Jack)// 因为代理类存在的意义就是让‘租房子的人’可以通过‘代理类’联系到‘出租房子的人’HostJack jack;// 设置传入的房东jackpublic void setJack(HostJack jack) {this.jack = jack;}@Overridepublic void rent() {// 调用被代理类(出租房子的人Jack)的rent方法jack.rent();}
}

ProxyCompanyForBill.java

import com.yang.RentHouse;
import com.yang.byproxy.HostBill;// 中介公司为Bill服务
public class ProxyCompanyForBill implements RentHouse {// 同样声明被代理类对象(出租房子的人Bill)HostBill bill;// 设置传入的房东billpublic void setBill(HostBill bill) {this.bill = bill;}@Overridepublic void rent() {// 调用被代理类(出租房子的人bill)的rent方法bill.rent();}
}

可以看到代理类并没有真正实现接口RentHouse的rent方法,而是调用的目标类的rent方法,这就是代理的特点之一;

三者间的关系已经梳理完成,下面就可以测试一下了:

测试:

import com.yang.byproxy.HostBill;
import com.yang.byproxy.HostJack;
import com.yang.proxy.ProxyCompanyForBill;
import com.yang.proxy.ProxyCompanyForJack;// 租房子测试
// 其实这就类似中介公司来了一个租房子的人要租房子
public class RentTest {public static void main(String[] args) {// 被代理类:房东JackHostJack jack = new HostJack();// 创建代理类对象(假设租户这时要看Jack的房子,就需要Jack的代理)ProxyCompanyForJack forJack = new ProxyCompanyForJack();// 将jack房东传入代理类中,表示他要出租房子了forJack.setJack(jack);// 调用出租房子的方法// 表面上看的是调用的中介公司的rent方式,实际则是通过中介公司调用的Jack房东的rent方法forJack.rent();/// 同样房东Bill也是一样HostBill bill = new HostBill();ProxyCompanyForBill forBill = new ProxyCompanyForBill();forBill.setBill(bill);forBill.rent();}
}

输出结果:

我Jack今天要出租房子!!!
我Bill今天要出租房子!!!

这就是一个完整的静态代理的实现了,当然很简单,只是为了展示好他们之间的关系;

可能第一次接触代理类会感觉有点怪:在测试的时候明明创建了被代理类房东,直接用创建的房东对象调用rent方法不好吗?这不是多此一举吗?

如果你有这个疑问,一定要清楚代理模式的前提:用户无法直接调用被代理类的方法,也就是说房东你是接触不到的,但是你又想用房东的rent方法,那么就只能通过代理类来实现了,代理类就是一个媒介,就是你和房东的桥梁;

静态代理缺点

分析一下静态代理存在的问题:

如果在一个项目中,目标类(被代理类)和代理类很多时候,有以下的缺点:

  • 当目标类增加了, 代理类可能也需要成倍的增加,因为一个代理类对应一个目标类,会造成代理类数量过多,不易于管理,代码量增多;
  • 当你的接口中功能增加了, 或者修改了,会影响众多的实现类,厂家类,代理都需要修改,影响比较多,修改成本太高;

而动态代理就可以很好的解决上面的问题;

动态代理实现

在动态代理中即使目标类很多, 但是代理类数量可以很少,并且当你修改了接口中的方法时,不会影响代理类;

动态代理: 在程序执行过程中,使用jdk的反射机制,创建代理类对象, 并动态的指定要代理目标类;

它可以实现不用写代理类的实现就可以创建代理类对象;

jdk 动态代理是基于 Java 的反射机制实现的,使用 jdk 中接口和类实现代理对象的动态创建

jdk 动态代理要求目标对象(被代理类)必须实现接口,这是 java 设计上的要求,从 jdk1.3 以来,java 通过 java.lang.reflect 包提供三个类支持代理模式 ,分别是:Proxy, Method 和 InovcationHandler;

所以在写动态代理代码前需要了解一下这三个类;

InvocationHandler 接口

InvocationHandler 接口叫做调用处理器,负责完调用目标方法(就是被代理类中的方法),并增强功能;

通过代理类对象执行目标接口中的方法,会把方法的调用分派给调用处理器 (InvocationHandler)的实现类,执行 实现类中的 invoke() 方法,我们需要把在该invoke方法中实现调用目标类的目标方法;

(记住这里的实现类,在这个实现类中会调用目标类中的目标方法)

InvocationHandler接口:

invoke方法

在 invoke 方法中可以截取对目标方法的调用,调用方式是通过反射调用;

方法中的参数:

public Object invoke ( Object proxy, Method method, Object[] args)

  • proxy:生成的代理对象

  • method:目标方法

  • args:目标方法的参数

第一个参数 proxy 是 jdk 在运行时赋值的,在方法中直接使用,所以这个参数不需要我们管;

第二个参数method是实现调用目标方法的关键,只有通过它的invoke方法(这是Method对象的内置方法)才可以调用到被代理类的目标方法;

第三个参数是方法执行的参数, 这三个参数都是 jdk 运行时赋值的,无需程序员给出,所以也不需要我们管;

Method 类

这里就说说invoke中的第二个参数method,它是Method类的实例化对象,Method类也有一个方法叫invoke(),

该方法在反射中就是用来执行反射对象的方法的,虽然这两个invoke方法名字一样,但是没有一点关系;

public Object invoke ( Object obj, Object... args)

  • obj:表示目标对象

  • args:表示目标方法参数,就是其上一层 invoke 方法的第三个参数

该方法的作用是:调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定;

就是因为method方法是InvocationHandler接口中的invoke方法的第二个参数,所以我们可以通过它实现对目标类的目标方法的调用;

从这可以联想到静态代理中的代理类中也是调用了目标类的目标方法;

Proxy 类

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

代理对象是由Proxy类来创建的,所以动态代理在使用上并没有那么难,因为最难的部分java已经帮你实现了,你只需要掌握使用它的方法就可以了;

public static newProxyInstance ( ClassLoader loader, Class[] interfaces, InvocationHandler handler)

  • loader:目标类的类加载器,通过目标对象的反射可获取
  • interfaces:目标类实现的接口数组,通过目标对象的反射可获取
  • handler:调用处理器

这几个参数一定要记好他们的功能,不然代码可能看不懂;

代码实现

下面我就用代码来简单实现一下,首先分析两个问题:

问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象(代理类如何创建)

问题二:当通过代理类的对象调用接口实现方法时,如何动态的去调用被代理类中的目标方法(如何通过代理类调用被代理类中的目标方法)

下面我就用代码实现动态代理,同样这两个问题的答案也在代码中;

还是租房子案例:

RentHouse.java

// 代理类和被代理类都有的行为
// 被代理类:各种房东要出租房子
// 代理类:中介公司帮助房东出租房子,也相当于有出租房子这个行为
public interface RentHouse {// 出租房子方法void rent();
}

房东(这里就定了两个):实现RentHouse.java接口

HostJack.java

import com.yang.RentHouse;// 一个名为Jack的房东要出租房子
public class HostJack implements RentHouse {@Overridepublic void rent() {System.out.println("我Jack今天要出租房子!!!");}
}

HostBill.java

import com.yang.RentHouse;// 一个名为Bill的房东要出租房子
public class HostBill implements RentHouse {@Overridepublic void rent() {System.out.println("我Bill今天要出租房子!!!");}
}

到目前为止,代码还没有任何不同,下面就是变化最大的地方;

动态代理的实现:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 这个类可以简单理解为一个代理工具类
// 通过该类可以创建任意代理
public class ProxyCompany {// 调用该方法返回一个对应的代理类对象// 因为无法判断返回的是什么类型的代理,所以返回值是Object// 传入参数obj是目标类的实列,通过该‘目标类’创建对应的‘代理类’对象// 同样无法判断传入的目标类是什么类型,所以只能使用Objectpublic static Object getProxyInstance(Object obj) {// 创建一个handler对象,它的作用就是实现通过‘代理类’去调用‘目标类’的目标方法MyInvocationHandler handler = new MyInvocationHandler();// 设置当前的目标类对象handler.setObj(obj);// 调用lang包下自带的Proxy类的静态方法newProxyInstance()创建代理类// 这就是java已经帮你实现的一个创建代理类的方法,你只需要传入对应参数即可// loader:目标类的类加载器// interfaces:目标类实现的接口数组(这里就是RentHouse)// handler:调用处理器return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);}
}
// 这个类也是一个类似工具类,它主要是为了实现重写InvocationHandler接口的invoke方法,
// 因为通过上面创建的‘代理类’会运行到该类中调用‘目标类’的方法
// 所以实际上这个类就是为了辅助上面的类ProxyCompany的
class MyInvocationHandler implements InvocationHandler {private Object obj; // 目标类对象// 设置目标类对象public void setObj(Object obj) {this.obj = obj;}// proxy:当前的代理类// method:当前‘代理类’对象调用的方法,该方法也就作为了‘目标类’对象要调用的方法(动态的无法确定是哪一个)// args:方法中的参数@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 调用目标类中的方法// this.obj:目标类对象Object returnValue = method.invoke(this.obj, args); // 传入对象和方法参数//上述方法的返回值就作为当前类中的invoke()的返回值。return  returnValue;}
}

这部分代码就是核心部分,注释也写的很详细,仔细琢磨几遍就能理解,就是个用法并不难;

测试代码:

import com.yang.RentHouse;
import com.yang.byproxy.HostBill;
import com.yang.byproxy.HostJack;
import com.yang.proxy.ProxyCompany;// 租房子测试
public class RentTest {public static void main(String[] args) {// 被代理类:房东JackHostJack jack = new HostJack();// 获取代理对象RentHouse forJack = (RentHouse) ProxyCompany.getProxyInstance(jack);// 执行出租房子方法forJack.rent();// 房东Bill也是一样HostBill bill = new HostBill();RentHouse forBill = (RentHouse) ProxyCompany.getProxyInstance(bill);forBill.rent();}
}

可以看出来其实动态代理和静态代理还是很多地方是相同的;

输出结果:

我Jack今天要出租房子!!!
我Bill今天要出租房子!!!

关键的几步:

如果实在搞不清楚的话强烈建议把代码模仿写下来之后 debug 走几回,分析什么时候创建代理,什么时候执行方法,多走几遍就清楚整个流程了;

总结

实现动态代理基本的步骤:

  1. 创建接口,定义目标类要完成的功能
  2. 创建目标类实现接口
  3. 通过Proxy创建代理类对象;
  4. 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能;

这只是简单说了一下动态代理的用法,如果想对动态代理有更多了解,可以看看Proxy对应newProxyInstance()方法的底层源码(本人太菜还不到研究源码的地步

代理模式———动态代理相关推荐

  1. 【设计模式】代理模式 ( 动态代理 | 模拟 Java 虚拟机生成对应的 代理对象 类 )

    文章目录 前言 一.模拟 JVM 生成对应的 代理对象 二.模拟 JVM 生成对应的 代理对象 完整流程展示 1.目标对象接口 2.被代理对象 3.调用处理程序 4.模拟 JVM 生成的代理对象类 5 ...

  2. 红橙Darren视频笔记 代理模式 动态代理和静态代理

    红橙Darren视频笔记 代理模式 动态代理和静态代理(Android API 25) 关于代理模式我之前有过相关的介绍: https://blog.csdn.net/u011109881/artic ...

  3. 【设计模式】代理模式 ( 动态代理使用流程 | 创建目标对象 | 创建被代理对象 | 创建调用处理程序 | 动态创建代理对象 | 动态代理调用 )

    文章目录 前言 一.静态代理的弊端 二.动态代理的优势 三.动态代理使用流程 1.目标对象接口 2.被代理对象 3.调用处理程序 4.客户端 四.动态生成 代理对象 类 的 字节码 文件数据 前言 代 ...

  4. Proxy 代理模式 动态代理 CGLIB

    代理的基本概念 几个英文单词: proxy [ˈprɒksi] n. 代理服务器:代表权:代理人,代替物:委托书: invoke [ɪnˈvəʊk] vt. 乞灵,祈求:提出或授引-以支持或证明:召鬼 ...

  5. Java设计模式-之代理模式(动态代理)

    一.简介 1.什么叫代理模式:        简而言之就是:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起 ...

  6. 【设计模式】代理模式 ( 动态代理 )

    文章目录 一.动态代理使用流程 二.动态代理代码示例 1.订单类 2.Service 接口 3.Service 实现类 4.Service 静态代理类 5.Dao 接口 6.Dao 实现类 7.Ser ...

  7. 代理模式(动态代理)

    动态代理.就是不用自己写代理类,只要实现接口就行了. 动态代理,这里介绍两种:jdk and cglib 第一个jdk //一个接口类 public interface Book{void read( ...

  8. Spring AOP中的静态代理和动态代理的原理和实践

    对于最近博主最近写博客的兴致大发,我也在思考:为什么而写博客?在互联网时代,无论你是牛人大咖,还是小白菜鸟,都有发表自己看法的权利.无论你是对的还是错的,都会在这个平台上找到答案.所以,我会尽可能去写 ...

  9. 【java项目实战】代理模式(Proxy Pattern),静态代理 VS 动态代理

    这篇博文,我们主要以类图和代码的形式来对照学习一下静态代理和动态代理.重点解析各自的优缺点. 定义 代理模式(Proxy Pattern)是对象的结构型模式,代理模式给某一个对象提供了一个代理对象,并 ...

  10. 【Android 插件化】Hook 插件化框架 ( Hook 技术 | 代理模式 | 静态代理 | 动态代理 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

最新文章

  1. 草根创业都选择的是什么人?
  2. 蚂蚁金服大佬自述:保持学习力,永远胜过不切实际的一夜暴富幻想!
  3. 将Eclipse代码导入到Android Studio的两种方式
  4. Windows核心编程 第23章 结束处理程序
  5. python猜密码游戏规则_【python笔记 三 】python脚本实战---数字密码小游戏
  6. grub配置文件丢失的情况下修复
  7. 移动web性能优化笔记
  8. SP和Fuction的关系
  9. 【Spring】Spring 深入理解注解及spring对注解的增强
  10. python定义一个函数列表排序_Python 列表(修改、添加、删除、排序)
  11. 3631: [JLOI2014]松鼠的新家
  12. oracle 11i 供应商api,Oracle EBS AP 供应商API
  13. python-学生管理系统--2录入学生信息内容以及代码
  14. 车联网在智慧城市中的发展与应用
  15. 人型自走输入法(网页日语输入法)
  16. NOIP2019数字游戏
  17. RK3399与MIPI DSI之间在DRM架构下的联结关系
  18. 计算出当天零分零点对应时间戳的方法分享
  19. 实物短缺下的现货白银操作建议
  20. 论文中的图片怎么修改为300dpi?图片dpi怎么调300?

热门文章

  1. 广联达软件未检测到加密锁请重新插入加密锁或网络服务器
  2. python可以代替按键精灵吗_Python 假装自己是按键精灵
  3. 短信接口防盗刷解决方案
  4. gimp中文版教程_Gimp中文经典入门实用教程(合辑).pdf
  5. Sketch项目安装缺失字体
  6. uniapp引入字体包——DIN
  7. 从0到1:打造移动端H5性能测试平台
  8. 【字体分享】设计师常用的日系中文字体
  9. 华为交换机console口登录密码遗忘
  10. 使用 ngrok(小米球)实现内网穿透映像到外网访问项目