JDK动态代理[2]----JDK动态代理的底层实现之Proxy源码分析

在上一篇里为大家简单介绍了什么是代理模式?为什么要使用代理模式?并用例子演示了一下静态代理和动态代理的实现,分析了静态代理和动态代理各自的优缺点。在这一篇中笔者打算深入源码为大家剖析JDK动态代理实现的机制,建议读者阅读本篇前可先阅读一下笔者上一篇关于代理模式的介绍《JDK动态代理[1]----代理模式实现方式的概要介绍》

上一篇动态代理的测试类中使用了Proxy类的静态方法newProxyInstance方法去生成一个代理类,这个静态方法接收三个参数,分别是目标类的类加载器,目标类实现的接口集合,InvocationHandler实例,最后返回一个Object类型的代理类。我们先从该方法开始,看看代理类是怎样一步一步造出来的,废话不多说,直接上代码

newProxyInstance方法:

 1 public static Object newProxyInstance(ClassLoader loader,
 2                                       Class<?>[] interfaces,
 3                                       InvocationHandler h) throws IllegalArgumentException {
 4     //验证传入的InvocationHandler不能为空
 5     Objects.requireNonNull(h);
 6     //复制代理类实现的所有接口
 7     final Class<?>[] intfs = interfaces.clone();
 8     //获取安全管理器
 9     final SecurityManager sm = System.getSecurityManager();
10     //进行一些权限检验
11     if (sm != null) {
12         checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
13     }
14     //该方法先从缓存获取代理类, 如果没有再去生成一个代理类
15     Class<?> cl = getProxyClass0(loader, intfs);
16     try {
17         //进行一些权限检验
18         if (sm != null) {
19             checkNewProxyPermission(Reflection.getCallerClass(), cl);
20         }
21         //获取参数类型是InvocationHandler.class的代理类构造器
22         final Constructor<?> cons = cl.getConstructor(constructorParams);
23         final InvocationHandler ih = h;
24         //如果代理类是不可访问的, 就使用特权将它的构造器设置为可访问
25         if (!Modifier.isPublic(cl.getModifiers())) {
26             AccessController.doPrivileged(new PrivilegedAction<Void>() {
27                 public Void run() {
28                     cons.setAccessible(true);
29                     return null;
30                 }
31             });
32         }
33         //传入InvocationHandler实例去构造一个代理类的实例
34         //所有代理类都继承自Proxy, 因此这里会调用Proxy的构造器将InvocationHandler引用传入
35         return cons.newInstance(new Object[]{h});
36     } catch (Exception e) {
37         //为了节省篇幅, 笔者统一用Exception捕获了所有异常
38         throw new InternalError(e.toString(), e);
39     }
40 }

可以看到,newProxyInstance方法首先是对参数进行一些权限校验,之后通过调用getProxyClass0方法生成了代理类的类对象,然后获取参数类型是InvocationHandler.class的代理类构造器。检验构造器是否可以访问,最后传入InvocationHandler实例的引用去构造出一个代理类实例,InvocationHandler实例的引用其实是Proxy持有着,因为生成的代理类默认继承自Proxy,所以最后会调用Proxy的构造器将引用传入。在这里我们重点关注getProxyClass0这个方法,看看代理类的Class对象是怎样来的,下面贴上该方法的代码

getProxyClass0方法:

1 private static Class<?> getProxyClass0(ClassLoader loader,
2                                        Class<?>... interfaces) {
3     //目标类实现的接口不能大于65535
4     if (interfaces.length > 65535) {
5         throw new IllegalArgumentException("interface limit exceeded");
6     }
7     //获取代理类使用了缓存机制
8     return proxyClassCache.get(loader, interfaces);
9 }

可以看到getProxyClass0方法内部没有多少内容,首先是检查目标代理类实现的接口不能大于65535这个数,之后是通过类加载器和接口集合去缓存里面获取,如果能找到代理类就直接返回,否则就会调用ProxyClassFactory这个工厂去生成一个代理类。关于这里使用到的缓存机制我们留到下一篇专门介绍,首先我们先看看这个工厂类是怎样生成代理类的。

ProxyClassFactory工厂类:

 1 //代理类生成工厂
 2 private static final class ProxyClassFactory
 3                 implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
 4     //代理类名称前缀
 5     private static final String proxyClassNamePrefix = "$Proxy";
 6     //用原子类来生成代理类的序号, 以此来确定唯一的代理类
 7     private static final AtomicLong nextUniqueNumber = new AtomicLong();
 8     @Override
 9     public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
10         Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
11         for (Class<?> intf : interfaces) {
12             //这里遍历interfaces数组进行验证, 主要做三件事情
13             //1.intf是否可以由指定的类加载进行加载
14             //2.intf是否是一个接口
15             //3.intf在数组中是否有重复
16         }
17         //生成代理类的包名
18         String proxyPkg = null;
19         //生成代理类的访问标志, 默认是public final的
20         int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
21         for (Class<?> intf : interfaces) {
22             //获取接口的访问标志
23             int flags = intf.getModifiers();
24             //如果接口的访问标志不是public, 那么生成代理类的包名和接口包名相同
25             if (!Modifier.isPublic(flags)) {
26                 //生成的代理类的访问标志设置为final
27                 accessFlags = Modifier.FINAL;
28                 //获取接口全限定名, 例如:java.util.Collection
29                 String name = intf.getName();
30                 int n = name.lastIndexOf(‘.‘);
31                 //剪裁后得到包名:java.util
32                 String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
33                 //生成的代理类的包名和接口包名是一样的
34                 if (proxyPkg == null) {
35                     proxyPkg = pkg;
36                 } else if (!pkg.equals(proxyPkg)) {
37                     //代理类如果实现不同包的接口, 并且接口都不是public的, 那么就会在这里报错
38                     throw new IllegalArgumentException(
39                         "non-public interfaces from different packages");
40                 }
41             }
42         }
43         //如果接口访问标志都是public的话, 那生成的代理类都放到默认的包下:com.sun.proxy
44         if (proxyPkg == null) {
45             proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
46         }
47         //生成代理类的序号
48         long num = nextUniqueNumber.getAndIncrement();
49         //生成代理类的全限定名, 包名+前缀+序号, 例如:com.sun.proxy.$Proxy0
50         String proxyName = proxyPkg + proxyClassNamePrefix + num;
51         //这里是核心, 用ProxyGenerator来生成字节码, 该类放在sun.misc包下
52         byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
53                                   interfaces, accessFlags);
54         try {
55             //根据二进制文件生成相应的Class实例
56             return defineClass0(loader, proxyName, proxyClassFile,
57                               0, proxyClassFile.length);
58         } catch (ClassFormatError e) {
59             throw new IllegalArgumentException(e.toString());
60         }
61     }
62 }

该工厂的apply方法会被调用用来生成代理类的Class对象,由于代码的注释比较详细,我们只挑关键点进行阐述,其他的就不反复赘述了。

1. 在代码中可以看到JDK生成的代理类的类名是“$Proxy”+序号。

2. 如果接口是public的,代理类默认是public final的,并且生成的代理类默认放到com.sun.proxy这个包下。

3. 如果接口是非public的,那么代理类也是非public的,并且生成的代理类会放在对应接口所在的包下。

4. 如果接口是非public的,并且这些接口不在同一个包下,那么就会报错。

生成具体的字节码是调用了ProxyGenerator这个类的generateProxyClass方法。这个类放在sun.misc包下,后续我们会扒出这个类继续深究其底层源码。到这里我们已经分析了Proxy这个类是怎样生成代理类对象的,通过源码我们更直观的了解了整个的执行过程,包括代理类的类名是怎样生成的,代理类的访问标志是怎样确定的,生成的代理类会放到哪个包下面,以及InvocationHandler实例的引用是怎样传入的。不过读者可能还会有疑问,WeakCache缓存是怎样实现的?为什么proxyClassCache.get(loader, interfaces)最后会调用到ProxyClassFactory工厂的apply方法?在下一篇中将会为读者详细介绍WeakCache缓存的实现原理。

原文地址:https://www.cnblogs.com/liuyun1995/p/8157098.html

时间: 2024-07-31 14:35:02

JDK动态代理[2]----JDK动态代理的底层实现之Proxy源码分析的相关文章

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

Java设计模式-代理模式之动态代理(附源码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的区别就是:动态代理是在运行时刻动态的创建出代理类及其对象.上篇中的静态代理是在编译的时候就确定了代理类具体类型,如果有多个类需要代理,那么就得创建多个.还有一点,如果Subject中新增了一个方法,那么对应的实现接口的类中也要相应的实习该方法,不符合设计模式原则. 动态代理的做法:在运行时刻,可以动态创建出一个实现了多个接口的代理类.每个代理类的对象都会关联一个表示内部处理

动态代理模式--源码分析

Proxy源码 1,成员变量 ?代理类的构造函数参数.默认每个代理类都具有一个invocationHandler的构造方法.(本文代码主要基于jdk 1.7) /** parameter types of a proxy class constructor */ private static final Class<?>[] constructorParams = { InvocationHandler.class }; ?缓存代理对象. private static final WeakCa

(五)myBatis架构以及SQlSessionFactory,SqlSession,通过代理执行crud源码分析---待更

MyBatis架构 首先MyBatis大致上可以分为四层: 1.接口层:这个比较容易理解,就是指MyBatis暴露给我们的各种方法,配置,可以理解为你import进来的各种类.,告诉用户你可以干什么 2.数据处理层:顾名思义对数据的处理,当接收到一个sql语句时,比如 selecr *from person where id=#{id};  会进行这四步:参数处理---sql解析---sql执行----处理结果,这里我们重点关心sql的执行 3.框架支撑层:一些辅助操作,缓存机制,事务管理,连接

ABP源码分析三十五:ABP中动态WebAPI原理解析

动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能,这应该算是对DRY的最佳诠释了. 如下图所示,一行代码就为所有实现了IApplicationService的类型,自动创建对应的动态WebAPI. 这么Magic的功能是如何实现的呢? 本文为你揭开其Magic的外表.你会发现,实现如此Magic的功能,最关键的代码只有四行. 先思考一个问题:如果不

JDK源码分析-ArrayList分析

花了两个晚上的时间研究了一下ArrayList的源码, ArrayList 继承自AbstractList 并且实现了List, RandomAccess, Cloneable, Serializable 通过实现这三个接口 就具备了他们的功能 RandomAccess 用来表明其支持快速(通常是固定时间)随机访问 Cloneable可以克隆对象 Serializable 对象序列化就是把一个对象变为二进制的数据流的一种方法,通过对象序列化可以方便地实现对象的传输和存储,Serializable

JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlockingQueue,以便日后灵活使用. 1. 在Java的Concurrent包中,添加了阻塞队列BlockingQueue,用于多线程编程.BlockingQueue的核心方法有: boolean add(E e) ,把 e 添加到BlockingQueue里.如果BlockingQueue可以容纳,则返回true,否则抛出异常. boolean offer(E e),表示如果可能的话,将 e 加到B

JDK源码分析之String篇

------------------------------String在内存中的存储情况(一下内容摘自参考资料1)----------------------------------- 前提:先了解下什么是声明,什么时候才算是产生了对象实例 其中x并未看到内存分配,变量在使用前必须先声明,再赋值,然后才可以使用.java基础数据类型会用对应的默认值进行初始化 一.首先看看Java虚拟机JVM的内存块及其变量.对象内存空间是怎么存储分配的 1.栈:存放基本数据类型及对象变量的引用,对象本身不存放

JDK中String类的源码分析(二)

1.startsWith(String prefix, int toffset)方法 包括startsWith(*),endsWith(*)方法,都是调用上述一个方法 1 public boolean startsWith(String prefix, int toffset) { 2 char ta[] = value; 3 int to = toffset; 4 char pa[] = prefix.value; 5 int po = 0; 6 int pc = prefix.value.l

Java开源生鲜电商平台-物流动态费率、免运费和固定运费设计与架构(源码可下载)

Java开源生鲜电商平台-物流动态费率.免运费和固定运费设计与架构(源码可下载) 说明:物流配送环节常见的有包邮,免运费,或者偏远地区动态费率,还存在一些特殊的情况,固定费率,那么如何进行物流的架构与设计呢? 运费之困惑 在淘宝买过东西的亲们,应该都有过这样的体会--每次购物,都要在运费上和商家讨价还价,商议好运费后,还得下达订单等待卖家修改了才能付款,每次都如此,实在很不方便.而且当碰到运费很贵时(跨省的),那往往就会导致这次买卖告吹.无论在淘宝,还是在其它网站,当购买大件商品时,如果按照实际