详解Java Proxy动态 代理机制
作者:知了一笑 发布时间:2023-07-24 21:01:58
一、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 {
@Override
public 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 ;
}
@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;
}
}
具体组合方式
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
来源:https://www.cnblogs.com/cicada-smile/p/14942945.html
猜你喜欢
- 微服务治理Spring Cloud 工具套件为微服务治理提供了全面的技术支持。这些治理工具主要包括服务的注册与发现、负载均衡管理、动态路由、
- 什么是队列结构一种线性结构,具有特殊的运算法则【只能在一端(队头)删除,在另一端(队尾)插入】。分类:顺序队列结构链式队列结构基本操作:入队
- 格式要求:SU MO TU WE TH FR SA &nb
- 前言本文的记录如何用CustomPaint、GestureDetector实现一个进度条控件。首先需要说明的是 flutter Materi
- 背景在接口请求过程中,传递json对象,springboot转换为实体VO对象后,所有属性都为null。post请求:后台接收请求:当时就懵
- 背景最近好几个项目在运行过程中客户都提出文件上传大小的限制能否设置的大一些,用户经常需要上传好几个G的资料文件,如图纸,视频等,并且需要在上
- Android之文件数据存储一、文件保存数据介绍Activity提供了openFileOutput()方法可以用于把数据输出到文件中,具体的
- 简介AccessibilityService的设计初衷是为了辅助有身体缺陷的群体使用Android应用,它的设计贯穿着Android的控件树
- 1 简介Solace是一个强大的实时性的事件驱动消息队列。本文将介绍如何在Spring中使用,虽然代码使用的是Spring Boot,但并没
- 序言在flutter开发中,我们使用 bloc 框架,基于状态变更进行响应式开发。本篇文章,小轰将 bloc 核心业务块进行拆解简化,聊一聊
- 实现“摇一摇”功能,其实很简单,就是检测手机的重力感应,具体实现代码如下:1、在 AndroidManifest.xml 中添加操作权限2、
- 前言之前我们提到了 CustomPaint er 的 Paint 可以使用渐变(GradientShader)来填充绘制的图形,本篇我们来介
- 前言Flutter (Channel stable, 2.10.3, on Microsoft Windows [Version 10.0.
- 图像滤波在opencv中可以有多种实现形式自定义滤波如使用3×3的掩模:对图像进行处理.使用函数filter2D()实现#include&l
- .c 源程序 ----- 编译 ----- 链接 ---- exe ----运行 -------->程序翻译环境和执行环境翻译环境:源
- 本文实例为大家分享了C++实现俄罗斯方块的具体代码,供大家参考,具体内容如下先是效果图:主菜单:游戏:设置:错误处理:代码:#include
- Handler每个初学Android开发的都绕不开Handler这个“坎”,为什么说是个坎呢,首先这是Android架构的精髓之一,其次大部
- 在实际应用中,我们往往有需要比较两个自定义对象大小的地方。而这些自定义对象的比较,就不像简单的整型数据那么简单,它们往往包含有许多的属性,我
- 本文实例为大家分享了Android实现支付宝支付密码输入界面的具体代码,供大家参考,具体内容如下效果图:主要代码:import java.u
- 先看看效果Like This↓一、公共WiFi 公用电脑什么的在我们日常在线上工作、玩耍时,不论开电脑、登录淘宝、玩网游统统都会用到键盘输入