1. 动态代理相关概念

  • 目标类:程序员自己写的、普通的业务类,是需要被代理的类;
  • 目标方法:目标类中的实现业务的具体方法,为了精简,只写核心业务代码,因此需要代理类来增强功能;
  • 增强器:是给目标方法增强功能的类,需要继承InvocationHandler接口,实现invoke(Object proxy, Method method, Object[] args)方法;
  • 代理类:由JDK相关类在程序执行过程中、为了给目标类的相关目标方法增强功能而生成的、存在于内存中的类;
  • 动态代理:在程序执行过程中,由JDK相关类来创建代理对象,通过代理对象来执行相关方法,在不改变目标类的业务代码的前提下,达到给目标类相关方法增强功能的目的,使得程序的可扩展性大大增强;

2. 动态代理实现方式

  • jdk动态代理:使用jdk的Proxy、InvocationHandler来创建代理对象,

    • 原理:通过实现目标类的父接口,重写目标类的相关方法
    • 要求:目标类必须实现接口
  • cglib动态代理:第三方的工具库,创建动态代理,
    • 原理:继承,通过继承目标类,创建具有增强功能的子类,
    • 要求:目标类不能是final类型,目标增强方法不能是final

3. JDK动态代理实例演示

  • 我现在要科普一下水的作用,核心类是Water,水可以饮用、清洗;
public interface Drinkable{void drink();
}
public interface Washable{void wash();
}
  • 在核心类Water中,我们只说核心功能:
public class Water implements Drinkable, Washable{@Overridepublic void drink(){System.out.println("喝水能够解渴");}@Overridepublic void wash(){System.out.println("水能用来清洗衣服");}
}
  • 但是科普讲解还需要把事情的来龙去脉讲清楚,增加的讲解放在增强器中进行:
public class DrinkHandler implements InvocationHandler{private Object target = null;//目标对象,通过构造器传入public DrinkHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{System.out.println("身体内产生能量的化学反应要消耗水...");method.invoke(target, args);System.out.println("身体产生的废物会溶解在水中排出体外...");return null;//目标方法没有返回值的话,就返回null}
}
public class WashHandler implements InvocationHandler{private Object target = null;public WashHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{System.out.println("衣服穿过就脏了...");method.invoke(target, args);System.out.println("洗过的衣服需要在太阳下晾晒才会干的...");return null;}
}
  • 要生成代理对象需要的步骤:

    1> 创建目标对象
    2> 创建增强器
    3> 根据JDK相关类来创建代理对象(底层机制是反射)
    4> 调用代理对象的方法,实现功能增强

public class WaterScience{public static void main(String[] args){//1.创建目标对象Water water = new Water();//2.创建增强器DrinkHandler drinkHandler = new DrinkHandler(water);WashHandler washHandler = new WashHandler(water);//3.根据JDK相关类来创建代理对象(底层机制是反射)、Drinkable drinkProxy = (Drinkable) Proxy.newProxyInstance(water.getClass().getClassLoader(),water.getClass().getInterfaces(),drinkHandler);Washable washProxy = (Washable) Proxy.newProxyInstance(water.getClass().getClassLoader(),water.getClass().getInterfaces(),washHandler);//4.调用代理对象的方法,实现功能增强drinkProxy.drink();System.out.println("========================");washProxy.wash();}
}
//以下是输出结果:
身体内产生能量的化学反应要消耗水...
喝水能够解渴
身体产生的废物会溶解在水中排出体外...
========================
衣服穿过就脏了...
水能用来清洗衣服
洗过的衣服需要在太阳下晾晒才会干的...

4. 源码解析

1> 创建目标对象
2> 创建增强器
3> 根据JDK相关类来创建代理对象(底层机制是反射)
4> 调用代理对象的方法,实现功能增强

  • 四个步骤中,有难点的是2、3两步
  • 增强器要实现InvocationHandler必须实现的invoke方法:
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{return null;}
  • 创建代理对象的JDK相关API:
Proxy.newProxyInstance(water.getClass().getClassLoader(),water.getClass().getInterfaces(),drinkHandler);
  1. 先进入Proxy.newProxyInstance方法(只保留核心语句):
    @CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{//记住此处参数列表是目标类的类加载器、目标类的父接口、增强器对象Class<?> cl = getProxyClass0(loader, intfs);//此方法,能够得到代理对象的类对象,是个核心方法,进入该方法
  1. 进入getProxyClass0(loader, intfs)方法:
    private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {return proxyClassCache.get(loader, interfaces);}
  1. 进入proxyClassCache.get(loader, interfaces)方法(只保留最核心语句):
  public V get(K key, P parameter) {// create subKey and retrieve the possible Supplier<V> stored by that subKey from valuesMapObject subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));//此处仍然没有到最底层,记住key是目标类的类加载器,parameter数组内存放目标类的所有父接口
  1. 进入subKeyFactory.apply(key, parameter)方法(注意此处subKeyFactory是BiFunction接口类型的,要到它的实现类ProxyClassFactory中寻找核心方法apply):
 //这个是核心方法,涉及【代理类名称】、【代理类字节码文件】、【代理类对象的生成】private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>{// 所有代理类名的前缀private static final String proxyClassNamePrefix = "$Proxy";@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {int accessFlags = Modifier.PUBLIC | Modifier.FINAL;long num = nextUniqueNumber.getAndIncrement();/*【代理类名称】拼接:这就是为什么代理类后面会有数字*/String proxyName = proxyPkg + proxyClassNamePrefix + num;/*【代理类字节码文件】:这个字节码以数组的形式存在于内存,没在磁盘盘上,后续我们可以把这个字节数组写入磁盘,以便直观查看代理类的字节码文件*/byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {/*【代理类对象的生成】:这是一个native方法,得到代理类的类对象,就是类加载中把磁盘的字节码读入方法区的那一步*/return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {throw new IllegalArgumentException(e.toString());}}}
  1. 为了直观感受到代理类,我们把代理类的字节码文件输出到磁盘上:
//把上文中:JDK动态代理实例中的代理类字节码文件输出到磁盘上:public static void createProxyClass() throws IOException{String  proxyName = "$proxy0";/*生成指定的代理类字节码:*/Class<?>[] interfaces = Water.class.getInterfaces();byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);/*指定字节码存放路径*/String path = "E:\\IDEA_2020_2_4\\IdeaProjects\\springLearn\\aop-leadin\\src\\com\\cpf\\proxyClass\\";File file = new File(path + proxyName + ".class");if(!file.exists()){file.createNewFile();}FileOutputStream fileOutputStream = new FileOutputStream(file);fileOutputStream.write(proxyClassFile);fileOutputStream.flush();fileOutputStream.close();}
  1. 我们来解析一下这个代理类字节码:
import com.cpf.service.Drinkable;
import com.cpf.service.Washable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/*代理类继承了Proxy,唯一的用处是复用了Proxy中的InvocationHandler增强器对象,
但其实完全可以在代理类中出现增强器对象,这就是cglib的思路*/
/*代理类实现了目标类的所有父接口,这就是为什么要求目标类一定要有接口,因为代理类已经有父类了,只能实现接口*/
public final class $proxy0 extends Proxy implements Drinkable, Washable {private static Method m1;private static Method m2;private static Method m3;private static Method m4;private static Method m0;
/*代理类的构造器,传入增强器对象,这在Proxy.newProxyInstance()方法的后续代码中会用到,
你或许已经发现,创建代理类的类对象只使用了【目标类加载器】【目标类父接口】这两个参数,
第三个参数【增强器】就是通过这个构造器赋值给Proxy的属性h的,由三个参数来形成代理对象*/public $proxy0(InvocationHandler var1) throws  {super(var1);}
/*这就是目标类中需要增强的两个目标方法drink wash,是从两个父接口中得到的信息;
由动态绑定机制,我们在用代理对象调用方法时,实际上会执行这里的drink方法,而不是目标类的drink方法*/public final void drink() throws  {try {/*super就是Proxy,h就是那第三个参数【增强器对象】,invoke就是我们在增强器中写的方法;第一个参数this,表明invoke的第一个参数就是代理对象第二个参数m3,查看后面的静态代码块可知,就是通过反射获得的drink的方法对象第三个参数null,这是因为drink()方法没有参数*/super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void wash() throws  {try {super.h.invoke(this, m4, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}
/*系统顺手帮忙生成了类中最常用的三个方法equals toString hasnCode*/public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}
/**静态代码块,使用反射,获得目标类的目标方法,这些信息,是通过第一个参数【目标类加载器】获得的/static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("com.cpf.service.Drinkable").getMethod("drink");m4 = Class.forName("com.cpf.service.Washable").getMethod("wash");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}
  1. 至此,我们就搞清楚了代理类的类对象的来源,也弄明白了代理类的基本结构,
    接下来继续回到第一步中newProxyInstance()的核心代码,继续分析:
    @CallerSensitive//记住这个Proxy类中的属性,他就是增强器的类对象,用来获得代理类的构造器对象private static final Class<?>[] constructorParams = { InvocationHandler.class };public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{Class<?> cl = getProxyClass0(loader, intfs);//通过代理类的类对象得到构造器对象,就是那个需要传入增强器h的构造器final Constructor<?> cons = cl.getConstructor(constructorParams);//由于Constructor类的newInstance方法时可变长参数列表,因此此处把增强器对象伪装进一个数组中,//实际上就是把这个增强器对象传入代理类的构造器中来构造器代理对象...return cons.newInstance(new Object[]{h});}
  1. 至此,我们理解了【代理类的字节码】【代理类对象的由来】【代理类与invoke的互动】,接下来解析一下增强器中的invoke()方法:
public class DrinkHandler implements InvocationHandler{private Object target = null;//目标对象,通过构造器传入/*目标对象必须传入增强器,因为目标方法还必须由目标对象来执行,增强器只负责增强的功能*/public DrinkHandler(Object target){this.target = target;}/*** @param proxy 回忆一下代理类字节码文件中代码: super.h.invoke(this, m3, (Object[])null);*              这个super就是Proxy类,h就是它的InvocationHandler属性,*              this就是代理类对象,所以此处的名字就是proxy,实至名归.* @param method 代理类字节码中静态代码块:m3 = Class.forName("com.cpf.service.Drinkable").getMethod("drink");*               可以知道这个就是反射得到的方法对象.   * @param args 由于drink()方法没有参数列表,因此字节码文件中是null,args代表了目标方法的参数类对象,*             注意是参数类对象,不是参数对象,如果原参数是一个String,此处就应该是String.class   * @return 由于目标方法可能有返回值,此处有必要设置一个返回值,如此例中drink没有返回值,invoke就返回null.* @throws Throwable 目标方法的执行是通过反射,反射有可能找不到对应的方法,因此要抛出异常.*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{System.out.println("身体内产生能量的化学反应要消耗水...");method.invoke(target, args);System.out.println("身体产生的废物会溶解在水中排出体外...");return null;//目标方法没有返回值的话,就返回null}
}
  • 至此,动态代理底层分析完毕

5. 结语

  • 动态代理是一种技术,更是一个思想,在不修改业务层代码的前提下,增强业务层的功能,增强器改名换姓,摇身一变就形成了面向切面编程;
  • 由于动态代理的代码写法很多,步骤也可以有变动,为了规范动态代理,就形成了aop(面向切面编程)技术,可以这样说:aop是动态代理的规范
  • spring本身实现了aop技术,但是非常的冗余和繁琐,业内公认的aop实现框架是aspectJ,spring也支持aspectJ框架,只是需要另外引入aspectJ的依赖;

JDK动态代理底层源码剖析相关推荐

  1. Jdk动态代理 底层源码分析

    前言 java动态代理主要有2种,Jdk动态代理.Cglib动态代理,本文主要讲解Jdk动态代理的使用.运行机制.以及源码分析.当spring没有手动开启Cglib动态代理,即:<aop:asp ...

  2. JDK 动态代理之源码解析

    过几天我会分享spring AOP 的相关的代理的源码,为了让大家学好springAOP ,今天先分析jdk 的动态代理 1.首先创建一个接口和一个被代理的对象: package com.nandao ...

  3. 【Unity主程手记(摘录)】第一章(二) - Dictory 底层源码剖析

    第一章(二) - Dictory 底层源码剖析 提示:个人学习总结,如有错误,敬请指正. 文章目录 第一章(二) - Dictory 底层源码剖析 一.Dictory 1.底层数据结构 2.Add - ...

  4. JDK动态代理底层剖析

    动态代理剖析 JDK动态代理是在内存中生成Java字节码文件(.class文件) 通过类的加载器加载class文件,生成Class对象 再通过Java的反射技术生成实例对象,提供服务.

  5. C#要点技术(二) - Dictionary 底层源码剖析

    Dictionary 底层代码 我们知道 Dictionary 字典型数据结构,是以关键字Key 和 值Value 进行一一映射的.Key的类型并没有做任何的限制,可以是整数,也可以是的字符串,甚至可 ...

  6. springboot拦截器拦截提示_Springboot拦截器使用及其底层源码剖析

    博主最近看了一下公司刚刚开发的微服务,准备入手从基本的过滤器以及拦截器开始剖析,以及在帮同学们分析一下上次的jetty过滤器源码与本次Springboot中tomcat中过滤器的区别.正题开始,拦截器 ...

  7. tomcat 责任链设计模式 底层源码剖析

    今天晚上花了些时间debug了下tomcat,注意观察了下tomcat内部过滤器的实现,其实tomcat内部过滤器采用了责任链的设计模式, (其实struts2拦截器那一块采用了相似的设计模式),以下 ...

  8. JDK动态代理实现原理详解(源码分析)

    无论是静态代理,还是Cglib动态代理,都比较容易理解,本文就通过进入源码的方式来看看JDK动态代理的实现原理进行分析 要了解动态代理的可以参考另一篇文章,有详细介绍,这里仅仅对JDK动态代理做源码分 ...

  9. Spring : Spring Aop JDK动态代理调用过程

    1.美图 2.概述 JDK动态代理参考 : JDK动态代理 3.源码 打开JdkDynamicAopProxy类,查看invoke方法: /*** Implementation of {@code I ...

最新文章

  1. linux字符设备文件的打开操作,Linux字符设备驱动模型之字符设备初始化
  2. Qt中 QString 和int,double等的转换
  3. 《Effective C#》读书笔记——条目19:保证0为值类型的有效状态.NET资源管理
  4. 一定要多角度看事物 | 今日最佳
  5. Windows 7硬盘安装方法大全
  6. 一道腾讯的专业面试题
  7. libpcap 编程入门资源
  8. python经纬度获取县名_利用 Python 批量获取县镇运输距离
  9. 前端处理方式:特殊格式时间转换(2020-11-27T02:58:41.000000Z)
  10. 小米mix2s html,【小米MIX2s评测】2018需要加价买的旗舰 小米MIX 2S评测_小米 MIX 2s(6GB RAM/全网通)_手机评测-中关村在线...
  11. 驱动,包括很多软件,并不是最新的就是最好的
  12. Tricks(十八)—— 转置 list of lists
  13. JQueryDOM之插入节点
  14. Java责任链模式及异步责任链
  15. TLC5615 产生频率可变的正弦波
  16. hbase报错: ERROR: Can‘t get master address from ZooKeeper; znode data == null
  17. 成功鲜有偶然:一览IT名人的教育成长经历
  18. Echarts折线图的平移假动画
  19. AI人工智能可以做哪些课题的毕业设计毕设
  20. c语言例题功能作用,一篇C语言面试题的汇总

热门文章

  1. 幻灯片插件-jquery.sliderPro.min.js
  2. fastjson远程代码执行漏洞问题分析
  3. JavaWeb基于老杜课程笔记的完善
  4. 对element-upload二次封装文件图片上传
  5. 「ML 实践篇」模型训练
  6. Codeforces Beta Round #7
  7. 51-20210316华为海思Hi3516DV300的linux系统编译1(SPI模式)
  8. android撕衣服应用介绍,Android开发基础面试题
  9. Redis学习笔记(十八) 集群(下)
  10. 在Windows安装Reids 详解