大家好!我是cxuan,距离上次更新已经有一段时间了。临近过年,项目方也比较忙,最近很多时间都花在了看书和提升自己上。文章写的比较拖沓,这里想反省一下自己(其实我已经准备了几篇文章了,坐等收尾,呵呵)。上一篇我们讲了什么是动态代理,接下来我从动态代理的四种实现入手,讲解JDK动态代理、CGLIB动态代理、Javaassist、ASM反向字节码生成的区别。有关详细信息,您可以参考以下文章。动态代理就是这么简单!那么在这篇文章中,我们就来说说动态代理的实现原理。为了安全起见,让我们先花几分钟回顾一下什么是动态代理!什么是动态代理?首先,动态代理是代理模式的一种实现。除了动态代理之外,代理模式还有静态代理,但是静态代理可以在编译时确定类的执行对象,而动态代理只能在运行时确定执行对象是谁。Proxy可以看作是对最终调用目标的封装。我们可以通过操作代理对象来调用目标类,这样就可以实现调用者和目标对象的解耦。动态代理的应用场景很多,最常见的有AOP实现、RPC远程调用、Java注解对象获取、日志框架、全局异常处理、事务处理等。动态代理的实现有很多,但是JDK动态代理是非常重要的一个。让我们看一下JDK动态代理来深入理解。JDK动态代理首先我们来看一下动态代理的执行过程。在JDK动态代理中,实现了InvocationHandler的类可以看做是一个代理类(因为类也是一个对象,所以我们上面为了描述关系描述了代理类,就成了代理对象)。JDK动态代理是基于实现了InvocationHandler的代理类。比如下面是一个InvocationHandler的实现类,它也是一个代理类。公共类UserHandler实现InvocationHandler{privateUserDaouserDao;publicUserHandler(UserDaouserDao){这个。userDao=userDao;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{saveUserStart();对象obj=method.invoke(userDao,args);保存用户完成();返回对象;}publicvoidsaveUserStart(){System.out.println("----开始插入----");}publicvoidsaveUserDone(){System.out.println("----插入完成----");}}代理类最重要的方法之一就是invoke方法,它有三个参数Objectproxy:动态代理对象,关于这个方法我们后面再说。Method方法:表示最终要执行的方法。method.invoke用于执行代理方法,是真正的目标方法Object[]args:该参数是传递给目标方法的参数。我们已经构造了代理类,现在我们需要用它来实现我们对目标对象的调用,那怎么办呢?请看下面的代码publicstaticvoiddynamicProxy(){UserDaouserDao=newUserDaoImpl();InvocationHandlerhandler=newUserHandler(userDao);ClassLoader加载器=userDao.getClass().getClassLoader();类>[]interfaces=userDao.getClass().getInterfaces();UserDaoproxy=(UserDao)Proxy.newProxyInstance(loader,interfaces,handler);proxy.saveUser();}如果要使用JDK动态代理,你需要知道目标对象的类加载器,目标对象的接口,当然还有目标对象是谁。构建完成后,我们就可以调用Proxy.newProxyInstance方法,然后将类加载器、目标对象的接口、目标对象绑定到它上面。这里需要注意Proxy类,它是动态代理实现使用的代理类。Proxy位于java.lang.reflect包下,这也说明了动态代理的本质是反射。下面我们就着眼于JDK动态代理,深入了解其原理,理解为什么动态代理的本质是反射。动态代理的实现原理在了解动态代理的实现原理之前,我们先了解一下InvocationHandler接口。种类。InvocationHandler接口中只有一个invoke方法。动态代理的好处是可以方便的集中处理代理类中的方法,而不用修改每个代理方法。因为所有被代理的方法(实际执行的方法)都是通过InvocationHandler中的invoke方法调用的。所以我们只需要集中invoke方法即可。invoke方法只有三个参数publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable;proxy:代理对象method:代理对象调用的方法args:调用方法中的参数。动态代理的整个代理过程并不像静态代理那么清晰易懂,因为在动态代理的过程中,我们没有看到代理类真正的代理过程,也没有了解它的具体操作,所以我们需要分析动态代理的实现原则上一定要使用源码。那么问题来了,第一步应该从哪里分析呢?如果不知道怎么分析,可以简单的用逆向的方法从后往前找。让我们从Proxy.newProxyInstance开始,看看我们是否能得到一点想法二。Proxy.newInstance方法分析Proxy提供了创建动态代理类和实例的静态方法,它也是这些方法创建的所有动态代理类的超类。publicstaticObjectnewProxyInstance(ClassLoaderloader,Class>[]interfaces,InvocationHandlerh)throwsIllegalArgumentException{Objects.requireNonNull(h);finalClass>[]intfs=interfaces.clone();最后的SecurityManagersm=System.getSecurityManager();if(sm!=null){checkProxyAccess(Reflection.getCallerClass(),loader,intfs);}Class>cl=getProxyClass0(loader,intfs);尝试{if(sm!=null){checkNewProxyPermission(Reflection.getCallerClass(),cl);}finalConstructor>cons=cl.getConstructor(constructorParams);最终调用处理程序ih=h;if(!Modifier.isPublic(cl.getModifiers())){AccessController.doPrivileged(newPrivilegedAction(){publicVoidrun(){cons.setAccessible(true);returnnull;}});}返回cons.newInstance(新对象ct[]{h});}catch(Exceptione){...}乍一看有点麻烦,其实源码是这样的,看起来很复杂,但是慢慢分析理清顺序就好了,最importantthing最重要的是分析源码不用担心上面的Proxy.newProxyInstsance。实际上,它做了以下事情。我画了一个流程图作为参考。从上图我们也可以看出,newProxyInstance方法最重要的步骤是获取代理类,获取构造函数,然后构造新的实例。对反射有一定了解的同学应该知道如何获取构造函数,构造新的实例。关于反思,可以参考作者的这篇文章。经过学习反思,我被录取了!所以我们的重点是获取代理类,这是最关键的一步,对应源码中的Classcl=getProxyClass0(loader,intfs);让我们进入这个方法找出privatestaticClass>getProxyClass0(ClassLoaderloader,Class>...interfaces){if(interfaces.length>65535){thrownewIllegalArgumentException("interfacelimitexceeded");}returnproxyClassCache.get(loader,interfaces);}这个方法比较简单。首先会直接判断接口的长度是否大于65535(一开始看到这个没看懂,心想,判断什么判断?interfaces这不是一个类吗type?点击长度是看不到这个属性的,仔细一看可以明白,这其实是一个可变参数,Class...中的...是一个可变参数,所以我猜这个judgement应该是判断interface的个数是否大于65535)然后根据loader和interfaces直接从proxyClassCache中获取代理对象实例。如果它可以根据加载器和接口找到一个代理对象,它会在缓存中返回该对象的副本;否则,它将通过ProxyClassFactory创建一个代理类。proxyClassCache.get是一系列从缓存中查询的操作。注意这里的proxyClassCache其实是一个WeakCache。WeakCache也是java.lang.reflect包下的缓存映射图。它的主要特点是弱引用映射,但是它里面有一个SubKey,但是这个子键是强引用的。这里我们不用去深究这个proxyClassCache是??怎么缓存的,我们只需要知道它的缓存时机:也就是类加载的时候去缓存。如果找不到代理对象,会通过ProxyClassFactory创建代理,ProxyClassFactory继承自BiFunctionprivatestaticfinalclassProxyClassFactoryimplementsBiFunction[],Class>>{...}ProxyClassFactory有两个属性一种方法。proxyClassNamePrefix:该属性表示使用ProxyClassFactory创建的代理实例的名称以“$Proxy”为前缀。nextUniqueNumber:该属性表示ProxyClassFactory的后缀是一个使用AtomicLong生成的数字,所以代理实例的名字一般是,Proxy1。这个apply方法是一个工厂方法,用于创建基于接口和类加载器的代理实例。以下是这段代码的核心。@OverridepublicClass>apply(ClassLoaderloader,Class>[]interfaces){...longnum=nextUniqueNumber.getAndIncrement();StringproxyName=proxyPkg+proxyClassNamePrefix+num;byte[]proxyClassFile=ProxyGenerator.generateProxyClass(proxyName,interfaces,accessFlags);尝试{returndefineClass0(loader,proxyName,proxyClassFile,0,proxyClassFile.length);}catch(ClassFormatErrore){thrownewIllegalArgumentException(e.toString());实例的命名就是我们上面介绍的命名方式,只是这里加上了proxyPkg包名的路径。那么下面就是生成代理实例的关键代码。ProxyGenerator.generateProxyClass我们后续只能看到.class文件。class文件是虚拟机编译的结果,所以我们要看.java文件的源码。.java源代码位于OpenJDK的sun.misc包中的ProxyGenerator下。该类的generateProxyClass()静态方法的核心内容是调用generateClassFile()实例方法生成Class文件。方法太长,这里就不贴了。下面粗略解释一下它的作用:第一步:收集所有要生成的代理方法,将它们包装成ProxyMethod对象,注册到Map集合中。第二步:收集Class文件所有需要生成的字段信息和方法信息。第三步:完成以上工作后,开始组装Class文件。defineClass0这个方法点击的时候是native的,底层是C/C++实现的,所以我又去看了下C/C++源码,路径打开后的C/C++源码还是相当的令人失望。但是让我们回顾一下defineClass0方法。它实际上是根据上面生成的proxyClassFile字节数组生成对应的实例,所以我们不必深究C/C++对代理对象的合成过程。所以综上所述,我们可以看到JDK为我们生成了一个名为$Proxy0的代理类。这个类文件放在内存中。我们在创建代理对象的时候,通过反射获取到这个类的构造方法,然后进行创建。代理实例。所以我们反编译代码的初始dynamicProxy方法就像这样Code:0:aload_01:aload_12:invokespecial#8//Methodjava/lang/reflect/Proxy."":(Ljava/lang/reflect/InvocationHandler;)V5:return我们看到了代理类它继承了Proxy类,所以确定Java动态代理只能代理接口。所以,你应该能看懂上图。invoke方法中第一个参数proxy的函数。细心的朋友可能已经发现了invoke方法中第一个proxy的作用是什么?代码中似乎没有使用代理。这个参数是什么意思?它的运行时类型是什么?为什么不用这个呢?Stackoverflow给了我们答案https://stackoverflow.com/questions/22930195/understanding-proxy-arguments-of-the-invoke-method-of-java-lang-reflect-invoca是什么意思?也就是说,这个代理才是真正的代理对象。invoke方法可以返回调用代理对象方法的返回结果,也可以返回对象真正的代理对象,即$Proxy0。这也是它运行的类型。至于为什么不用this代替代理,因为实现InvocationHandler的对象中的this指的是InvocationHandler接口实现类本身,而不是真正的代理对象。后记另外,这段时间公众号出现了一些问题,你在公众号回复的部分关键词没有对应的链接。在此向大家说声抱歉。