03-JAVA设计模式-代理模式详解

代理模式

什么是代理模式

Java代理模式是一种常用的设计模式,主要用于在不修改现有类代码的情况下,为该类添加一些新的功能或行为。代理模式涉及到一个代理类和一个被代理类(也称为目标对象)。代理类负责控制对目标对象的访问,并可以在访问前后添加一些额外的操作。

核心作用:

  • 通过代理,控制对对象的访问。
  • 可以详细控制访问某个(某类)对象的方法,在调用这个方法茜做前置处理,调用这个方法后做后置处理(即AOP的微观实现)。

核心角色:

  • 抽象角色:定义代理角色和真实角色的公共对外方法
  • 真实角色:实现抽象角色,定义真实角色所需要实现的业务逻辑,供代理角色调用(关注真正的业务逻辑)。
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色业务逻辑方法,来实现抽象角色,并可附加自己的操作。

应用场景:

  • 安全代理:屏蔽对真实角色的直接访问
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟代理:先加载轻量级代理对象,真正需要再加载真实对象。

Java代理模式主要分为两种:静态代理和动态代理。

静态代理

静态代理是在代码中手动定义代理类,代理类与目标对象实现相同的接口,并在代理类中持有目标对象的引用。
当调用代理类的方法时,代理类会在调用目标对象方法的前后添加一些额外的逻辑。

案例

明星个人接活,与代理人接活
明星个人接活:

  1. 面谈
  2. 合同起草
  3. 签字
  4. 前期准备
  5. 唱歌
  6. 收尾款

通过代理人接活:

  1. 面谈
  2. 合同起草
  3. 签字
  4. 前期准备
  5. 唱歌(明星负责)
  6. 收尾款

UML

在这里插入图片描述

1 提供一个抽象角色,定义代理角色和真实角色的公共对外方法。
2 定义真实角色,实现抽象角色的方法(当然真实角色也可以定义一些自己的方法)
3 定义一个代理角色,实现抽象角色方法,通过传入真实角色调用真实角色的方法
4 使用时,创建代理角色及真实角色,通过代理角色调用相应方法

实现代码

Start.java

// 抽象角色
// 明星:具备唱歌能力
public interface Start {void sing();
}

RealStart.java

// 真实角色
// * 实现明星唱歌
public class RealStart implements Start{@Overridepublic void sing() {System.out.println("周杰伦唱歌");}
}

ProxyStart.java

// 代理明星
// * 通过实现Star方法代理明星唱歌
public class ProxyStart implements Start{// 代理明星private Start start;// 通过构造器传入设置代理明星public ProxyStart(Start start) {this.start = start;}// 重写唱歌,代理实现唱歌@Overridepublic void sing() {System.out.print("5. ");start.sing();}public void interview(){System.out.println("1. 面谈");}public void contractDrafting(){System.out.println("2. 合同起草");}public void signature(){System.out.println("3. 签字");}public void preliminaryPreparation(){System.out.println("4. 前期准备");}public void closingPayment(){System.out.println("6. 收尾款");}
}

TestClient.java

public class TestClient {public static void main(String[] args) {// 创建真实角色RealStart realStart = new RealStart();// 创建代理角色ProxyStart proxyStart = new ProxyStart(realStart);// 方法调用// 面谈proxyStart.interview();// 合同起草proxyStart.contractDrafting();// 签字proxyStart.signature();// 前期准备proxyStart.preliminaryPreparation();// 唱歌proxyStart.sing();// 收尾款proxyStart.closingPayment();}
}

执行结果:

在这里插入图片描述

JDK实现动态代理

动态代理是在运行时动态生成代理类。相对于静态代理,将抽象角色(接口)中声明的所有方法都被转移到调用处理器一个集中的地方中处理,这样可以更加灵活和统一的处理众多的方法。

通过JDK提供了java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现动态代理。

  • java.lang.reflect.Proxy
    作用:动态生成代理类和对象
  • java.lang.reflect.InvocationHandler(处理器接口)
    • 可以通过invoke方法实现对真实角色的代理访问。
    • 每次通过Proxy生成代理类对象时都要指定对应的处理器对象。

UML

在这里插入图片描述

使用JD实现动态代理时,我们需要创建一个实现了InvocationHandler接口的处理器类,并在该类中实现invoke方法。
然后,我们可以使用Proxy.newProxyInstance方法创建一个代理类的实例。
当调用代理类的方法时,实际上会调用处理器类的invoke方法。

代码实现

Start.java

// 抽象角色
interface Start {void sing();
}

RealStart.java

// 真实角色
// * 实现明星唱歌
public class RealStart implements Start {@Overridepublic void sing() {System.out.println("周杰伦唱歌");}
}

StartHandle.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;// JDK 实现动态代理
public class StartHandle implements InvocationHandler {// 代理明星private Start start;// 通过构造器传入设置代理明星public StartHandle(Start start) {this.start = start;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理方法执行之前");System.out.println("1. 面谈");System.out.println("2. 合同起草");System.out.println("3. 签字");System.out.println("4. 前期准备");System.out.print("5. ");method.invoke(start,args);System.out.println("代理方法执行之后");System.out.println("6. 收尾款");return true;}
}

TestClient.java

import java.lang.reflect.Proxy;// 动态代理测试
public class TestClient {public static void main(String[] args) {// 创建真实角色RealStart realStart = new RealStart();// 创建动态代理处理类StartHandle startHandle = new StartHandle(realStart);Start proxy = (Start) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Start.class}, startHandle);// 处理业务proxy.sing();}
}

执行结果:

在这里插入图片描述

通过接口实现代理

UML

在这里插入图片描述

1 定义一个抽象角色Start,接口中定义抽象sing方法

2 真实角色实现Star接口,实现sing方法

3 定义一个中转处理接口MethodHandler,接口中定义invoke方法(后续处理,后续代理类方法全部先调用该方法进行处理)

4 定义处理类(具体实现)StartMethodHandle,实现invoke方法,通过反射调用真实角色对应方法

5 代理对象StartProxy,实现Start接口,重写接口,所有接口调用处理类(具体实现)的invoke方法,并传入真实角色

注意:JDK 实现代理的逻辑与其类似

具体实现

Start.java

// 抽象角色
// * 明星:具备唱歌能力
public interface Start {void sing() throws Throwable;
}

RealStart.java

// 真实角色
// * 实现明星唱歌
public class RealStart implements Start {@Overridepublic void sing() throws Throwable {System.out.println("周杰伦唱歌");}
}

MethodHandler.java

// 中转处理接口
public interface MethodHandler {Object invoke(Object self, String methodName, Object[] args) throws Throwable;
}

StartMethodHandle.java

import java.lang.reflect.Method;
// 处理类(具体实现)
public class StartMethodHandle implements MethodHandler {@Overridepublic Object invoke(Object self, String methodName, Object[] args) throws Throwable {System.out.println("代理执行之前");Method method = self.getClass().getMethod(methodName);Object invoke = method.invoke(self, args);System.out.println("代理执行之后");return invoke;}
}

StartProxy.java

// 代理对象
public class StartProxy implements Start {private MethodHandler methodHandler;private Start start;public StartProxy(MethodHandler methodHandler, Start start) {this.methodHandler = methodHandler;this.start = start;}@Overridepublic void sing() throws Throwable {methodHandler.invoke(start,"sing",null);}
}

TestClient3.java

public class TestClient3 {public static void main(String[] args) {// 创建真实角色Start start = new RealStart();// 创建代理处理类StartMethodHandle startMethodHandle = new StartMethodHandle();// 创建代理对象StartProxy startProxy = new StartProxy(startMethodHandle, start);try {startProxy.sing();} catch (Throwable e) {e.printStackTrace();}}
}

执行结果:

在这里插入图片描述

javassist字节码实现动态代理

Javassist 是一个开源的分析、编辑和创建 Java 字节码的库。它主要用于操作 Java 字节码,但也可以用来创建动态代理。

需要引入依赖如下

<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.27.0-GA</version>
</dependency>

实现代码

Start.java

// 抽象角色
// * 明星:具备唱歌能力
public interface Start {void sing();
}

RealStart.java

/*** 真实角色* 实现明星唱歌** @author Anna.* @date 2024/4/9 10:10*/
public class RealStart implements Start {@Overridepublic void sing() {System.out.println("周杰伦唱歌");}
}

MethodHandler.java

// 中转处理接口
public interface MethodHandler {Object invoke(Object self, String methodName, Object[] args) throws Exception;
}

StartMethodHandle.java

// 处理类
public class StartMethodHandle implements MethodHandler {@Overridepublic Object invoke(Object self, String methodName, Object[] args) throws Exception {System.out.println("代理执行之前");Method method = self.getClass().getMethod(methodName);Object invoke = method.invoke(self, args);System.out.println("代理执行之后");return invoke;}
}

JavassistProxyFactory.java

import javassist.*;import java.lang.reflect.Constructor;
import java.lang.reflect.Method;public class JavassistProxyFactory {/*** 创建代理类:相当于实现demo3中StartProxy** @param target   真实角色* @param methodHandler  处理类(具体实现)* @return T* @author Anna.* @date 2024/4/9 17:26*/public static <T> T createProxy(T target, MethodHandler methodHandler) throws Exception {// 创建 ClassPool,它是 Javassist 的核心类,用于处理类ClassPool pool = ClassPool.getDefault();// 获取目标类的 CtClass 对象CtClass ctClass = pool.get(target.getClass().getName());// 获取目标类的 CtClass 对象CtClass methodHandlerCtClass = pool.get(methodHandler.getClass().getName());// 创建代理类的名称String proxyClassName = target.getClass().getName() + "$Proxy";// 创建代理类的 CtClass 对象CtClass proxyCtClass = pool.makeClass(proxyClassName);// 设置代理类继承自目标类 用于实现其所有方法proxyCtClass.setSuperclass(ctClass);// 添加字段CtField aField = new CtField(ctClass, "a", proxyCtClass);aField.setModifiers(javassist.Modifier.PRIVATE);CtField bField = new CtField(methodHandlerCtClass, "b", proxyCtClass);bField.setModifiers(javassist.Modifier.PRIVATE);// 添加属性proxyCtClass.addField(aField);proxyCtClass.addField(bField);// 创建一个构造函数来初始化name属性CtConstructor constructor = new CtConstructor(new CtClass[]{ctClass,methodHandlerCtClass}, proxyCtClass);constructor.setModifiers(javassist.Modifier.PUBLIC);// $0 this $1 第一个参数 $2 第二个参数 依次类推constructor.setBody("{this.a = $1;this.b = $2;}");proxyCtClass.addConstructor(constructor);// 遍历目标类的所有方法,并创建相应的方法在代理类中for (Method method : target.getClass().getDeclaredMethods()) {// 创建方法CtMethod ctMethod = new CtMethod(pool.get(method.getReturnType().getName()), method.getName(), getParameterTypes(method), proxyCtClass);ctMethod.setModifiers(Modifier.PUBLIC);// 设置方法体,调用 MethodHandler 的 invoke 方法ctMethod.setBody("{ b.invoke(a, \"" + method.getName() + "\", $args); }");// 添加方法到代理类proxyCtClass.addMethod(ctMethod);}// 反射Class<?>  aClass = proxyCtClass.toClass();Constructor<?>[] constructors = aClass.getConstructors();Object obj = constructors[0].newInstance(target, methodHandler);// 创建代理类的实例return (T) obj;}/*** 获取方法参数列表** @param method* @return javassist.CtClass[]* @author Anna.* @date 2024/4/9 17:40*/private static CtClass[] getParameterTypes(Method method) throws NotFoundException {CtClass[] ctClasses = new CtClass[method.getParameterTypes().length];for (int i = 0; i < method.getParameterTypes().length; i++) {ctClasses[i] = ClassPool.getDefault().get(method.getParameterTypes()[i].getName());}return ctClasses;}}

TestClient4.java

public class TestClient4 {public static void main(String[] args) throws Exception {Start realStart = new RealStart();StartMethodHandle startMethodHandle = new StartMethodHandle();Start proxy = (Start) JavassistProxyFactory.createProxy(realStart, startMethodHandle);proxy.sing();}
}

执行结果:

在这里插入图片描述

gitee源码

git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git

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

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

相关文章

ASP.NET Core 标识(Identity)框架系列(一):如何使用 ASP.NET Core 标识(Identity)框架创建用户和角色?

前言 ASP.NET Core 内置的标识&#xff08;identity&#xff09;框架&#xff0c;采用的是 RBAC&#xff08;role-based access control&#xff0c;基于角色的访问控制&#xff09;策略&#xff0c;是一个用于管理用户身份验证、授权和安全性的框架。 它提供了一套工具和库&…

openGauss学习笔记-259 openGauss性能调优-使用Plan Hint进行调优-指定不使用全局计划缓存的Hint

文章目录 openGauss学习笔记-259 openGauss性能调优-使用Plan Hint进行调优-指定不使用全局计划缓存的Hint259.1 功能描述259.2 语法格式259.3 示例 openGauss学习笔记-259 openGauss性能调优-使用Plan Hint进行调优-指定不使用全局计划缓存的Hint 259.1 功能描述 全局计划缓…

Windows下MySQL服务启动常见的两种方式,完美适配Mysql5.7,MySql8.0

文章目录 一、图形界面下启动mysql服务二、在命令行重新启动mysql服务三、推荐阅读四、源码获取&#xff1a; Windows系统下&#xff0c;MySQL服务的启动&#xff0c;常见的两种启动方式如下&#xff1a; 一、图形界面下启动mysql服务 在图形界面下启动mysql服务的流程如下&am…

雄安建博会:中矿雄安新区的总部开工建设

中矿落位雄安&#xff1a;助力国家战略与新区发展 雄安新区&#xff0c;作为中国未来发展的重要战略支点&#xff0c;正迎来一系列央企总部的疏解与建设。最近&#xff0c;中国矿产资源集团有限公司&#xff08;简称“中矿”&#xff09;在雄安新区的总部项目正式开工建设&…

C++设计模式:原型模式(八)

1、定义与动机 定义&#xff1a;使用原型实例指定创建对象的种类&#xff0c;然后通过拷贝这些原型来创建新的对象。 动机&#xff1a; 在软件系统中&#xff0c;经常面临着“某些结构复杂的对象”的创建工作&#xff1b;由于需求的变化&#xff0c;这些对象经常面临着剧烈的变…

BUUCTF刷题十一道(12)附-SSTI专题二

文章目录 学习文章[CISCN2019 华东南赛区]Web11【存疑】[RootersCTF2019]I_<3_Flask 学习文章 SSTI-服务端模板注入漏洞 flask之ssti模板注入从零到入门 CTFSHOW SSTI篇-yu22x SSTI模板注入绕过&#xff08;进阶篇&#xff09;-yu22x SSTI模板注入学习-竹言笙熙 全部总结看最…

Chatgpt掘金之旅—有爱AI商业实战篇|在线辅导业务|(十一)

演示站点&#xff1a; https://ai.uaai.cn 对话模块 官方论坛&#xff1a; www.jingyuai.com 京娱AI 一、AI技术创业在线辅导业务有哪些机会&#xff1f; 人工智能&#xff08;AI&#xff09;技术作为当今科技创新的前沿领域&#xff0c;为创业者提供了广阔的机会和挑战。随着…

谷歌浏览器快捷键, VScode 快捷键

谷歌浏览器快捷键 谷歌浏览器跳转标签页的方式&#xff1a; control Tab 跳转下一个标签页 control shift tab 上一个标签页 command 1-8 跳转对应的标签页&#xff0c;而command 9 则是跳转最后一个标签页 Previous Tab 插件实现谷歌浏览器两个tab页来回切换。快捷键为…

稀碎从零算法笔记Day44-LeetCode:整数转罗马数字

题型&#xff1a;贪心、模拟 链接&#xff1a; 12. 整数转罗马数字 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 …

网络名称空间在Linux虚拟化技术中的位置

网络名称空间&#xff08;Network Namespaces&#xff09;是Linux内核特性之一&#xff0c;提供了隔离网络环境的能力&#xff0c;使得每个网络名称空间都拥有独立的网络设备、IP地址、路由表、端口号范围以及iptables规则等。这一特性在Linux虚拟化技术中占据了核心位置&#…

天池医疗AI大赛[第一季] Rank5解决方案

一、赛题说明 数据格式 本次大赛数据集包含数千份高危患者的低剂量肺部CT影像&#xff08;mhd格式&#xff09;数据&#xff0c;每个影像包含一系列胸腔的多个轴向切片。每个影像包含的切片数量会随着扫描机器、扫描层厚和患者的不同而有差异。原始图像为三维图像。这个三维图…

Win10系统下的EDGE浏览器启用IE模式

Win10系统下的EDGE浏览器目前已弃用IE内核&#xff0c;这样在访问某些较老的网站会有兼容性问题&#xff0c;本文记录了在EDGE浏览器中启用IE模式的操作方法。 一、启用EDGE浏览器的IE模式 要打开Internet Explorer模式&#xff0c;执行以下步骤: 1、在Microsoft Edge的地址栏…

TryHackMe - HTTP Request Smuggling

学完、打完后的复习 HTTP 1 这部分比较简单&#xff0c;直接略过 HTTP2请求走私 首先要了解HTTP2的结构&#xff0c;与HTTP1之间的一些差异 HTTP2中不再使用CRLF来作为字段的边界限定&#xff0c;而是在二进制中直接通过长度、名字、值长度、值&#xff0c;来确认边界 而这…

数据仓库的概念和作用?如何搭建数据仓库?

随着企业规模的扩大和数据量的爆炸性增长&#xff0c;有效管理和分析海量数据成为企业数字化转型的关键。而在互联网的普及过程中&#xff0c;信息技术已深入渗透各行业&#xff0c;逐渐融入企业的日常运营。然而&#xff0c;企业在信息化建设中面临了一系列困境和挑战&#xf…

https的配置和使用(以腾讯云为例)

1、注册域名 2、获取证书 3、下载证书 下载下来的证书所有格式 4、在服务器上下载nginx并配置 nginx的配置文件 如下 server {listen 80;listen 443 ssl;server_name delegate.letspiu.net.cn;ssl on; #开启ssl#指定证书位置ssl_certificate /etc/ss…

JRT判断数据是否存在优化

有一种业务情况类似下图&#xff0c;质控能做的项目是仪器关联的项目。这时候维护质控物时候开通项目时候要求加载仪器项目里面的项目&#xff08;没有开通的子业务数据的部分&#xff09;。对右边已经开通的部分要求加载仪器项目里面的项目&#xff08;有开通业务子数据的部分…

Python从0到100(十二):函数的定义及模块

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

SQL注入sqli_libs靶场第一题

第一题 联合查询 1&#xff09;思路&#xff1a; 有回显值 1.判断有无注入点 2.猜解列名数量 3.判断回显点 4.利用注入点进行信息收集 爆用户权限&#xff0c;爆库&#xff0c;爆版本号 爆表&#xff0c;爆列&#xff0c;爆账号密码 2&#xff09;解题过程&#xff1…

GitHub 仓库 (repository) Pulse - Contributors - Network

GitHub 仓库 [repository] Pulse - Contributors - Network 1. Pulse2. Contributors3. NetworkReferences 1. Pulse 显示该仓库最近的活动信息。该仓库中的软件是无人问津&#xff0c;还是在火热地开发之中&#xff0c;从这里可以一目了然。 2. Contributors 显示对该仓库进…

easyExcel - 动态复杂表头的编写

目录 前言一、情景介绍二、问题分析三、代码实现方式一&#xff1a;head 设置方式二&#xff1a;模板导出方式三&#xff1a;自定义工具类 前言 Java-easyExcel入门教程&#xff1a;https://blog.csdn.net/xhmico/article/details/134714025 之前有介绍过如何使用 easyExcel&…