Java进阶 | Proxy动态代理机制详解
一、Jvm加载对象
在说Java动态代理之前,还是要说一下Jvm加载对象的过程,这个依旧是理解动态代理的基础性原理:
Java类即源代码程序.java
类型文件,经过编译器编译之后就被转换成字节代码.class
类型文件,类加载器负责读取字节代码,并转换成java.lang.Class对象,描述类在元数据空间的数据结构,类被实例化时,堆中存储实例化的对象信息,并且通过对象类型数据的指针找到类。
过程描述:源码->.java文件->.class文件->Class对象->实例对象
所以通过New创建对象,独断其背后很多实现细节,理解上述过程之后,再了解一个常用的设计模式,即代理模式。
二、代理模式
1、基本描述
代理模式给某一个(目标)对象提供一个代理对象,并由代理对象持有目标对象的引用。所谓代理,就是一个对象代表另一个对象执行相应的动作程序。而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式在实际的生活中场景很多,例如中介、律师、代购等行业,都是简单的代理逻辑,在这个模式下存在两个关键角色:
目标对象角色:即代理对象所代表的对象。
代理对象角色:内部含有目标对象的引用,可以操作目标对象;AOP编程就是基于这个思想。
2、静动态模式
静态代理:在程序运行之前确定代理角色,并且明确代理类和目标类的关系。
动态代理:基于Java反射机制,在JVM运行时动态创建和生成代理对象。
三、静态代理
基于上述静态代理的概念,用一段代码进行描述实现,基本逻辑如下:
- 明确目标对象即被代理的对象;
- 定义代理对象,通过构造器持有目标对象;
- 代理对象中定义前后置增强方法;
目标对象与前后置增强代码就组成了代理对象,这样就不用直接访问目标对象,像极了电视剧中那句话:我是律师,我的当事人不方便和你对话。
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、场景描述
基于一个场景来描述动态代理和静态代理的区别,即最近几年很火的概念,海外代购:
在代购刚兴起的初期,是一些常去海外出差的人,会接代购需求,即代理人固定;后来就兴起海外代购平台,海淘等一系列产品,即用户代购需求(目标对象)由代购平台去实现,但是具体谁来操作这个就看即时分配,这个场景与动态代理的原理类似。
2、基础API案例
首先看两个核心类,这里简述下概念,看完基本过程再细聊:
Proxy-创建代理对象,核心参数:
- ClassLoader:(目标类)加载器;
- Interfaces:(目标类)接口数组;
- InvocationHandler:代理调用机制;
InvocationHandler-代理类调用机制:
- invoke:这个上篇说的反射原理;
- method:反射类库中的核心API;
目标对象和接口
interface IUser {Integer update (String name) ;
}
class UserService implements IUser {@Overridepublic Integer update(String name) {Integer userId = 99 ;System.out.println("UserId="+userId+";updateName="+name);return userId ;}
}
代理对象执行机制
class UserHandler implements InvocationHandler {private Object target ;public UserHandler (Object target){this.target = target ;}@Overridepublic 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;}
}
具体组合方式
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
查看代理类名称
/** proxyClassName=com.java.proxy.$Proxy0*/
String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName();
System.out.println("proxyClassName="+proxyClassName);
下意识输出代理对象名称,这里即对应JVM机制,找到Class对象名,然后分析结构,这样就明白动态代理具体的执行原理了。
生成代理类.class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
通过上面JVM加载对象的机制可知,描述代理类的Class对象一定存在,只是在运行时并没有生成显式的.class
文件,通过上面生成代理类.class
的语法,会在项目目录的/com/java/proxy
路径下创建文件。
顺便说一句:作为一只程序员,复杂总是和我们环环相绕,说好的简单点呢?
3、代理类结构
继承与实现
class $Proxy0 extends Proxy implements IUser {}
从代理类的功能来思考,可以想到需要继承Proxy与实现IUser接口,还有就是持有调用机制的具体实现类,用来做业务增强。
构造方法
public $Proxy0(InvocationHandler var1) throws {super(var1);
}
通过构造方法,持有UserHandler具体的执行机制对象。
接口实现
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实现具体的增强业务处理。
基础方法
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源码的逻辑:
IUser proxyUser = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);
Proxy提供的静态方法newProxyInstance()
,通过各个参数的传入,构建一个新的代理Class对象,即$Proxy0类的结构信息,这里再回首看下三个核心参数:
ClassLoader:基于JVM运行过程,所以需要获取目标类UserService的类加载器;
Interfaces:目标类UserService实现的接口,从面向对象来考虑,接口与实现分离,代理类通过实现IUser接口,模拟目标类的需求;
InvocationHandler:代理类提供的功能封装即UserHandler,可以在目标方法调用前后做增强处理;
最后总结一下动态代理的实现的核心技术点:Jvm加载原理、反射机制、面向对象思想;每次阅读JDK的源码都会惊叹设计者的鬼斧神工,滴水穿石坚持才会有收获。
JVM类加载机制 | 代理模式 | AOP切面编程 | 自定义日志记录 | Map源码分析
五、源代码地址
GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent
阅读标签
【Java基础】【设计模式】【结构与算法】【Linux系统】【数据库】
【分布式架构】【微服务】【大数据组件】【SpringBoot进阶】【Spring&Boot基础】
【数据分析】【技术导图】【 职场】
Java进阶 | Proxy动态代理机制详解相关推荐
- java的动态代理机制详解
2019独角兽企业重金招聘Python工程师标准>>> 参考资料 1.java的动态代理机制详解 转载于:https://my.oschina.net/Howard2016/blog ...
- 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
2019独角兽企业重金招聘Python工程师标准>>> 在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码.当前有很多开源框架可以完成这些功能,如A ...
- Java 动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java动态代理机制详解
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- 【Spring AOP】静态代理设计模式、Spring 动态代理开发详解、切入点详解(切入点表达式、切入点函数)
AOP 编程 静态代理设计模式 1. 为什么需要代理设计模式 2. 代理设计模式 名词解释 代理开发的核心要素 静态代理编码 静态代理存在的问题 Spring 动态代理开发 搭建开发环境 Spring ...
- Java 技术之动态代理机制
文章推荐 精选java等全套学习资源 精选java电子图书资源 精选大数据学习资源 java项目练习精选 ###静态代理 常规的代理模式有以下三个部分组成: 功能接口 interface IFunct ...
- JAVA 进阶篇 动态代理 JDK动态代理和CGlib动态代理
JDK动态代理和CGlib动态代理 JDK动态代理: 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. CGlib动态代理: 利用ASM(开源的Java ...
- JDK和cglib动态代理原理详解
AOP的基础是Java动态代理,了解和使用两种动态代理能让我们更好地理解 AOP,在讲解AOP之前,让我们先来看看Java动态代理的使用方式以及底层实现原理. 转自https://www.jiansh ...
最新文章
- linux mipi驱动分析_寒武纪社招内推数字IC设计、DSI驱动、软件架构、产品经理、芯片架构、工具链开发、深度学习、FAE工程师...
- 生鲜电商APP开发,有哪五大商机?
- android--service之aidl传递复杂对象,Android--Service之AIDL传递复杂对象
- 学习PWM的一些总结
- 第三次学JAVA再学不好就吃翔(part98)--自定义异常类
- 专业程序员必知必会的技巧:驯服复杂代码
- bash/shell编程学习(2)
- 2-27 最短路径《啊哈算法》2-28完成四种算法
- 好程序员大数据教程分享之Hadoop优缺点
- matlab之中文字体乱码处理
- ELK收集日志到mysql
- Zygo读取保存dat文件(光学领域知道Zygo的一定要看)
- 【linux系统学习笔记】Ubuntu文本界面和图像界面的切换
- 大平房到朝阳环路时间表_冰蓄冷系统设计计算10大要点
- 数字电子技术基础(五):编码器
- 软件工程实体-联系图
- CUPS学习三:CUPS主要模块介绍。
- EOS 一周回顾(12.2-12.8)
- unity2d里实现鼠标拖拽物体的功能
- 搭建私服环境及私服的使用-将第三方jar上传私服
热门文章
- 界址点圆圈怎么生成_技巧|CASS10.1的界址点圆圈如何变细?
- 计组之总线:2、总线仲裁(链式查询、计数器查询、独立请求、分布式查询)
- 操作系统之进程管理:20、死锁的检测和解除
- (王道408考研数据结构)第六章图-第四节2:最小生成树之克鲁斯卡尔算法(思想、代码、演示、答题规范)
- (计算机组成原理)第五章中央处理器-第二节:指令执行过程(取指周期、间址周期、执行周期和中断周期)
- libjpeg(2)
- C/C++获取当前路径、获取任意pid完整路径(readlink()函数)
- Glances:一款功能强大的操作系统安全监控平台
- 13. OD-内嵌补丁,过期的软件DVD Menu Studio破解,switch函数,break等于KillTimer
- 图:图的邻接表创建、深度优先遍历和广度优先遍历代码实现