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,一经查实,立即删除!

相关文章

搜索引擎语法大全(Google、bing、baidu)

搜索引擎语法大全 搜索引擎语法通常指的是在搜索引擎中使用特定的运算符和语法来优化搜索结果。 提高搜索精度&#xff1a;使用特定的语法可以帮助用户更精确地找到相关信息&#xff0c;避免无关结果。例如&#xff0c;通过使用引号搜索确切短语&#xff0c;可以确保搜索结果包…

PySpark Standalone 集群部署教程

目录 1. 环境准备 1.1 配置免密登录 2. 下载并配置Spark 3. 配置Spark集群 3.1 配置spark-env.sh 3.2 配置spark-defaults.conf 3.3 设置Master和Worker节点 3.4 设配置log4j.properties 3.5 同步到所有Worker节点 4. 启动Spark Standalone集群 4.1 启动Master节点 …

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

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

【H2O2|全栈】JS进阶知识(三)jQuery(3)

目录 前言 开篇语 准备工作 $ 拷贝 浅拷贝和深拷贝 节点拷贝 $ 正则表达式 概念 创建 正则校验 匹配条件 边界符 范围匹配 量词 值类型 字符串正则匹配替换 $ 遍历 结束语 前言 开篇语 本系列博客主要分享JavaScript的进阶语法知识&#xff0c;本期为第三期…

flask+celery处理异步任务

celery是一个强大的分布式任务队列&#xff0c;在这里我们介绍一下它最基本的处理异步任务的功能&#xff0c;包含以下几个&#xff1a; 创建Celery实例 创建一个异步任务 查询异步任务的信息 取消异步任务 使用的环境是flask3.0Celery5.4 1. 创建Celery实例 celery通过…

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

展示效果 源码 <!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…

SpringBoot集成Shiro+Jwt+Redis

概述 首先需要知道为什么使用 ShiroJwtRedis 进行登录认证和权限控制。 1. 为什么用Shiro&#xff1f; 主要用的是 shiro 里面的登录认证和权限控制功能。 2. 为什么用Jwt&#xff1f; Shiro 默认的 Session 机制来帮助实现权限管理&#xff0c;用于维护用户的状态信息。而 …

Docker Compose --- 管理多容器应用

用于定义和运行多容器 Docker 应用程序。通过 Compose&#xff0c;用户可以使用 YAML 文件来配置应用程序的服务、网络和卷等资源 简化多容器的管理和部署过程 以下compose.yaml示例展示如何部署两个服务WordPress 和 MySQL的环境 version: 3.8 # 指定 Docker Compose 文件的…

18.04Ubuntu遇到Unable to locate package

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

Django ORM详解:外键使用(外键逻辑关联)与查询优化

Django数据库迁移 # 创建迁移 python manage.py makemigrations your_app_name # 应用迁移 python manage.py migrate # 查看迁移状态 python manage.py showmigrations # 回滚迁移 python manage.py migrate your_app_name 0001 # 修改表后,删除迁移记录和表删除迁移记录后重…

redis做缓存,mysql的数据怎么与redis进行同步(双写一致性)

基于业务做选择,强一致性和允许延迟再加消息队列 强一致性:当修改了数据库的数据同时更新缓存的数据,缓存和数据库的数据保持一致 读操作:缓存命中,直接返回数据,缓存没有命中,查询数据库,写入缓存,设定过期时间 写操作:延迟双删 :先删除缓存,修改数据库,等待延迟(数据库主从节…

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

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

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

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

一些CSS的基础知识点

写在前面 Cascading Style Sheets&#xff08;CSS&#xff09;是用于描述网页样式和布局的标记语言。它允许开发者将内容与表示分离&#xff0c;从而使得网页的设计和结构更加清晰和易于维护。本文将详细介绍CSS的基础知识点&#xff0c;帮助初学者快速掌握CSS的核心概念和应用…

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;本节内容建议了解网络端口号的友友们观看哦。 目录…