Java设计模式之代理模式(一)

什么是代理?可以理解为其他对象提供一种代理以控制对这个对象的访问。

举个例子,生活中的外卖平台,店铺是制作外卖的,然后放到平台上售卖。这里的店铺就是真实角色,为了能够让店铺不用担心销售等问题,从而能够更加专注做外卖,所以店铺的外卖都会放到平台上面。这里平台就是代理。平台代店家出售,而店家只需要做外卖就好了。

代理模式和装饰器模式的区别:装饰器模式为了增强目标对象的功能,而代理模式是为了代理或增强目标对象的功能,本质上两者都可以为目标对象添加一些功能。

代理模式可以分为静态代理和动态代理。

一、静态代理

静态代理:程序在运行之前需要手动创建代理类,代理类与目标类实现同一个接口。代理类为目标类增加一些额外的功能。

1、创建接口:

package com.statics.proxy.interfaces;/*** @Author: 倚天照海* @Description:*/
public interface HotelInterface {public void cook();}

2、创建实现类

package com.statics.proxy.impl;import com.statics.proxy.interfaces.HotelInterface;/*** @Author: 倚天照海* @Description:*/
public class HuangMenJi implements HotelInterface {@Overridepublic void cook() {System.out.println("黄焖鸡米饭很好吃!");}}

3、创建代理类

package com.statics.proxy.impl;import com.statics.proxy.interfaces.HotelInterface;/*** @Author: 倚天照海* @Description:*/
public class MeiTuan implements HotelInterface {private HuangMenJi huangMenJi;public MeiTuan(HuangMenJi huangMenJi) {this.huangMenJi = huangMenJi;}@Overridepublic void cook() {discount();huangMenJi.cook();pay();}private void discount(){System.out.println("5折优惠,量大实惠,欢迎来购");}private void pay(){System.out.println("快捷付款,一键支付");}}

4、创建测试类

package com.statics.proxy;import com.statics.proxy.impl.HuangMenJi;
import com.statics.proxy.impl.MeiTuan;/*** @Author: 倚天照海* @Description:*/
public class ProxyTest {public static void main(String[] args) {HuangMenJi huangMenJi = new HuangMenJi();MeiTuan meiTuan = new MeiTuan(huangMenJi);meiTuan.cook();}}

输出结果:

二、JDK动态代理

动态代理是一种在运行时动态地创建代理对象并调用代理方法的机制。动态代理不需要定义代理类的.java源文件,在程序运行时由JVM根据反射机制动态的创建代理类对象。动态代理其实就是jdk运行期间,动态创建class字节码并加载到JVM。

动态代理的优点是提高了代码的灵活性和扩展性,具有解耦的意义。缺点是生成代理对象和调用代理方法需要耗费时间。

动态代理主要有两种实现方式:JDK动态代理和CGLIB动态代理

JDK动态代理其实是通过反射机制实现的。

使用JDK动态代理主要有以下几个步骤:

1.编写一个被代理的接口和接口实现类;

2.自定义一个调用处理器,并实现InvocationHandler接口,重写invoke方法;

3.在invoke方法中通过反射调用被代理方法(即上面接口实现类中的方法);

4.创建接口实现类的实例,并将该实例作为参数来创建自定义调用处理器的实例;

5.通过Proxy类的静态方法newProxyInstance获取代理对象(实现接口),并通过代理对象调用接口中的目标方法(实际是代理对象中重写后的目标方法)。

InvocationHandler是proxy代理实例调用处理程序实现的一个接口,每一个proxy代理实例都与一个调用处理器进行关联,在代理实例调用接口方法时,方法调用会被转发到调用处理器的invoke方法,即代理对象调用接口方法时,在内部会调用InvocationHandler的invoke方法。

Proxy类就是用来创建一个代理对象的类,其中最常用的方法是newProxyInstance静态方法。这个方法的作用就是创建一个代理类对象,它接收三个参数,三个参数的含义如下所示:

loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载。

interfaces:一个interface对象数组,表示给代理对象提供一组接口对象,也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。

h:一个InvocationHandler对象,表示的是当动态代理对象调用接口方法时,会关联到指定的InvocationHandler对象上,并调用指定的InvocationHandler的invoke方法。

示例代码:

被代理的接口及实现类:

package com.tx.study.others.proxy.jdkProxy;/*** @Author: 倚天照海*/
public interface Manager {public void modify();}package com.tx.study.others.proxy.jdkProxy;/*** @Author: 倚天照海*/
public class ManagerImpl implements Manager {@Overridepublic void modify() {System.out.println("*******实现类的modify()方法被调用*********");}}

自定义的调用处理器

package com.tx.study.others.proxy.jdkProxy;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** @Author: 倚天照海*/
public class ManagerInvocationHandler implements InvocationHandler {private Object object = null;public ManagerInvocationHandler(Object object) {this.object = object;}/*** 重写了InvocationHandler接口中的invoke方法(该方法在何处被调用?)* @param proxy 代理对象* @param method 被代理类中的被代理方法的Method对象* @param args 被代理方法的参数* @return 被代理方法的返回值,如果被代理方法是被void修饰的,则返回null* @throws Throwable 可能会抛出的异常*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//在真实的对象执行之前可以添加自己的操作System.out.println("do something before method");//通过反射调用被代理方法,即接口实现类中的方法Object ret = method.invoke(this.object, args);//在真实的对象执行之后可以添加自己的操作System.out.println("do something after method");return ret;}}

获取代理对象并调用方法

package com.tx.study.others.proxy.jdkProxy;import java.lang.reflect.Proxy;/*** @Author: 倚天照海*/
public class JdkProxyTest {public static void main(String[] args) {//原对象(被代理对象)Manager managerImpl = new ManagerImpl();//业务代理类ManagerInvocationHandler businessHandler = new ManagerInvocationHandler(managerImpl);//获得代理类($Proxy0 extends Proxy implements Manager)的实例Manager managerProxy = (Manager) Proxy.newProxyInstance(managerImpl.getClass().getClassLoader(),managerImpl.getClass().getInterfaces(),businessHandler);//通过代理对象调用代理类中重写的接口方法(重写的invoke方法在此处被调用)managerProxy.modify();}}

执行结果:

do something before method
*******实现类的modify()方法被调用*********
do something after method

拓展:生成jdk动态代理类的源码:

package com.tx.study.others.proxy.jdkProxy;import sun.misc.ProxyGenerator;import java.io.*;/*** @Author: 倚天照海* @Description: 生成jdk动态代理类的源码*/
public class JdkProxySourceCodeUtil {public static void main(String[] args) {//指定生成的代理类名称String proxyClassName = "$proxy0";Class<Manager> interfaceClass = Manager.class;//指定保存代理类的路径String path = "D:\\ProgramFiles\\workspace\\myself\\data-query\\tx-study\\src\\main\\java\\com\\tx\\study\\others\\proxy\\jdkProxy\\";path = path.concat(proxyClassName).concat(".class");writeClassToFile(proxyClassName,interfaceClass,path);}private static <T> void writeClassToFile(String proxyClassName, Class<T> interfaceClass, String path){byte[] proxyClass = ProxyGenerator.generateProxyClass(proxyClassName, new Class[]{interfaceClass});OutputStream os = null;try {os = new FileOutputStream(new File(path));os.write(proxyClass);os.flush();} catch (IOException e) {e.printStackTrace();} finally {try {if (os != null){os.close();}} catch (IOException e) {e.printStackTrace();}}}}

由上可知,Proxy类中的newProxyInstance静态方法被调用,该方法返回一个代理对象,然后向上转型为其对应的接口。接下来简单看一下Proxy类中的部分源码(如下),主要看一下静态方法newProxyInstance。在该方法中调用getProxyClass0(loader,intfs)方法,根据类加载器和接口数组获取动态代理类的字节码文件对象,进而获取构造器对象,最后执行cons.newInstance(new Object[]{h})(根据指定的调用处理器对InvocationHandler进行初始化),通过反射调用有参构造器创建代理对象,并返回该代理对象。

public class Proxy implements java.io.Serializable {private static final long serialVersionUID = -2222568056686623797L;/** parameter types of a proxy class constructor */private static final Class<?>[] constructorParams ={ InvocationHandler.class };/** a cache of proxy classes */private static final WeakCache<ClassLoader, Class<?>[], Class<?>>proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());/** the invocation handler for this proxy instance. */protected InvocationHandler h;private Proxy() {}//有参构造器创建代理对象(为InvocationHandler初始化)protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/* Look up or generate the designated proxy class. *///根据类加载器和接口数组获取动态代理类的字节码文件对象Class<?> cl = getProxyClass0(loader, intfs);/* Invoke its constructor with the designated invocation handler. */try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}//通过字节码文件对象获取构造器对象final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}//获取代理对象(通过反射调用有参构造器创建对象)return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}}//根据类加载器和接口数组获取动态代理类的字节码文件对象private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// If the proxy class defined by the given loader implementing the given interfaces exists, // this will simply return the cached copy; otherwise, it will create the proxy class// via the ProxyClassFactoryreturn proxyClassCache.get(loader, interfaces);}//省略了Proxy类中的其他内部类和方法
}

在自定义调用处理器中重写了InvocationHandler接口中的invoke方法,但是,该方法在何处被调用?接下来看一下本例的$Proxy0类的源码。

下面是本例的代理类$Proxy0的源码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Manager {private static Method m1;private static Method m0;private static Method m3;private static Method m2;static {try {m1 = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") });m0 = Class.forName("java.lang.Object").getMethod("hashCode",new Class[0]);m3 = Class.forName("com.tx.study.others.proxy.jdkProxy.Manager").getMethod("modify",new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString",new Class[0]);} catch (NoSuchMethodException nosuchmethodexception) {throw new NoSuchMethodError(nosuchmethodexception.getMessage());} catch (ClassNotFoundException classnotfoundexception) {throw new NoClassDefFoundError(classnotfoundexception.getMessage());}} //静态代码块结束//在代理类$Proxy0的构造方法中调用父类Proxy的构造方法,初始化InvocationHandlerpublic $Proxy0(InvocationHandler invocationhandler) {super(invocationhandler);}@Overridepublic final boolean equals(Object obj) {try {return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}@Overridepublic final int hashCode() {try {return ((Integer) super.h.invoke(this, m0, null)).intValue();} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}//自定义的InvocationHandler中重写的invoke方法就是在此处被调用的public final void modify() {try {super.h.invoke(this, m3, null);return;} catch (Error e) {} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}@Overridepublic final String toString() {try {return (String) super.h.invoke(this, m2, null);} catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}

本例的代理类$Proxy0继承了Proxy类,并实现了Manager接口(上面编写的需要被代理的接口),所以,在$Proxy0类中也实现了Manager接口中的modify()方法。在$Proxy0类中通过反射获取到了实现的modify()方法(由于此例中Manager接口中只有modify一个方法,所以就获取到了modify这一个方法,如果Manager接口中有多个方法,那么在$Proxy0类中通过反射将获取所有的实现方法),即Method m3方法。在$Proxy0类的modify方法中调用父类Proxy中h的invoke()方法,即InvocationHandler.invoke()方法。由于自定义调用处理器实现了InvocationHandler接口,并重写了invoke方法,由多态性可知,实际上调用的是重写后的invoke方法。所以,在上面的例子中,当创建了代理对象managerProxy,并通过代理对象调用managerProxy.modify()方法时,实际上是调用代理类$Proxy0中重写的modify方法,并在modify方法中调用了自定义调用处理器中重写的invoke方法。因此,自定义调用处理器中重写的invoke方法是在执行managerProxy.modify()方法时被调用的

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

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

相关文章

各类素材网站下载主题源码 CeoDocs v3.6 开心版

WordPress付费办公素材下载主题 – 各类素材网站下载主题 CeoDocs_v3.6_开心版CeoDocs主题是一款轻量级、 且简洁大气、付费素材下载类型主题&#xff0c;定位于办公素材行业&#xff0c;当然也适用于办公文档、PPT模板、设计素材、 图片素材、音效素材、视频素材各类素材网站…

巨好看的登录注册界面源码

展示效果 源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"widthdevic…

Redis-发布/订阅交互模式

文章目录 一、消息代理介绍二、Redis中客户端、服务器之间的交互模式介绍三、Redis发布/订阅交互模式的操作 一、消息代理介绍 “消息代理”&#xff08;Message Broker&#xff09;是一种软件组件&#xff0c;它在不同的应用程序之间传递消息。在Redis的上下文中&#xff0c;…

利用Kubernetes原生特性实现简单的灰度发布和蓝绿发布

部分借鉴地址: https://support.huaweicloud.com/intl/zh-cn/bestpractice-cce/cce_bestpractice_10002.html 1.原理介绍 用户通常使用无状态负载 Deployment、有状态负载 StatefulSet等Kubernetes对象来部署业务&#xff0c;每个工作负载管理一组Pod。以Deployment为例&#x…

18.04Ubuntu遇到Unable to locate package

解决办法&#xff1a; 要先升级你的apt Sudo apt-get update

《安全基石:等保测评的全方位解读》

在数字化转型的浪潮中&#xff0c;网络安全已成为企业生存与发展的核心议题。等保测评&#xff0c;作为我国网络安全等级保护制度的重要组成部分&#xff0c;不仅是企业安全的基石&#xff0c;更是推动企业高质量发展的关键。本文将全面解读等保测评的内涵、作用及其对企业的深…

(五)Spark大数据开发实战:灵活运用PySpark常用DataFrame API

目录 一、PySpark 二、数据介绍 三、PySpark大数据开发实战 1、数据文件上传HDFS 2、导入模块及数据 3、数据统计与分析 ①、计算演员参演电影数 ②、依次罗列电影番位前十的演员 ③、按照番位计算演员参演电影数 ④、求每位演员所有参演电影中的最早、最晚上映时间及…

SpringFactoriesLoader

1.什么是SPI (面试题) SPI全名Service Provider interface&#xff0c;翻译过来就是“服务提供接口”&#xff0c;再说简单就是提供某一个服务的接口&#xff0c; 提供给服务开发者或者服务生产商来进行实现。 Java SPI 是JDK内置的一种动态加载扩展点的实现。 这个机制在一…

Apifox 10月更新|测试步骤支持添加脚本和数据库操作、测试场景支持回收站、变量支持「秘密」类型

Apifox 新版本上线啦&#xff01; 看看本次版本更新主要涵盖的重点内容&#xff0c;有没有你所关注的功能特性&#xff1a; 自动化测试模块能力持续升级 测试步骤支持添加「脚本」和「数据库操作」 测试场景和定时任务支持回收站内恢复 定时任务支持设置以分钟频率运行 导入…

「C/C++」C++标准库之#include<fstream>文件流

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

liunx网络套接字 | 实现基于tcp协议的echo服务

前言&#xff1a;本节讲述linux网络下的tcp协议套接字相关内容。博主以实现tcp服务为主线&#xff0c;穿插一些小知识点。以先粗略实现&#xff0c;后精雕细琢为思路讲述实现服务的过程。下面开始我们的学习吧。 ps&#xff1a;本节内容建议了解网络端口号的友友们观看哦。 目录…

uni-app 运行HarmonyOS项目

1. uni-app 运行HarmonyOS项目 文档中心 1.1. HarmonyOS端 1.1.1. 准备工作 &#xff08;1&#xff09;下载DevEco Studio开发工具。   &#xff08;2&#xff09;在 DevEco Studio 中打开任意一个项目&#xff08;也可以新建一个空项目&#xff09;。   &#xff08;3&…

WPF+MVVM案例实战(十三)- 封装一个自定义消息弹窗控件(上)

文章目录 1、案例效果2、功能实现1、创建文件2、资源文件获取3、枚举实现3、弹窗实现1、界面样式实现2、功能代码实现4、总结1、案例效果 2、功能实现 1、创建文件 打开 Wpf_Examples 项目,我们在用户控件类库中创建一个窗体文件 SMessageBox.xaml,同时创建枚举文件夹 Enum…

Unity BesHttp插件修改Error log的格式

实现代码 找到插件的 UnityOutput.cs 然后按照需求替换为下面的代码即可。如果提示 void ILogOutput.Flush() { } 接口不存在&#xff0c;删除这行代码即可。 using Best.HTTP.JSON.LitJson; using System; using System.Collections.Generic; using UnityEngine; using Syst…

Python热化学固态化学模型模拟

&#x1f3af;要点 使用热化学方式&#xff0c;从材料项目数据库获得热力学数据构建固态材料无机合成模拟模型。固态反应网络是热力学相空间的模型&#xff0c;使得能够纳入简单的反应动力学行为。反应坐标图可视为加权有向图&#xff0c;其表示出热力学相空间的密集连接模型。…

详解软件设计中分库分表的几种实现以及应用示例

详解软件设计中分库分表的几种实现以及应用示例https://mp.weixin.qq.com/s?__bizMzkzMTY0Mjc0Ng&mid2247485108&idx1&sn8b3b803c120c163092c70fa65fe5541e&chksmc266aaa1f51123b7af4d7a3113fe7c25daa938a04ced949fb71a8b7773e861fb93d907435386#rd

简缩极化模型+简缩极化求解用优化的方法,也需要保证方程和未知数个数

一个定标器可以得到一个复数矢量&#xff0c;4个实数方程 而模型中我们有&#xff0c;每个定标器有不同的A和φ (两个实数)和相同的R和δc &#xff08;4个复数&#xff09;

多浏览器同步测试工具的设计与实现

在做Web兼容测试时&#xff0c;测试人员往往需要在不同浏览器上重复执行相同的操作。 现有自动化录制手段&#xff0c;其实是后置的对比&#xff0c;效率与反馈都存在延迟&#xff0c;执行过程相对是黑盒的&#xff0c;过程中如果测试人员没细化到具体的校验点&#xff0c;即使…

Google Recaptcha V2 简单使用

最新的版本是v3&#xff0c;但是一直习惯用v2&#xff0c;就记录一下v2 的简单用法&#xff0c;以免将来忘记了 首先在这里注册你域名&#xff0c;如果是本机可以直接直接填 localhost 或127.0.0.1 https://www.google.com/recaptcha/about/ 这是列子 网站密钥&#xff1a;是…

【初识Linux】

寻不到花的折翼枯叶蝶&#xff0c;永远也看不见凋谢............................................................................. 文章目录 前言 一、【基本指令】 1、ls 2、pwd 3、cd 4. touch 5.mkdir 6.rmdir 7、rm 8.man 9.cp 10、mv 11、cat 12、tac 13、more 14、le…