学习设计模式之代理模式,但是宝可梦

前言

作者在准备秋招中,学习设计模式,做点小笔记,用宝可梦为场景举例,有错误欢迎指出。

代码同步更新到 github ,要是点个Star您就是我的神

目录

  • 前言
  • 代理模式
    • 1.情景模拟
      • 1.1静态代理
        • 优点
        • 局限
      • 1.2 动态代理
    • 2.应用
    • 3.局限
    • 4.解决方案CGLIB
      • 踩坑注意!!

代理模式

代理模式是一种结构型设计模式。
对于代理模式,其实不难理解,就是甲乙双方在做一件事的时候,有一个中间人作为代理。
甲委托代理,代理和乙对接。生活中的例子就是租房、房东、中介的关系,租房和房东作为甲乙双方,通过中介完成业务。

在代码开发中,代理模式主要用于控制对对象的访问,通过中介,避免调用者和提供方法的对象直接接触。

1.情景模拟

代理模式的主要抽象思路就是:A和B的直接交互变为A和B通过C来交互。
在宝可梦没血的时候,我们会选择对其进行治疗,我们可以通过背包里的伤药(直接接触),或者宝可梦中心(通过代理)。
于是我们首先抽象出代理模式的第一个概念: Subject抽象主题——回血,以及Real Subject真实主题——实现类

/*** 休息的地方* 提供回血方法*/
public interface Rest {void heal();
}/*** 回血的具体实现类*/
public class RestImpl implements Rest{@Overridepublic void heal() {System.out.println("治疗...");}
}

宝可梦中心作为代理类,自然要先懂得业务,所以代理类也要实现对应的接口:

/*** 静态代理类*/
public class PokemonCenterProxy implements Rest{// 被代理的角色private Rest rest;public PokemonCenterProxy(Rest rest) {this.rest = rest;}@Overridepublic void heal() {System.out.println("在宝可梦中心...");rest.heal();}
}

1.1静态代理

这样,当主角(即程序调用者)想要回血的时候,我们可以直接找到宝可梦中心(代理类):

public class StaticProxyDemo {public static void main(String[] args) {// 真实主题RestImpl rest = new RestImpl();// 传入代理类PokemonCenterProxy pokemonCenterProxy = new PokemonCenterProxy(rest);// 代理类来执行方法pokemonCenterProxy.heal();}
}
在宝可梦中心...
治疗...

优点

可以发现,我们仍然调用了原有对象的heal()方法,但是我们在此基础上,完成了方法的扩展。
即我们在没有修改原有实现类的基础上,实现了新增执行前后的动作的功能,我们甚至可以:

    @Overridepublic void heal() {System.out.println("在宝可梦中心...");rest.heal();System.out.println("按摩SPA");}

可能大家看到这里就比较眼熟,这不可以实现日志功能吗?没错,我们可以在方法执行前后织入另外的行为。
这样做的局限也很明显。

局限

从代理类的代码可以看出,我提供了一个构造方法,传入Rest接口的实现类,这样避免了有新的实现类的时候要再写对应的新的静态代理类的情况。
这样做的问题在于,我们仍然把被代理类给暴露出来了,仍然要先new一个Rest的实现类。
其次,如果Rest接口的方法增多,作为继承了接口的静态代理类,仍要实现每个方法,可能之间有大量的冗余代码。

所以要解决以上局限,动态代理是个更好的选择。

1.2 动态代理

Java对动态代理提供了支持。
要实现动态代理,第一步是实现内置的InvocationHandler接口,重写Invoke方法

public class DynamicProxy implements InvocationHandler {private Rest rest;public DynamicProxy(Rest rest) {this.rest = rest;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("进入宝可梦中心");Object invoke = method.invoke(rest, args);System.out.println("售后服务");return invoke;}
}

基于反射,程序在运行途中获得了类的方法(invoke的参数中的method),方法的参数等信息。
被代理的类,运行的所有方法都被替换为这个invoke方法,真正执行原方法的逻辑是在method.invoke(rest, args);
所以这一行的前后,就可以写代理类在执行方法之前/之后的逻辑。

public class DynamicProxyDemo {public static void main(String[] args) {// 需要被代理的对象Rest rest = new RestImpl();// 以此创建代理类DynamicProxy dynamicProxy = new DynamicProxy(rest);ClassLoader classLoader = rest.getClass().getClassLoader();Rest o = (Rest) Proxy.newProxyInstance(classLoader, new Class[]{Rest.class}, dynamicProxy);o.heal();}
}

动态代理的核心就在于Proxy类,在动态代理中,创建真实对象的实例是通过Proxy的newProxyInstance方法。
其参数有三:

  • 类加载器:接口类的类加载器,直接调用api获取
  • 实现的接口的数组: new Class[]{...}是创建数组并赋值的语法,里面传入要实现的接口的类对象
  • 实现了InvocationHandler的对象,用来执行逻辑

然后用Proxy类创建出的对象调用方法,就可以实现代理类中实现的逻辑,无论Rest接口有多少方法,我们都不需要一一去实现。
相应地,有新业务接口的时候,也不用新增代理类。除非你有不同的代理逻辑(即invoke方法里的逻辑),否则都不需要新增代码。
运行结果:

进入宝可梦中心
治疗...
售后服务

2.应用

Spring框架中AOP就是基于动态代理,以此来实现一种切面逻辑。
应用场景包括:日志记录、权限控制。

3.局限

通过Proxy类来实现动态代理有一个最主要的局限:只能代理接口类。
并且通过反射来实现的性能开销比较大。

4.解决方案CGLIB

通过CGLIB实现动态代理。CGLIB可以实现对类的动态代理,并且实现原理是生成新的字节码类。
第一步:引入依赖

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

第二部:写要代理的类(这里可以不是接口了)

public class Heal {public void heal(){System.out.println("HP+++");}
}

第三步:创建代理类

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 代理逻辑System.out.println("Pre");Object o1 = methodProxy.invokeSuper(o, objects);System.out.println("Suf");return o1;}
}

第四步:创建代理对象

public class Demo {public static void main(String[] args) {// 创建Enhancer类,类似于Proxy类Enhancer enhancer = new Enhancer();// 设置目标类enhancer.setSuperclass(Heal.class);// 设置拦截器enhancer.setCallback(new MyMethodInterceptor());// 创建代理对象Heal proxy = (Heal)enhancer.create();// 执行原有方法proxy.heal();}
}

踩坑注意!!

如果跟我一样用的Java17, 那么运行的时候会出现:

Exception in thread "main" java.lang.ExceptionInInitializerErrorat com.example.springbootdemo.proxyCglib.Demo.main(Demo.java:7)
Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @14899482at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:464)at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:96)at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:94)at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:119)at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)at net.sf.cglib.core.KeyFactory$Generator.create(KeyFactory.java:221)at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:174)at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:153)at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:73)... 1 more

解决方法和原因: https://blog.csdn.net/guoshengkai373/article/details/127319933

只能换到低版本的JDK,我试过把CGLIB依赖版本弄到最新,也没用。

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

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

相关文章

读懂AUTOSAR,之CAN Driver L-PDU发送和“重入问题”

1. L-PDU发送 L-PDU传输时,Can模块将L-PDU内容ID和数据长度转换为硬件特定格式(如果需要),并触发传输。 [SWS_Can_00059] CAN到内存的数据映射定义为首先发送的CAN数据字节为数组元素0,最后发送的CAN数据字节为数组元素7或63(在CAN FD的情况下)。(SRS_SPAL_12063)[S…

CSDN每日一练 |『生命进化书』『订班服』『c++难题-大数加法』2023-09-06

CSDN每日一练 |『生命进化书』『订班服』『c++难题-大数加法』2023-09-06 一、题目名称:生命进化书二、题目名称:订班服三、题目名称:c++难题-大数加法一、题目名称:生命进化书 时间限制:1000ms内存限制:256M 题目描述: 小A有一本生命进化书,以一个树形结构记载了所有生…

leetcode分类刷题:易混题辨析一、209. 长度最小的子数组 vs 560. 和为K的子数组

1、刷题慢慢积累起来以后&#xff0c;遇到相似的题目时&#xff0c;会出现算法思路混淆了 2、这两道题都是对连续子数组加和进行考察&#xff0c;细节区别在于数组元素在209. 长度最小的子数组为正整数&#xff08;窗口增加元素递增&#xff0c;减少元素递减&#xff09;&#…

linux 进程管理命令

进程管理命令 查看进程命令 ps命令 显示系统上运行的进程列表 # 查看系统中所有正在运行的系统ps aux# 获取占用内存资源最多的10个进程&#xff0c;可以使用如下命令组合&#xff1a;ps aux|head -1;ps aux|grep -v PID|sort -rn -k 4|head# 获取占用CPU资源最多的10个进程&am…

PXE批量装机

目录 前言 一、交互式 &#xff08;一&#xff09;、搭建环境 &#xff08;二&#xff09;、配置dhcp服务 &#xff08;三&#xff09;、FTP服务 &#xff08;四&#xff09;、配置TFTP服务 &#xff08;五&#xff09;、准备pxelinx.0文件、引导文件、内核文件 &#…

C#开发的OpenRA游戏之信标按钮

前面已经分析了两个按钮:变卖和维修,接着下来就是分析信标按钮,这个按钮使用是比较少,但是对于多人游戏时,使用这个信号就很方便同盟军过来查看和帮助了,相当于一个朋友之间共同查看的地址。当你经过同盟标记的标志时,就会听到beacon detected,检测到信标,这就是你的盟…

如何系统地学习 JavaScript?

前言 在学习JavaScript前需要先将Html和Css的相关知识点弄清楚&#xff0c;Js的很多操作是要结合Html和Css&#xff0c;下面我总结了Html、Css和Js的相关学习知识点供参考&#xff0c;希望对你有所帮助喔~ Html 文档学习 【HTML 】w3school教程 :https://www.w3school.com.…

【SpringBoot入门】详解@Autowired的使用

【SpringBoot入门】详解Autowired的使用 在构造函数中使用Autowired的注解的简单实例除了构造函数&#xff0c;Autowired注解还可以用在哪些地方&#xff1f;方法上&#xff1a;字段上&#xff1a;Setter方法上&#xff1a;构造函数参数上&#xff1a; 在构造函数中使用Autowir…

Java分别用BIO、NIO实现简单的客户端服务器通信

分别用BIO、NIO实现客户端服务器通信 BIONIONIO演示&#xff08;无Selector&#xff09;NIO演示&#xff08;Selector&#xff09; 前言&#xff1a; Java I/O模型发展以及Netty网络模型的设计思想 BIO Java BIO是Java平台上的BIO&#xff08;Blocking I/O&#xff09;模型&a…

树状数组,线段树,容斥,P3801 红色的幻想乡

P3801 红色的幻想乡 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目背景 蕾米莉亚的红雾异变失败后&#xff0c;很不甘心。 题目描述 经过上次失败后&#xff0c;蕾米莉亚决定再次发动红雾异变&#xff0c;但为了防止被灵梦退治&#xff0c;她决定将红雾以奇怪的阵势释…

监控基本概念

监控&#xff1a;这个词在不同的上下文中有不同的含义&#xff0c;在讲到监控MySQL或者监控Redis时&#xff0c;这里只涉及数据采集和可视化&#xff0c;不涉及告警引擎和事件处理。要是监控系统的话&#xff0c;不但包括数据采集和可视化&#xff0c;而且也包括告警和事件发送…

【深入解析spring cloud gateway】02 网关路由断言

一、断言(Predicate)的意义 断言是路由配置的一部分&#xff0c;当断言条件满足&#xff0c;即执行Filter的逻辑&#xff0c;如下例所示 spring:cloud:gateway:routes:- id: add_request_header_routeuri: https://example.orgpredicates:- Path/red/{segment}filters:- AddR…

Python 编程秘籍:掌握这些,你还会担心写不出高效代码吗?

建议点击下面原文链接&#xff0c;效果更佳 Python 编程秘籍&#xff1a;掌握这些&#xff0c;你还会担心写不出高效代码吗&#xff1f; 在 Python 编程的世界里&#xff0c;高手们常常掌握着一些不为人知的独门绝技。这些技巧不仅让他们在编程时如虎添翼&#xff0c;还让他们…

Kafka3.0.0版本——文件存储机制

这里写木目录标题 一、Topic 数据的存储机制1.1、Topic 数据的存储机制的概述1.2、Topic 数据的存储机制的图解1.3、Topic 数据的存储机制的文件解释 二、Topic数据的存储位置示例 一、Topic 数据的存储机制 1.1、Topic 数据的存储机制的概述 Topic是逻辑上的概念&#xff0c…

ASP.NET Core 中基于 Controller 的 Web API

基于 Controller 的 Web API ASP.NET Wep API 的请求架构 客户端发送Http请求&#xff0c;Contoller响应请求&#xff0c;并从数据库读取数据&#xff0c;序列化数据&#xff0c;然后通过 Http Response返回序列化的数据。 ControllerBase 类 Web API 的所有controllers 一般…

uni-app app端.m3u8类型流的播放

1.开发环境&#xff1a;HBuilderX3.8.7、uni-app、vue2.0、view2.0、uni-ui 2.实现通过web-view 嵌入H5页面&#xff0c;进行视频流自动播放。 注意事项&#xff1a; 如果只是在android端可以直接使用.flv格式的视频流&#xff1b; 如果App需要支持ios就可以考虑一下播放.m3u8格…

植物大战僵尸植物表(二)

前言 此文章为“植物大战僵尸”专栏中的第007刊&#xff08;2023年9月第六刊&#xff09;。 提示&#xff1a; 1.用于无名版&#xff1b; 2.用于1代&#xff1b; 3.pvz指植物大战僵尸&#xff08;Plants VS Zonbies)。 植物大战僵尸植物表 土豆雷窝瓜火炬树桩火爆辣椒杨…

Spring MVC @Controller和@RequestMapping注解

Controller 注解 Controller 注解可以将一个普通的 Java 类标识成控制器&#xff08;Controller&#xff09;类&#xff0c;示例代码如下。 package net.biancheng.controller; import org.springframework.stereotype.Controller; Controller public class IndexController …

数学建模:拟合算法

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 数学建模&#xff1a;拟合算法 文章目录 数学建模&#xff1a;拟合算法拟合算法多项式拟合非线性拟合cftool工具箱的使用 拟合算法 根据1到12点间的温度数据求出温度与时间之间的近似函数关系 F ( t ) F(…