Java 动态代理解析

引言

本博客总结自《Java 编程思想》第十四章

一、实现第一个动态代理程序

代理是软件设计中重要的设计思想,它允许我们在调用实际操作之前或之后解耦式地编写额外的操作,而一旦不需要这些操作了,就可以轻易的移除它们。

浏览了《编程思想》中对动态代理的解释,我发现动态代理的实现也是非常简单的。

想要实现动态代理,除了要借助于 Java 的运行时类型信息( RTTI :Run-Time Type Identification),即 Class 对象,还需要反射包(java.lang.reflect.*)下的一些 API  的帮助。

1.1 完成原始调用

动态代理的本质还是代理,其最根本的目的就是充当中介者的角色,将请求转发,并在转发的过程中添加操作。因此,我们还是需要先按部就班地完成最原始的调用。

以最常见的 web 调用为例,我们写一个 Service 接口,并实现它。再写一个 Controller 来进行接口的调用。

RequestService 接口:

public interface RequestService {public void processRequest(Object request);
}

RequestServiceImpl 接口实现:

public class RequestServiceImpl implements RequestService {@Overridepublic void processRequest(Object request) {System.out.println("执行请求处理逻辑...");System.out.println("请求对象:" + request);System.out.println("处理请求完成!");}
}

MyController :

public class MyController {private RequestService service;// 模拟依赖注入public MyController(RequestService service) {this.service = service;}// 模拟@RequestMapping接口方法public void receiveRequest(String httpRequest) {service.processRequest(httpRequest);}
}

然后我们写一个 main 方法,来模拟浏览器的调用,直接向 MyController 传递一个请求:

public class GoogleBrowser {// 模拟浏览器的调用public static void main(String[] args) {// 初始化service组件RequestService service = new RequestServiceImpl();// 初始化controller组件MyController clr = new MyController(service);// 向controller发送请求clr.receiveRequest("Morty");}
}

上面几段代码是最最简单的 web 调用的层级关系,Controller 调用 Service 的方法完成业务逻辑。

执行结果:

1.2 实现调用处理器

实现动态代理的关键是要自定义一个代理类也即调用处理器,它必须实现一个叫作 InvocationHandler 的接口。

public class MyDynamicProxy implements InvocationHandler {private Object proxied;public MyDynamicProxy(Object proxied) {this.proxied = proxied;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);return method.invoke(proxied, args);}
}

invoke 方法接收三个参数,第一个 proxy 是代理对象本身,一般我们不需要关心,第二个和第三个参数是请求转发的必须参数。我们在调用一个实例方法的时候,必须要知道方法的签名和实例对象,方法的签名又包括方法名和参数列表。method 参数就是接口中的抽象方法映射,而 args 就是方法所需的参数列表,而 proxied 就是能够调用方法的实际对象。

1.3 获得动态代理对象

这是动态代理调用的最关键一步,我们虽然定义好了一个动态代理类,但是通常情况下,我们并不是直接通过 new 关键字来创建动态代理对象,而是通过一个静态方法。改造后的 main 如下所示:

public class GoogleBrowser {// 模拟浏览器的调用public static void main(String[] args) {RequestService service = new RequestServiceImpl();// 获取动态代理对象RequestService serviceProxy = (RequestService) Proxy.newProxyInstance(RequestService.class.getClassLoader(),new Class[] { RequestService.class }, new MyDynamicProxy(service));MyController clr = new MyController(serviceProxy);// 调用 controller 接口clr.receiveRequest("Morty");}
}

执行结果:

二、Proxy.newProxyInstance(...) 解析

这个静态方法会返回一个指定接口的代理类实例,这个代理类实例可以将方法调用转发给指定的调用处理器。

该静态方法需要传递三个参数:

1、ClassLoader loader :通常可以直接将被代理接口 Class 对象的类加载器传递进来。

2、Class<?>[] interfaces :一个你希望代理对象实现的接口列表(注意不是类或抽象类)。

3、InvocationHandler h :InvocationHandler 接口的一个实现对象(就是刚刚定义的调用处理器对象)。

通过该方法生成的代理对象,实际上也是接口的子类对象,在调用者只有接口引用的情况下,代理对象将自动接管发送给接口的请求,并在处理后转发给被代理对象

三、对动态代理的理解

代理其实并不复杂,它是基于对接口调用的一种内部处理技巧。

代理类必须要伪装成被代理接口的子类,并封装被代理对象,才能骗过调用者的眼睛。

从编码的过程不难看出,动态代理类本身就是一个调用处理器,也就是说,动态代理的本质就是请求转发。由于使用了接口引用调用这种动态绑定的方式(多态),因此在发生真正调用行为之前,JVM 会进行类型检查,当发现接口引用指向的对象同时也是一个 InvocationHandler 接口的子类时,就明白了一切,从而自动调用 invoke() 方法,然后将必要的反射信息传递进去。这样就完成了运行时动态的请求处理。

动态代理的工作过程可以这样来描述:

在 controller 中,只有一个 RequestService 接口的引用。

当 controller 调用接口中的 processRequest() 方法时,JVM 会通过类型检查检测到 service 对象真正的类型是(通过 Proxy.newProxyInstance()方法获得的)MyDynamicProxy 类型。

那么 JVM 内部就会将请求直接转发给 MyDynamicProxy 对象内部的 invoke() 方法。

invoke() 方法收到请求后,可以通过传来的 Method 参数(实际上就是被调用的抽象接口方法信息)以及 args 参数做额外的处理。

处理完成后,将被代理对象参数列表传递给 method.invoke() 方法,完成请求的 “转发”(其中被代理对象是在Proxy.newProxyInstance() 创建代理对象的时候传递给代理对象的构造器,参数列表则是在调用者进行方法调用的时候传递给调用处理器的)。

动态代理是普通代理的进一步扩展和应用,代理类在定义之初并不知道将会代理哪个接口以及哪个被代理对象,但通过 newProxyInstance 静态方法,我们可以让代理类代理多个接口,这种变化让程序显得更加自由。

动态的创建代理,以及动态的处理所代理方法的调用都是动态代理这项技术的优秀之处。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558716.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 基础 ————高频函数总结

一、MySQL函数调用方式 函数调用的基本语法&#xff1a; SELECT 函数(实参列表) [FROM 表] 其中&#xff0c;对于函数&#xff0c;需要重点关注三点&#xff1a; 1、函数的名称 2、参数列表 3、函数功能 二、函数的分类 在 MySQL中&#xff0c;函数分为两类&#xff1a;1、…

MySQL 高级 —— 复合索引简介(多列索引)

引言 复合索引是指包含多个数据列的索引&#xff0c;与之概念相对的是单列索引&#xff0c;仅包含一个数据列。在大多数情况下&#xff0c;建立多列索引的好处都要多于单列索引。另外&#xff0c;复合索引最多支持16个列&#xff0c;但请一定不要让复合索引包含太多的列&#…

MySQL 高级 —— 索引实现的思考

引言 最近看了一个公开课&#xff0c;是有关MySQL对索引设计的思考。详细讲解了几种索引实现的设计思考与利弊辨析&#xff0c;讨论了为什么MySQL默认情况下会使用B树索引&#xff0c;B树索引又对B树做了哪些结构改进。 本片博客通过个人的学习理解和总结&#xff0c;由几种简…

Git 初学札记(十)—— Reset 回退的三种状态解析

引言 工作中经常会涉及到需要本地代码覆盖更新的操作。有时候可能是从远端git 上直接覆盖更新&#xff0c;或者是其他本地分支覆盖更新当前分支等等。这个时候就需要用到 reset 操作。 reset 操作分为三种类型&#xff1a;Soft、Mixed、Hard。今天我们就来说说这三种类型究竟…

MySQL 高级 —— 深入理解 InnoDB 与 MyISAM

引言 在文件系统中&#xff0c;MySQL将每个数据库&#xff08;也可以称之为schema&#xff09;保存为数据目录下的一个子目录。创建表时&#xff0c;MySQL会在数据库子目录下创建一个与表同名的.frm文件保存表的定义。因为MySQL使用文件系统的目录和文件来保存数据库和表的定义…

关于 OutOfMemoryError 的总结与解决方法

引言 本文总结自周志明的《深入理解Java虚拟机》第二章部分内容。 这部分内容&#xff0c;可以为后续性能调优方面的工作起到铺垫作用。 一、什么是 OutOfMemoryError OurOfMemory 简称“OOM”&#xff0c; 直译为“内存耗尽”或“内存溢出”&#xff0c;当然&#xff0c;并…

Windows误关闭资源管理器重启的办法

引言 有时候Windows系统在开机后&#xff0c;在桌面底部的任务栏中无法正常加载必要的网络连接图标或音量图标等&#xff0c;导致无法手动操作音量或连接网络。这时候就会需要打开“任务管理器”重新启动“资源管理器”使其重新加载这些必要的控制图标。 但是由于操作失误&am…

MySQL高级 —— 高性能索引

引言 最近一直在抱着《高性能MySQL&#xff08;第三版&#xff09;》研究MySQL相关热点问题&#xff0c;诸如索引、查询优化等&#xff0c;这阶段的学习是前一段时间MySQL基础与官方的“阅读理解”的进一步延伸。 书中第五章详细阐述了如何设计高性能的索引&#xff0c;以及索…

MySQL高级 —— 查询性能优化

引言 承接《MySQL高级 —— 高性能索引》&#xff0c;本篇博客将围绕《高性能MySQL&#xff08;第三版&#xff09;》第六章内容进行总结和概括。 与索引的部分一样&#xff0c;SQL优化也是广大程序员深入MySQL的又一条必经之路。希望通过本篇博客的总结&#xff0c;能够为我…

Java常用设计模式————适配器模式

引言 由于无法直接使用某个类中的方法而采取的一种中间类转换的策略。将一个类的接口转换成另一个接口&#xff0c;让原本接口不兼容的类可以兼容。 适配器模式可以分为三种&#xff1a;类适配器、对象适配器、接口适配器。它们之间的区别主要体现在适配器角色与被适配角色之…

Java常用设计模式————桥接模式

引言 在实际的业务中&#xff0c;经常会遇到多维度的概念组合&#xff0c;公园的门票&#xff0c;颐和园有年票、月票、日票&#xff0c;故宫也有年票、月票、日票。那么不同的公园和票种类型就可以视为两种不同的纬度&#xff0c;它们之间会形成相互组合的关系。 在类的设计…

Java常用设计模式————装饰者模式

引言 装饰者模式&#xff0c;又叫装饰器模式。它可以动态的将新功能附加到对象上。在对象功能扩展方面&#xff0c;它比继承更灵活&#xff0c;同时装饰者模式也体现了OCP原则。 在客户端调用使用了装饰者模式的对象时&#xff0c;就好像在使用构造器层层包裹核心对象&#x…

Java常用设计模式————组合模式

引言 组合模式&#xff0c;是一种类似递归算法的结构性设计模式&#xff0c;通过以简单的 List &#xff0c;组合本类对象&#xff0c;实现树状对象结构的“部分、整体”的层次。 它可以让调用程序不需要关心复杂对象与简单对象的区别&#xff0c;而统一地实现处理逻辑。 对…

Java常用设计模式————外观模式

引言 外观模式&#xff08;Facade Pattern&#xff09;&#xff0c;又叫“过程模式”。外观模式为子系统中的一组接口提供一个一致的入口&#xff0c;此模式定义了一个高层接口&#xff0c;这个接口使得这一组子系统更加易用。 一、案例分析 生活中有很多类似的案例&#xf…

Java常用设计模式————享元模式

引言 享元模式&#xff0c;也叫蝇量模式&#xff08;Flyweight Pattern&#xff09;。运用共享技术有效地支持大量细粒度的对象。 享元模式常用于系统底层开发&#xff0c;解决系统的性能问题。例如数据库连接池&#xff0c;里面都是创建好的连接对象&#xff0c;在这些连接对…

IDEA——常用基础设置

一、设置入口 File—>Settings... 或者 在工具栏的“小扳手”图标。 二、主题设置 三、编辑通用设置 设置面板中的 Editor 3.1 自动导包 可以设置IDEA自动为程序导包&#xff0c;在书写时加入准确的导包&#xff0c;在书写时优化导包&#xff08;自动去掉未使用的&#…

IDEA——常用快捷键

引言 总结 IDEA 的常用快捷键&#xff0c;除了部分快捷键与 Eclipse 保持一致之外&#xff0c;枚举更多的实用快捷键。 一、如何设置快捷键 在 Settings -> Keymap 中&#xff0c;下拉框里选择 Eclipse &#xff0c;即可将 IDEA 的快捷键设置为与 Eclipse 保持一致。但并…

IDEA——常用代码模板

引言 IDEA 提供了一些内置的代码模板&#xff0c;可以让开发者快速方便的使用&#xff0c;当然 eclipse 中也是有的&#xff0c;比如输入 syso 快速生成输出语句&#xff0c;main 快速生成主函数等。 idea 的模板设置都在 Settings --> Live Templates 和 General-->Po…

IDEA——Git 的设置与使用

引言 在本机下载好 Git 之后&#xff0c;再去在 IDEA 中设置 Git 相关的参数。详细的 Git 操作和 Eclipse 大同小异&#xff0c;可以移步至&#xff1a;《Git必知必会》 一、设置Git执行程序路径 二、导入一个新的远程 git 托管项目 打开 File ——> New ——> Project…

IDEA——Maven的配置与使用

引言 简单介绍一下如何在 idea 中配置maven&#xff0c;以及如何去使用 maven 。 一、配置 Maven home Maven home 和 settings 文件一般都需要进行重新设置&#xff0c;关联到本机已经安装好的 maven 版本&#xff0c;settings 这里可以使用默认&#xff0c;也可以设置为 ma…