看了这篇文章非常不错转载:https://www.jianshu.com/p/4e14dd223897

Java设计模式(14)----------动态代理原理源码分析

上篇文章《Java设计模式(13)----------代理模式》中,介绍了两种代理模式(静态代理和动态代理)的应用场景和实际应用案例。本篇文章中,对动态代理的原理做进行深入的分析。

关于动态代理,初看可能会比较费解,大概有如下几个疑问:

  • 代理是怎么形成的,代理类在哪里?
  • TimeHandler类是做什么用的,在哪里被调用了?
  • 客户端调用的时候,写的是调用m.move();,程序是如何执行到了TimeHandler对象的invoke方法中了呢?

这篇文章,主要针对如上几个疑问进行展开。

首先声明一点,案例中的TimeHandler并不是代理类,而是代理依赖的一个类而已。真正的代理类,是JDK直接生成的字节码并进行加载的,所以对用户是不可见的。

生成代理类的代码是这个:
Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
这里向newProxyInstance传递了三个参数,分别是原始类的加载器,原始类实现的接口(Moveable),TimeHandler类的对象。

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {Objects.requireNonNull(h);final Class<?> caller = System.getSecurityManager() == null? null: Reflection.getCallerClass();/** Look up or generate the designated proxy class and its constructor.*/Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);return newProxyInstance(caller, cons, h);

newProxyInstance的代码实现比较清晰,通过getProxyConstructor方法创建了代理类,并返回了该类的构造方法。之后使用反射,生成代理类的对象并返回。

private static Constructor<?> getProxyConstructor(Class<?> caller,ClassLoader loader,Class<?>... interfaces){// optimization for single interfaceif (interfaces.length == 1) {Class<?> intf = interfaces[0];if (caller != null) {checkProxyAccess(caller, loader, intf);}return proxyCache.sub(intf).computeIfAbsent(loader,(ld, clv) -> new ProxyBuilder(ld, clv.key()).build());} else {// interfaces clonedfinal Class<?>[] intfsArray = interfaces.clone();if (caller != null) {checkProxyAccess(caller, loader, intfsArray);}final List<Class<?>> intfs = Arrays.asList(intfsArray);return proxyCache.sub(intfs).computeIfAbsent(loader,(ld, clv) -> new ProxyBuilder(ld, clv.key()).build());}}

getProxyConstructor函数中,可以看到if-else分支,这里是对单接口的情况做了代码优化。我们主要关注其代理类的生成部分,就是new ProxyBuilder(ld, clv.key()).build()

(ld, clv) -> new ProxyBuilder(ld, clv.key()).build() 这种写法是lambda表达式的语法,非常精简。

        Constructor<?> build() {Class<?> proxyClass = defineProxyClass(module, interfaces);final Constructor<?> cons;try {cons = proxyClass.getConstructor(constructorParams);} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});return cons;}

在build方法中,从语意上可以看到代理类proxyClassdefineProxyClass方法中生成。之后通过反射并根据构造函数的参数,获取到代理类的构造方法并返回。

这里的参数为private static final Class<?>[] constructorParams = { InvocationHandler.class };。所以看到这里,应该明白了TimeHandler的作用了,就是作为代理类的构造方法的一个参数,也就是代理类依赖的对象。到这里,也就找到了文章开始处提出的问题二的答案。这里可以猜测一下,代理类中的对应接口的方法,应该是调用的TimeHandler类的invoke方法,通过控制invoke的参数,来调用不同的方法。

defineProxyClass方法中,最关键的代码如下:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);try {Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,0, proxyClassFile.length,loader, null);reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);return pc;} catch (ClassFormatError e) {/** A ClassFormatError here means that (barring bugs in the* proxy class generation code) there was some other* invalid aspect of the arguments supplied to the proxy* class creation (such as virtual machine limitations* exceeded).*/throw new IllegalArgumentException(e.toString());}

这里先生成字节码,之后将其加载到内存中。

    static byte[] generateProxyClass(final String name,Class<?>[] interfaces,int accessFlags){ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);final byte[] classFile = gen.generateClassFile();if (saveGeneratedFiles) {java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {public Void run() {try {int i = name.lastIndexOf('.');Path path;if (i > 0) {Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));Files.createDirectories(dir);path = dir.resolve(name.substring(i+1, name.length()) + ".class");} else {path = Paths.get(name + ".class");}Files.write(path, classFile);return null;} catch (IOException e) {throw new InternalError("I/O exception saving generated file: " + e);}}});}return classFile;}

generateProxyClass方法中,先生成了ProxyGenerator对象,然后调用对象的generateClassFile生成字节码。可以看到其中有个saveGeneratedFiles的标志位,表示是否需要保存生成的字节码文件。在generateClassFile方法中,创建了ProxyMethod,字段表和方法表集合,并将内容按照字节码的规范写入到流中。至此代理类就生成了,文章开始处的问题一就回答完毕了

此处代码中可以根据语意看到会使用InvocationHandler的方法,目前对java的class文件的格式还不够了解,有机会研究一下。

刚刚说到代码中是存在打印生成的代理类的逻辑的,就是saveGeneratedFiles标志位,在该变量定义处可以看到:

private static final boolean saveGeneratedFiles =java.security.AccessController.doPrivileged(new GetBooleanAction("jdk.proxy.ProxyGenerator.saveGeneratedFiles")).booleanValue();

表示该变量是由jdk.proxy.ProxyGenerator.saveGeneratedFiles控制的。所以我们在代码中只需要指定这个值为true就可以了,代码如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;public class Test {public static void main(String[] args) throws Exception{System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");Car car = new Car();Class<?> cls = car.getClass();InvocationHandler h = new TimeHandler(car);Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);m.move();System.out.println("");m.move_back();System.out.println("");System.out.println("");}
}

运行之后,就可以在工程目录下找到对应的代理类的class文件啦

使用idea打开,可以看到对应的java代码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package com.sun.proxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements Moveable {private static Method m1;private static Method m3;private static Method m2;private static Method m4;private static Method m0;public $Proxy0(InvocationHandler var1) throws  {super(var1);}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 void move() throws Exception {try {super.h.invoke(this, m3, (Object[])null);} catch (Exception | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}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 void move_back() throws Exception {try {super.h.invoke(this, m4, (Object[])null);} catch (Exception | 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"));m3 = Class.forName("Moveable").getMethod("move");m2 = Class.forName("java.lang.Object").getMethod("toString");m4 = Class.forName("Moveable").getMethod("move_back");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

代理类解读:

  • 代理类的构造方法,接收一个InvocationHandler对象,调用父类Proxy的初始化方法,使用该对象为依赖h的对象赋值。
  • 以move方法为例,我们可以看到,代理类实现了Moveable接口,在move方法的实现中,调用了InvocationHandler对象的invoke方法,并且其中第二个参数为m3对象。而m3对象是静态块中通过反射获取到的Moveable接口的move方法。所以,此处就回答的文章开始处的问题三。

到这里,动态代理的原理就非常清晰了,代理类是jdk直接以字节码的形式生成出来的,继承Proxy类,实现客户端定义的接口。因为Proxy依赖InvocationHandler实现类,所以代理类也同样依赖。对于接口中定义的函数move,代理类中也会实现,其逻辑是调用InvocationHandler类中的invoke方法,并将方法move方法作为invoke的一个参数。在invoke方法中,可以进行功能扩展,进而执行invoke参数中的方法,完成对原始对象的move方法的调用。不得不佩服java源码的作者们的深厚功力,能够设计出如此高扩展的结构,自己需要学习和实践的还有很多。

动态代理原理源码分析相关推荐

  1. Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用示例

    Linux内核 eBPF基础 kprobe原理源码分析:基本介绍与使用示例 荣涛 2021年5月11日 kprobe调试技术是为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术. 利用kpro ...

  2. Linux内核 eBPF基础:kprobe原理源码分析:源码分析

    Linux内核 eBPF基础 kprobe原理源码分析:源码分析 荣涛 2021年5月11日 在 <Linux内核 eBPF基础:kprobe原理源码分析:基本介绍与使用>中已经介绍了kp ...

  3. Linux内核 eBPF基础:Tracepoint原理源码分析

    Linux内核 eBPF基础 Tracepoint原理源码分析 荣涛 2021年5月10日 1. 基本原理 需要注意的几点: 本文将从sched_switch相关的tracepoint展开: 关于st ...

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

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

  5. Java Review - 并发编程_ThreadLocalRandom实现原理源码分析

    文章目录 概述 Random的局限性 ThreadLocalRandom使用及原理 使用 原理 ThreadLocalRandom源码分析 ThreadLocalRandom current() 该方 ...

  6. Nacos、Eureak、Feign原理源码分析

    一.AP.CP架构,脑裂问题 分区容错性 在同等的容错性下,奇数个机器要比偶数个机器更节省资源. 因为按照防止脑裂的设置,节点投票数量必须要 > 总结点数 / 2. 那么5 / 2 = 2, 6 ...

  7. (十五)Zookeeper原理源码分析之数据日志文件归档

    文章目录 1. 日志文件概述 1.1 作用说明 1.2 日志文件格式 1.2.1 事务日志文件解析 1.2.2 快照日志文件解析 2. 日志文件序列化 2.1 日志路径配置 2.2 序列化时机 2.3 ...

  8. EventBus原理源码分析和使用

    前段时间被别人问到关于EventBus的原理,但我确实使用过,用起来也方便简单,一个注册,一个Post就能通知到所有的订阅者Subcriber,其实明明是知道一点点原理的,但是当时就是支支吾吾没有说, ...

  9. android 动画原理源码分析之Animation

    在开发移动应用程序的时候用到动画是家常便饭的事,但是你有没有想过它是怎么实现的呢?今天小弟就在此分析一下. 1 startAnimation 方法. 设置好animation变量,刷新父视图绘画缓存. ...

最新文章

  1. hdu4685 最大匹配可能性
  2. .NET精品文章系列(二)
  3. python vscode_VScode 配置为Python编辑器
  4. iOS 11.4.1 正式版越狱
  5. 文献学习(part28)--Biclustering of gene expression data based on related genes and conditions extraction
  6. java private 命名_java private关键字用法实例
  7. 构建Electron的常见问题(Mac)
  8. mysql 重启爆红,MySQL 启动报错
  9. php实现防止sql注入的通用方法,PHP简单实现防止SQL注入的方法
  10. Zotero | zotero与endnote题录与pdf文件的完整互转(2)
  11. 拼多多电商玩家如何利用软件机器人快速采集平台数据
  12. js实现手机摇一摇功能
  13. 05 Python基础
  14. office安装错误“错误25004,您输入的产品密钥无法在此计算机上使用,-----------”
  15. OpenKE 的使用(四)— HolE 和 ComplEx 论文复现
  16. 小白记录——识别RNA编辑位点
  17. PA1--实现基础设施、表达式求值和监视点
  18. 基于HTML旅游网站项目的设计与实现——千岛湖旅游景点网站模板(6个页面)HTML+CSS+JavaScript
  19. 专题8:dfs和bfs
  20. STM32单片机基于HAL库开发HC-SR04 超声波测距模块(终极版)

热门文章

  1. 【OpenCV 例程200篇】07. 图像的创建(np.zeros)
  2. .net core orm框架_轻量级高性能PHP框架ycroute
  3. hbuild json红叉_MUI+Hbuilder之踩坑(三)
  4. firefox flash插件_巧用firefox下载视频资源
  5. express+handlebars 快速搭建网站前后台
  6. 轻量级ORM框架 【Dapper】 的使用
  7. discuz x2.5 DIY模块模板语法详解
  8. 连通图遍历策略之广度优先搜索(C语言)
  9. Openssl更新步骤
  10. Hive内部表与外部表区别详细介绍