Java进阶 | Proxy动态代理机制详解

2021/6/28 9:20:16

本文主要是介绍Java进阶 | Proxy动态代理机制详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

# 一、Jvm加载对象 在说Java动态代理之前,还是要说一下Jvm加载对象的过程,这个依旧是理解动态代理的基础性原理: ![021.png](http://www.www.zyiz.net/i/li/?n=2&i=images/20210627/1624801681602726.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) Java类即源代码程序`.java`类型文件,经过编译器编译之后就被转换成字节代码`.class`类型文件,类加载器负责读取字节代码,并转换成java.lang.Class对象,描述类在元数据空间的数据结构,类被实例化时,堆中存储实例化的对象信息,并且通过对象类型数据的指针找到类。 过程描述:**源码->.java文件->.class文件->Class对象->实例对象** 所以通过New创建对象,独断其背后很多实现细节,理解上述过程之后,再了解一个常用的设计模式,即代理模式。 # 二、代理模式 ## 1、基本描述 代理模式给某一个(目标)对象提供一个代理对象,并由代理对象持有目标对象的引用。所谓代理,就是一个对象代表另一个对象执行相应的动作程序。而代理对象可以在客户端和目标对象之间起到中介的作用。 ![022.png](http://www.www.zyiz.net/i/li/?n=2&i=images/20210627/1624801680164921.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 代理模式在实际的生活中场景很多,例如中介、律师、代购等行业,都是简单的代理逻辑,在这个模式下存在两个关键角色: 目标对象角色:即代理对象所代表的对象。 代理对象角色:内部含有目标对象的引用,可以操作目标对象;AOP编程就是基于这个思想。 ## 2、静动态模式 - **静态代理**:在程序运行之前确定代理角色,并且明确代理类和目标类的关系。 - **动态代理**:基于Java反射机制,在JVM运行时动态创建和生成代理对象。 # 三、静态代理 基于上述静态代理的概念,用一段代码进行描述实现,基本逻辑如下: - 明确目标对象即被代理的对象; - 定义代理对象,通过构造器持有目标对象; - 代理对象中定义前后置增强方法; 目标对象与前后置增强代码就组成了代理对象,这样就不用直接访问目标对象,像极了电视剧中那句话:我是律师,我的当事人不方便和你对话。 ```java public class Proxy01 { public static void main(String[] args) { TargetObj targetObj = new TargetObj() ; ProxyObj proxyObj = new ProxyObj(targetObj) ; proxyObj.invoke(); } } class TargetObj { public void execute (){ System.out.println("目标类方法执行..."); } } class ProxyObj { private TargetObj targetObj ; /** * 持有目标对象 */ public ProxyObj (TargetObj targetObj){ this.targetObj = targetObj ; } /** * 目标对象方法调用 */ public void invoke (){ before () ; targetObj.execute(); after () ; } /** * 前后置处理 */ public void before (){ System.out.println("代理对象前置处理..."); } public void after (){ System.out.println("代理对象后置处理..."); } } ``` 静态代理明确定义了代理对象,即有一个代理对象的`.java`文件加载到JVM的过程,很显然的一个问题,在实际的开发过程中,不可能为每个目标对象都定义一个代理类,同样也不能让一个代理对象去代理多个目标对象,这两种方式的维护成本都极高。 代理模式的本质是在目标对象的方法前后置入增强操作,但是又不想修改目标类,通过前面反射机制可以知道,在运行的时候可以获取对象的结构信息,基于Class信息去动态创建代理对象,这就是动态代理机制。 **顺便说一句**:技术的底层实现逻辑不好理解是众所周知,然而基础知识点并不复杂,例如代理模式的基本原理,但是结合到实际的复杂应用中(AOP模式),很难活灵活现的理解到是基于反射和动态代理的方式实现的。 # 四、动态代理 ## 1、场景描述 基于一个场景来描述动态代理和静态代理的区别,即最近几年很火的概念,海外代购: ![023.png](http://www.www.zyiz.net/i/li/?n=2&i=images/20210627/1624801680971339.png?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) 在代购刚兴起的初期,是一些常去海外出差的人,会接代购需求,即代理人固定;后来就兴起海外代购平台,海淘等一系列产品,即用户代购需求(目标对象)由代购平台去实现,但是具体谁来操作这个就看即时分配,这个场景与动态代理的原理类似。 ## 2、基础API案例 首先看两个核心类,这里简述下概念,看完基本过程再细聊: - Proxy-创建代理对象,核心参数: - ClassLoader:(目标类)加载器; - Interfaces:(目标类)接口数组; - InvocationHandler:代理调用机制; - InvocationHandler-代理类调用机制: - invoke:这个上篇说的反射原理; - method:反射类库中的核心API; **目标对象和接口** ```java interface IUser { Integer update (String name) ; } class UserService implements IUser { @Override public Integer update(String name) { Integer userId = 99 ; System.out.println("UserId="+userId+";updateName="+name); return userId ; } } ``` **代理对象执行机制** ```java class UserHandler implements InvocationHandler { private Object target ; public UserHandler (Object target){ this.target = target ; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before()..."); Object result = method.invoke(target, args); System.out.println("after()..."); return result; } } ``` **具体组合方式** ```java public class Proxy02 { public static void main(String[] args) { /* * 生成$Proxy0的class文件 */ System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); /* * 目标对象信息 */ IUser userService = new UserService(); ClassLoader classLoader = userService.getClass().getClassLoader(); Class<? >[] interfaces = UserService.class.getInterfaces() ; /* * 创建代理对象 */ InvocationHandler userHandler = new UserHandler(userService); /* * 代理类对象名 * proxyClassName=com.java.proxy.$Proxy0 */ String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName(); System.out.println("proxyClassName="+proxyClassName); /* * 具体业务实现模拟 */ IUser proxyUser1 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler); IUser proxyUser2 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler); proxyUser1.update("cicada") ; proxyUser2.update("smile") ; } } ``` 这里之所以要生成代理类的结构信息,因为从JVM加载的过程看不到相关内容,关键信息再次被独断: ``` javap -v Proxy02.class ``` ![024.jpeg](http://www.www.zyiz.net/i/li/?n=2&i=images/20210627/1624801681369261.jpeg?,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=) **查看代理类名称** ```java /* * proxyClassName=com.java.proxy.$Proxy0 */ String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName(); System.out.println("proxyClassName="+proxyClassName); ``` 下意识输出代理对象名称,这里即对应JVM机制,找到Class对象名,然后分析结构,这样就明白动态代理具体的执行原理了。 **生成代理类.class文件** ```java System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); ``` 通过上面JVM加载对象的机制可知,描述代理类的Class对象一定存在,只是在运行时并没有生成显式的`.class`文件,通过上面生成代理类`.class`的语法,会在项目目录的`/com/java/proxy`路径下创建文件。 **顺便说一句**:作为一只程序员,复杂总是和我们环环相绕,说好的简单点呢? ## 3、代理类结构 **继承与实现** ```java class $Proxy0 extends Proxy implements IUser {} ``` 从代理类的功能来思考,可以想到需要继承Proxy与实现IUser接口,还有就是持有调用机制的具体实现类,用来做业务增强。 **构造方法** ```java public $Proxy0(InvocationHandler var1) throws { super(var1); } ``` 通过构造方法,持有UserHandler具体的执行机制对象。 **接口实现** ```java final class $Proxy0 extends Proxy implements IUser { private static Method m3; public final Integer update(String var1) throws { try { return (Integer)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } } ``` 目标类的基本需求`update()`方法,通过代理类进行承接,并基于UserHandler实现具体的增强业务处理。 **基础方法** ```java final class $Proxy0 extends Proxy implements IUser { private static Method m0; private static Method m1; private static Method m2; public $Proxy0(InvocationHandler var1) throws { super(var1); } 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.java.proxy.IUser").getMethod("update", Class.forName("java.lang.String")); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } 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); } } } ``` 基于Object类,定义Java中几个常用方法equals()判断,toString()方法,hashCode()值,这个在分析Map源码的时候有说过为什么这几个方法通常都是一起出现。 ## 4、JDK源码 上面是案例执行的过程和原理,还有一个关键点要明白,即JDK源码的逻辑: ```java IUser proxyUser = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler); ``` Proxy提供的静态方法`newProxyInstance()`,通过各个参数的传入,构建一个新的代理Class对象,即$Proxy0类的结构信息,这里再回首看下三个核心参数: - ClassLoader:基于JVM运行过程,所以需要获取目标类UserService的类加载器; - Interfaces:目标类UserService实现的接口,从面向对象来考虑,接口与实现分离,代理类通过实现IUser接口,模拟目标类的需求; - InvocationHandler:代理类提供的功能封装即UserHandler,可以在目标方法调用前后做增强处理; 最后总结一下动态代理的实现的核心技术点:Jvm加载原理、反射机制、面向对象思想;每次阅读JDK的源码都会惊叹设计者的鬼斧神工,滴水穿石坚持才会有收获。 [JVM类加载机制](https://mp.weixin.qq.com/s/buJo4c2K_9Y7qRUEp_l2kg) | [代理模式](https://mp.weixin.qq.com/s/vb5mNqguPB18VDNjEEIEyw) | [AOP切面编程](https://mp.weixin.qq.com/s/kWubXXVIZwALbb7dPaJijw) | [自定义日志记录](https://mp.weixin.qq.com/s/Cnieu8n-wkTyGbUd0WxG6g) | [Map源码分析](https://mp.weixin.qq.com/s/3jiwsnCk5YodV561-e2Tsw) # 五、源代码地址 ``` GitHub·地址 https://github.com/cicadasmile/java-base-parent GitEE·地址 https://gitee.com/cicadasmile/java-base-parent ``` ![](http://www.www.zyiz.net/i/li/?n=4&i=images/blog/202008/11/7fd614ba86c567d35245a247b675bb35.png?,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) **阅读标签** 【[Java基础](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1342230680016683009#wechat_redirect)】【[设计模式](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1709518416274833422#wechat_redirect)】【[结构与算法](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1709518416274833422#wechat_redirect)】【[Linux系统](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1334314473573744641#wechat_redirect)】【[数据库](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1376212870744358913#wechat_redirect)】 【[分布式架构](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1327025063014596608#wechat_redirect)】【[微服务](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1460269376221200386#wechat_redirect)】【[大数据组件](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1701021199339667459#wechat_redirect)】【[SpringBoot进阶](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1425309486268661760#wechat_redirect)】【[Spring&Boot基础](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1461797173297135618#wechat_redirect)】 【[数据分析](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1695231212027428866#wechat_redirect)】【[技术导图](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1506615482391511042#wechat_redirect)】【 [职场](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU4Njg0MzYwNw==&action=getalbum&album_id=1719834087936278530#wechat_redirect)】

这篇关于Java进阶 | Proxy动态代理机制详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程