动态代理模式的Java实现

2024-09-06 01:08
文章标签 java 动态 实现 模式 代理

本文主要是介绍动态代理模式的Java实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

动态代理(Dynamic Proxy):相比前一篇文章所实现的静态代理,动态代理具有更强的灵活性,因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延迟到程序运行时由JVM来实现
我们知道,所谓代理,就是需要代理类和被代理类有相同的对外接口或者说成服务,所以代理类一般都必须实现了所有被代理类已实现的接口,因为接口就是制定了一系列对外服务的标准。
正因为动态代理有这样灵活的特性,所以我们在设计动态代理类(DynamicProxy)时不用显式地让它实现与真实主题类(RealSubject)相同的接口(interface),而是把这种实现推迟到运行时。
为了能让DynamicProxy类能够在运行时才去实现RealSubject类已实现的一系列接口并执行接口中相关的方法操作,需要让DynamicProxy类实现JDK自带的java.lang.reflect.InvocationHandler接口,该接口中的invoke()方法能够让DynamicProxy实例在运行时调用被代理类的“对外服务”,即调用被代理类需要对外实现的所有接口中的方法,也就是完成对真实方法的调用,Java帮助文档中称这些真实方法为处理程序。
按照上面所述,我们肯定必须先把被代理类RealSubject已实现的所有interface都加载到JVM中,不然JVM怎么能够找到这些方法呢?明白了这个道理,那么我们就可以创建一个被代理类的实例,获得该实例的类加载器ClassLoader
所谓的类加载器ClassLoader,就是具有某个类的类定义,即类的内部相关结构(包括继承树、方法区等等)。
更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到控制被代理对象的行为的目的。请详看下面代码中的DynamicProxy类,其中必须实现的invoke()方法在调用被代理类的真实方法的前后都可进行一定的特殊操作。这是动态代理最明显的优点。

虽然都是根据自己看了书之后的理解说了这么多,不知道能不能让人明白,这里先给出动态代理的类图吧,如下:


具体代码实现如下:
  
  1. import java.lang.reflect.InvocationHandler;  
  2. import java.lang.reflect.Method;  
  3. import java.lang.reflect.Proxy;  
  4.  
  5. //抽象主题类,这里不能用abstract抽象类,一定要是interface  
  6. interface AbstractSubject {  
  7.     public abstract void request();  
  8. }  
  9.  
  10. // 真实主题类,即被代理类  
  11. class RealSubject implements AbstractSubject {  
  12.     public void request() {  
  13.         System.out.println("RealSubject's request() ...");  
  14.     }  
  15. }  
  16.  
  17. // 动态代理类,实现InvocationHandler接口  
  18. class DynamicProxy implements InvocationHandler {  
  19.  
  20.     // 被代理类的实例  
  21.     Object obj = null;  
  22.  
  23.     // 将被代理者的实例传进动态代理类的构造函数中  
  24.     public DynamicProxy(Object obj) {  
  25.         this.obj = obj;  
  26.     }  
  27.  
  28.     /**  
  29.      * 覆盖InvocationHandler接口中的invoke()方法  
  30.      *   
  31.      * 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构  
  32.      * 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到  
  33.      * 控制被代理对象的行为,下面的before、after就是我们可以进行特殊  
  34.      * 代码切入的扩展点了。  
  35.      */ 
  36.     public Object invoke(Object proxy, Method method, Object[] args)  
  37.             throws Throwable {  
  38.         /*  
  39.          * before :doSomething();  
  40.          */ 
  41.         Object result = method.invoke(this.obj, args);  
  42.           
  43.         /*  
  44.          * after : doSomething();  
  45.          */ 
  46.         return result;  
  47.     }  
  48. }  
  49.  
  50. // 测试类  
  51. public class Client {  
  52.     public static void main(String[] args) {  
  53.  
  54.         // 被代理类的实例  
  55.         AbstractSubject realSubject = new RealSubject();  
  56.  
  57.         // 获得被代理类的类加载器,使得JVM能够加载并找到被代理类的内部结构,以及已实现的interface  
  58.         ClassLoader loader = realSubject.getClass().getClassLoader();  
  59.  
  60.         // 获得被代理类已实现的所有接口interface,使得动态代理类的实例  
  61.         Class<?>[] interfaces = realSubject.getClass().getInterfaces();  
  62.  
  63.         // 用被代理类的实例创建动态代理类的实例,用于真正调用处理程序  
  64.         InvocationHandler handler = new DynamicProxy(realSubject);  
  65.  
  66.         /*  
  67.          * loader : 被代理类的类加载器  
  68.          * interfaces :被代理类已实现的所有接口,而这些是动态代理类要实现的接口列表  
  69.          * handler : 用被代理类的实例创建动态代理类的实例,用于真正调用处理程序  
  70.          *   
  71.          * return :返回实现了被代理类所实现的所有接口的Object对象,即动态代理,需要强制转型  
  72.          */ 
  73.         //获得代理的实例  
  74.         AbstractSubject proxy = (AbstractSubject) Proxy.newProxyInstance(  
  75.                 loader, interfaces, handler);  
  76.  
  77.         proxy.request();  
  78.         //打印出该代理实例的名称  
  79.         System.out.println(proxy.getClass().getName());  
  80.     }  
 
测试结果:
RealSubject's request() ...
DesignPattern.proxy.dynamicProxy.$Proxy0

运行结果与前一篇文章中静态代理的一样,我们也发现这个动态代理的实例的名称为“$Proxy0”,前面的都是我所用的包名,记得以前学习内部类时,内部类编译之后生成的.class文件的默认命名方式是带有“$”。但是现在这个肯定不是内部类,因为“$”之前并没有任何一个外部类的名称。是不是以后遇到“$Proxy0”这样的名字就可以推断出该实例一定是个动态代理类的实例呢?有待证明。
其实,在上面整个代码当中,注释最少的就是DynamicProxy类中的invoke()方法了,因为看了Java帮助文档也不是特别明白,所以不敢乱写,这一点还有待探究。
话又说回来,动态代理机制确实很灵活,或者说很智能,但是这是运用到了Java中的反射机制,而反射机制又与JVM中栈区、堆区、方法区等底层细节以及类的加载、生命周期等知识相关,要完全理解相当不容易,看来还有很长的路要走呢!
     最后,可能大家都觉得测试类即Client类中代码很繁杂,人家一看就不想要使用动态代理了,认为一使用就要有相当的消耗。那么此时我们可以再继续扩展一下,设计一个类能够根据传进的相关参数而返回最终客户需要的代理,这样的类设计是不是很像前面文章中工厂方法模式的应用呢?

这篇关于动态代理模式的Java实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1140571

相关文章

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows