java动态代理基本原理及proxy源码分析一
本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的态度,于是对java动态代理的本质原理做了一些研究,于是便有了这个系列的文章
为了尽快进入正题,这里先跳过spring aop和java动态代理的使用流程的讲解,这部分内容后面再单独写文章整理
不过,我们首先还是先看下java dynamic proxy的基本使用方法,假定我们要代理的对象是一个Map,则代码如下:
Map proxyInstance = (Map) Proxy.newProxyInstance(
HashMap.class.getClassLoader(),
new Class[]{Map.class},
new DynamicInvocationHandler());
之后proxyInstance就可以作为一个正常的Map对象进行使用了
为了对生成对象的属性做一个基本的了解,我们先打印一下proxyInstance的实际类型名称
System.out.println(proxyInstance.getClass().getName());
得到结果
com.sun.proxy.$Proxy11
如果使用多了,就会发现所有的代理类的名称都是$Proxy加一个数字,且包名是com.sun.proxy
当我们查看Proxy.newProxyInstance方法时,会发现它返回的其实是一个Object对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
而在实际使用的过程中,它是可以被直接转型成我们传入的接口类型,因此可以推测出,该proxyInstance对象的实际类型肯定是实现了我们传入的接口
我们打印一下该类实现的接口
for (Class intf : proxyInstance.getClass().getInterfaces()) {
System.out.println(intf.getName());
}
得到结果
java.util.Map
符合我们之前的推测
接着我们再打印一下该类的父类
System.out.println(proxyInstance.getClass().getSuperclass().getName());
得到结果
java.lang.reflect.Proxy
因此总结一下,该proxyInstance对象有以下3个属性
1.继承了Proxy类
2.实现了我们传入的接口
3.以$Proxy+随机数字的命名
那么动态生成代理类的功能究竟是如何实现的呢?接下去就来看java的源码
因为源码有点多,所以我只贴出关键的部分
入口自然是Proxy.newProxyInstance方法
其中有2个部分我们需要关心
第一部分,类的创建
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
这个就是实际生成类的方法,后面我们会继续深究,先略放一放
第二部分,实例的创建
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
...
return cons.newInstance(new Object[]{h});
最终对象的实例化过程就是通过之前生成的class,获取其指定参数的构造函数,并将InvocationHandler对象传入
查看constructorParams字段
/** parameter types of a proxy class constructor */
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
的确就是获取InvocationHandler对象的一个构造函数
回想一下之前类定义的第一条,继承了Proxy类,因此我们去Proxy类中找一下
/**
* Constructs a new {@code Proxy} instance from a subclass
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
* @param h the invocation handler for this proxy instance
*
* @throws NullPointerException if the given invocation handler, {@code h},
* is {@code null}.
*/
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
在该构造函数中就是将参数h赋值给了成员变量h,这里名称h可以记一下,在之后的文章中还会遇到
看完实例的创建,让我们回到更重要的第一部分,类的生成
进入getProxyClass0(loader, intfs)方法
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
} // If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
该方法很简单,直接从一个cache中拿取对象
查看proxyClassCache对象
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
该对象本质就是一个类似于Map的缓存,不过使用的是WeakCache,这个WeakCache本身的特性我们放到另一篇文章中讨论,本文专注于Proxy
我们可以看到该缓存的构造函数获取了2个Factory,顾名思义,第一个是生成key的,第二个是生成ProxyClass的,自然我们需要继续看第二个Factory
类的注解如下
/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
这个就是我们要寻找的负责具体生成类的工厂了,查看其apply方法
首先其会对传入的接口类型做一些校验,包括loader能否加载到传入的接口,接口是否实际上是接口(因为数组的类型是Class),接口是否有重复
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
接着设置类的默认access_flag,public final
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
接着检查传入的接口数组中是否包含非public的接口,如果有,则生成的类需要和该接口处于同一个package,且访问属性会去掉public,只保留final。如果有多个不同package中的非public接口,则报错
(具体原因大家应该都可以理解)
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
如果没有非public类,则会使用默认的package名,即com.sun.proxy
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
然后获取一个静态自增的int
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
固定的类名前缀
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
将上面三者组合成最终的类名(回想之前我们打印出的实例的类名)
String proxyName = proxyPkg + proxyClassNamePrefix + num;
上面这几个步骤确定了类的名称,但还是皮毛,接下去是生成类的血肉:字节码
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
具体的探究也先放一下,先看字节码转换成具体类的方法
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} 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());
}
而该方法是一个native的方法,所以暂时就无法继续探究了,不过知道了这个方法后,如果我们自己有需要,也可以利用这种机制实现自己的动态类生成,后面会想办法做一个demo,本文就不做探讨了
private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);
之前其实都是开胃菜,现在回到之前生成字节码的方法,查看方法源码
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
} Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
} return var4;
}
中间if部分的代码可以先忽略,不过我们会在后面的文章中使用到这部分功能,这里先关注下面这2行代码
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
这里让我们记一下
var0是类名
var1是接口
var3是access_flag
后面我会尽量将这些varX转换成更实际的命名,方便大家理解
之后就是本文的最终的重点,也是难点,即二进制字节码的实际生成过程,包括jvm操作指令,所以我们需要先对class文件的结构和jvm操作指令有一个了解,见下篇文章
总结而言:java动态代理的基本原理就是在运行时生成字节码,并通过一个native方法将其转换成Class对象供我们使用
java动态代理基本原理及proxy源码分析一的更多相关文章
- MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析
我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样 <?xml version="1.0" encoding=& ...
- JAVA设计模式-动态代理(Proxy)源码分析
在文章:JAVA设计模式-动态代理(Proxy)示例及说明中,为动态代理设计模式举了一个小小的例子,那么这篇文章就来分析一下源码的实现. 一,Proxy.newProxyInstance方法 @Cal ...
- java 动态代理深度学习(Proxy,InvocationHandler),含$Proxy0源码
java 动态代理深度学习, 一.相关类及其方法: java.lang.reflect.Proxy,Proxy 提供用于创建动态代理类和实例的静态方法.newProxyInstance()返回一个指定 ...
- java动态代理——jvm指令集基本概念和方法字节码结构的进一步探究及proxy源码分析四
前文地址 https://www.cnblogs.com/tera/p/13336627.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...
- java动态代理——代理方法的假设和验证及Proxy源码分析五
前文地址 https://www.cnblogs.com/tera/p/13419025.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...
- java动态代理——字段和方法字节码的基础结构及Proxy源码分析三
前文地址:https://www.cnblogs.com/tera/p/13280547.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...
- 《Java Spring框架》Spring IOC 源码分析
1.下载源码 源码部署:https://www.cnblogs.com/jssj/p/11631881.html 并不强求,最好是有源码(方便理解和查问题). 2. 创建子项目 Spring项目中创建 ...
- class文件的基本结构及proxy源码分析二
前文地址:https://www.cnblogs.com/tera/p/13267630.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...
- Java入门系列之集合LinkedList源码分析(九)
前言 上一节我们手写实现了单链表和双链表,本节我们来看看源码是如何实现的并且对比手动实现有哪些可优化的地方. LinkedList源码分析 通过上一节我们对双链表原理的讲解,同时我们对照如下图也可知道 ...
随机推荐
- 如何快速的找到好玩的旅游景点信息?Python爬虫帮你轻松解决
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 喜欢的朋友欢迎关注小编 当我们出去旅游时,会看这个地方有哪些旅游景点,景点 ...
- 【loj - 6516】「雅礼集训 2018 Day11」进攻!
目录 description solution accepted code details description 你将向敌方发起进攻!敌方的防御阵地可以用一个 \(N\times M\) 的 \(0 ...
- TensorFlow从0到1之TensorFlow实现反向传播算法(21)
反向传播(BPN)算法是神经网络中研究最多.使用最多的算法之一,它用于将输出层中的误差传播到隐藏层的神经元,然后用于更新权重. 学习 BPN 算法可以分成以下两个过程: 正向传播:输入被馈送到网络,信 ...
- cookie与session区别?
参考 cookie与session的区别是什么 Java中Cookie的使用(Cookie 和Session的区别) 什么是会话? 用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭 ...
- cb19a_c++_只适合string类型的操作_提取_追加_替换
*cb19a_c++_只适合string类型的操作_提取_追加_替换三个substr重载函数-获取一个字符串的一部分六个append重载函数-追加字符十个replace重载函数-替换更换 重载函数越多 ...
- Redis删除策略和逐出策略
本文知识点 过期数据概念 数据删除策略 逐出算法 过期数据 先来看三个key值,分别为sex.name.age. 这三个值设置的指令为 set name kaka setex age 100 24 s ...
- Andrew Ng - 深度学习工程师 - Part 1. 神经网络和深度学习(Week 4. 深层神经网络)
=================第2周 神经网络基础=============== ===4.1 深层神经网络=== Although for any given problem it migh ...
- 05 . Prometheus监控Nginx
List CentOS7.3 prometheus-2.2.1.linux-amd64.tar.gz nginx-module-vts 节点名 IP 软件版本 硬件 网络 说明 Prometheus ...
- 一个非侵入的Go事务管理库——工作原理
在上一篇文章"一个非侵入的Go事务管理库--如何使用"中,我讲述了如何使用事务库.有些读者可能读过"清晰架构(Clean Architecture)的Go微服务: 事物管 ...
- 旷世提出类别正则化的域自适应目标检测模型,缓解场景多样的痛点 | CVPR 2020
论文基于DA Faster R-CNN系列提出类别正则化框架,充分利用多标签分类的弱定位能力以及图片级预测和实例级预测的类一致性,从实验结果来看,类该方法能够很好地提升DA Faster R-CNN系 ...