GoF 代理模式

2024-09-03 03:44
文章标签 模式 代理 gof

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

代理模式的理解

代理模式,就是自己做不了,需要别人来代理,代替自己来完成。最终这个行为还是要发生,只不过不是由自己来完成,而是由别人代理完成,只是对于客户其他人来说感受不到

代理模式的作用:

  1. 当一个对象需要受到保护时,可以考虑使用代理模式去完成某个行为。
  2. 需要给某个对象的功能进行增强时,可以考虑找一个代理进行增强。
  3. A 对象和 B 对象无法直接进行交互时,也可以使用代理模式来解决。

代理模式中的三大角色:

  1. 目标对象:需要被代理的对象
  2. 代理对象:代理目标对象的对象
  3. 目标对象和代理对象的公共接口:目标对象和代理对象之间应该具有相同的行为。为了使用户察觉不到是由代理对象完成的,使用户感觉还是由目标对象进行完成的

使用代理模式,对于客户端程序来说,客户端无法察觉,客户端在使用代理对象的时候,就像在使用目标对象。对于客户端程序来说,使用代理对象时就像在使用目标对象一样。

代理模式是GoF23种设计模式之一。属于结构型设计模式。

  • 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

代理模式的代码实现有两种形式:

  • 静态代理
  • 动态代理

14.2 静态代理

项目经理提出一个新的需求:要统计所有业务接口中每一个业务方法的耗时。

解决方案一:硬编码,在每一个业务接口中的每一个业务方法中直接添加统计耗时的程序。

  • 缺点:
    • 缺点一:违背OCP开闭原则。
    • 缺点二:代码没有得到复用,相同的代码写了很多遍。

解决方案二:编写业务类的子类,让子类继承业务类,对每个业务方法进行重写。

  • 缺点一:虽然解决了OCP开闭原则。但是这种方式会导致耦合度很高,因为采用了继承关系。继承关系是一种耦合度非常高的关系,不建议使用。
  • 缺点二:代码没有得到复用,相同的代码写了很多遍。

解决方案三:代理模式。

  • 优点1:解决了OCP问题。
  • 优点2:采用代理模式的has a,可以降低耦合度。
  • 缺点:类爆炸,假设系统中有1000个接口,那么每个接口都需要对应的代理类,这样类会急剧膨胀,不好维护
公共接口
package cw.study.spring.service;/*** ClassName: OrderService* Package: cw.study.spring.service* Description:*/
public interface OrderService {/*** 生成订单*/void generate();/*** 修改订单*/void modify();/*** 查看订单详情*/void detail();}
目标对象
package cw.study.spring.service;/*** ClassName: OrderSeriveImpl* Package: cw.study.spring.service* Description:** @Author tcw* @Create 2023-05-28 9:34* @Version 1.0*/
public class OrderSeriveImpl implements OrderService {@Overridepublic void generate() {try {Thread.sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单生成成功...");}@Overridepublic void modify() {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单修改成功...");}@Overridepublic void detail() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单查询成功...");}
}
代理对象

代理对象和目标对象要具有相同的行为,就需要实现同样的接口,使得客户端在使用代理对象的时候,就像在使用目标对象一样

在代理对象中最终也还是要执行目标对象中的目标方法,为了能够在代理对象中执行目标对象的目标方法,我们可以将目标对象作为代理对象的属性,代理对象中要有目标对象的引用,这种关系为关联关系,耦合度比泛化关系

  • 关联关系:在A中,有B作为其属性,A has a B
  • 泛化关系:A继承B,A is a B

package cw.study.spring.service;/*** ClassName: OrderServiceProxy* Package: cw.study.spring.service* Description:* 代理对象** @Author tcw* @Create 2023-05-29 10:25* @Version 1.0*/
public class OrderServiceProxy implements OrderService {// 使用公共接口,公共接口的耦合度低private OrderService target;// 创建代理对象的时候,给代理对象中目标对象引用赋值public OrderServiceProxy(OrderService target) {this.target = target;}@Overridepublic void generate() {// 增强代码long begin = System.currentTimeMillis();// 调用目标对象的目标方法target.generate();long end = System.currentTimeMillis();System.out.println("执行耗时:" + (end - begin));}@Overridepublic void modify() {long begin = System.currentTimeMillis();// 调用目标对象的目标方法target.modify();long end = System.currentTimeMillis();System.out.println("执行耗时:" + (end - begin));}@Overridepublic void detail() {long begin = System.currentTimeMillis();// 调用目标对象的目标方法target.detail();long end = System.currentTimeMillis();System.out.println("执行耗时:" + (end - begin));}
}
public class Client {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderSeriveImpl();// 创建代理对象OrderService proxy = new OrderServiceProxy(target);// 使用代理对象的代理方法// 使用代理对象就像在使用目标对象一样,// 都可以完成相同的功能,并且还可以进行加强proxy.generate();proxy.modify();proxy.detail();}
}

使用静态代理模式,没有修改原先写好的类,符合OCP原则,且在代理类中只是有目标对象的引用,耦合度比前两种方法更低

Q:目前我们使用的是静态代理,这个静态代理的缺点是什么?

A:类爆炸。假设系统中有1000个接口,那么每个接口都需要对应代理类,这样类会急剧膨胀。不好维护。

Q:怎么解决类爆炸问题?

A:可以使用动态代理来解决这个问题。

动态代理还是代理模式,只不过添加了字节码生成技术,可以在内存中为我们动态的生成一个class字节码,这个字节码就是代理类。在内存中动态的生成字节码代理类的技术,叫做:动态代理。

动态代理

动态代理:在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

在内存当中动态生成类的技术常见的包括:

  • JDK动态代理技术:java.lang.reflect.Proxy,只能代理接口,即只适合有一个目标类和一个公共接口的情况
  • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)由于动态生成的类是在内存中生成的,可以采用继承的方式,无所谓其耦合度
  • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
JDK 动态代理技术
  • JDK 动态代理只能代理接口
  • 还是使用静态代理中的例子:一个接口和一个实现类。
公共接口
package cw.study.spring.service;/*** ClassName: OrderService* Package: cw.study.spring.service* Description:*/
public interface OrderService {String getName();/*** 生成订单*/void generate();/*** 修改订单*/void modify();/*** 查看订单详情*/void detail();}
目标对象
package cw.study.spring.service;/*** ClassName: OrderSeriveImpl* Package: cw.study.spring.service* Description:*/
public class OrderSeriveImpl implements OrderService {@Overridepublic String getName() {return "张三";}@Overridepublic void generate() {try {Thread.sleep(30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单生成成功...");}@Overridepublic void modify() {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单修改成功...");}@Overridepublic void detail() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("订单查询成功...");}
}
动态生成代理类分析
  • 在动态代理中代理类是可以动态生成的,这个类不需要写,我们直接写客户端程序即可
  • 需要理解:Object proxyObj = Proxy.newProxyInstance(类加载器, 代理类要实现的接口(公共接口), 调用处理器)
    • newProxyInstance:创建代理对象,调用该方法可以创建代理对象
      • Proxy.newProxyInstance():该方法的执行在内存中生成了代理类的字节码,并且通过内存中生成的代理类的字节码创建了代理对象
    • Proxy.newProxyInstance() 的三个参数:
      • 类加载器 ClassLoader loader:内存中动态生成的类的字节码需要加载到JVM中,需要类加载器,JDK要求代理类的类加载器和目标类的类加载器要是同一个
      • 代理类要实现的接口 Class<?>[] interfaces:代理类要和目标类实现相同的接口,代理类需要实现的接口需要我们进行告知
      • 调用处理器 InvocationHandler h:JDK不可能知道我们要代理类增强目标类哪些功能,JDK不知道增强的代码,这个需要我们进行传递,我们可以通过调用处理器告诉JDK代理类的增强代码,调用处理器 InvocationHandler 是一个接口,需要我们进行实现,在需要实现的方法中编写增强代码
实现调用处理器接口 InvocationHandler 编写增强代码
  • 编写增强代码的调用处理器只需要编写一次即可
  • 实现调用处理器接口 InvocationHandler,需要实现调用处理器的invoke方法,实现该方法其实就是在实现代理类实现公共接口时需要实现公共接口的方法的方法体
  • 增强代码在调用处理器接口 InvocationHandler 的invoke方法中编写
  • invoke方法由JDK在底层进行调用,如何调用invoke方法,JDK在底层已经写好了,当代理对象调用代理方法的时候,invoke方法会被调用
package client.cw.study.spring.improve;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** ClassName: TimerInvocationHandler* Package: client.cw.study.spring.improve* Description:* 负责计时的调用处理器类* 在该类中编写关于计时的增强代码*/
public class TimerInvocationHandler implements InvocationHandler {// 目标对象private Object target;/*** 调用目标对象的目标方法是通过反射进行调用的,* 所以需要目标对象的引用** @param target 目标对象*/public TimerInvocationHandler(Object target) {this.target = target;}/*** 在invoke方法中写增强代码。* 调用代理对象的代理方法,对目标方法进行增强时要保证目标方法执行。* invoke方法是JDK进行调用的,JDK在调用该方法时,会将invoke方法需要的参数* 传递过来** @param proxy 代理对象的引用(使用较少)* @param method 目标对象的目标方法* @param args 目标方法上的实参* @return 目标对象的目标方法的返回值* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 调用目标对象的目标方法的前后编写增强代码// 目标方法执行开始时间long start = System.currentTimeMillis();// 调用目标对象的目标方法,那么这里需要一个目标对象:使用构造器传参Object returnVal = method.invoke(target, args);// 目标方法执行结束时间long end = System.currentTimeMillis();// 计算输出目标方法的执行耗时System.out.println(end - start);// 返回目标对象的目标方法的返回值return returnVal;}
}
客户端程序
package client;import client.cw.study.spring.improve.TimerInvocationHandler;
import cw.study.spring.service.OrderSeriveImpl;
import cw.study.spring.service.OrderService;
import cw.study.spring.service.OrderServiceProxy;import java.lang.reflect.Proxy;/*** ClassName: Client* Package: client* Description:** @Author tcw* @Create 2023-05-28 9:38* @Version 1.0*/
public class Client {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderSeriveImpl();// 创建代理对象// Object proxyObj = Proxy.newProxyInstance(类加载器, 代理类要实现的接口(公共接口), 调用处理器)OrderService orderService = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target));// 使用代理对象的代理方法orderService.generate();orderService.modify();orderService.detail();String name = orderService.getName();System.out.println(name);}
}

JDK 动态代理工具类封装
package client.cw.study.spring.improve;import cw.study.spring.service.OrderService;import java.lang.reflect.Proxy;/*** ClassName: ProxyUtil* Package: client.cw.study.spring.improve* Description:*/
public class ProxyUtil {private ProxyUtil() {}/*** 创建目标对象的代理对象** @param target 目标对象* @return 代理对象*/public static Object newProxyInstance(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 获取目标对象的目标类的类加载器target.getClass().getInterfaces(), // 获取目标对象的目标类实现的接口new TimerInvocationHandler(target) // 调用处理器);}
}
public class Client {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderSeriveImpl();// 创建代理对象// Object proxyObj = Proxy.newProxyInstance(类加载器, 代理类要实现的接口(公共接口), 调用处理器)// OrderService orderService = (OrderService) Proxy.newProxyInstance(//         target.getClass().getClassLoader(),//         target.getClass().getInterfaces(),//         new TimerInvocationHandler(target)// );OrderService orderService = (OrderService) ProxyUtil.newProxyInstance(target);// 使用代理对象的代理方法orderService.generate();orderService.modify();orderService.detail();String name = orderService.getName();System.out.println(name);}
}

CGLIB 动态代理
  • CGLIB 动态代理可以代理接口,也可以代理类
  • CGLIB 动态代理底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。

功能更强大,效率更高

CGLIB 依赖
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
目标类
package cw.study.spring.service;/*** ClassName: UserService* Package: cw.study.spring.service* Description:* 目标类*/
public class UserService {public boolean login(String username, String password) {System.out.println("正在验证身份...");if ("admin".equals(username) && "123".equals(password)) return true;return false;}public void logout() {System.out.println("退出登录...");}}
方法拦截器 MethodInterceptor

相当于要执行目标方法的时候,会被拦截器拦截,在执行增强代码的过程中执行目标方法,实现对目标方法的增强

package client.cw.study.spring.improve;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** ClassName: TimerMethodInterceptor* Package: client.cw.study.spring.improve* Description:*/
public class TimerMethodInterceptor implements MethodInterceptor {/*** 在该方法中编写对目标方法的增强代码* * @param target 目标对象* @param method 目标方法* @param objects 目标方法调用时的实参* @param methodProxy 代理方法* @return 目标方法的返回值* @throws Throwable*/@Overridepublic Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 调用目标前后编写增强代码long start = System.currentTimeMillis();// 调用目标对象的方法// 调用代理对象的父类的方法Object returnVal = methodProxy.invokeSuper(target, objects);long end = System.currentTimeMillis();System.out.println("耗时:" + (end - start));// 返回目标方法的返回值return returnVal;}
}
客户端
package client;import client.cw.study.spring.improve.TimerMethodInterceptor;
import cw.study.spring.service.UserService;
import net.sf.cglib.proxy.Enhancer;/*** ClassName: Client* Package: client* Description:*/
public class Client {public static void main(String[] args) {// 创建字节码增强器对象,CGLIB的核心对象,用于代理类的生成Enhancer enhancer = new Enhancer();// 告诉CGLIB代理的目标类,由于CGLIB采用的是继承的方式,所以目标类为代理类的父类enhancer.setSuperclass(UserService.class);// 设置回调,等同于JDK动态代理的调用处理器// 在CGLIB中需要实现的接口为MethodInterceptor方法拦截器(不是JDK动态代理中的InvocationHandler接口)enhancer.setCallback(new TimerMethodInterceptor());// 创建代理对象// 会在内存中生成目标类的子类,即代理类,然后会创建代理类的对象UserService userServiceProxy = (UserService) enhancer.create();// 使用代理对象的代理方法System.out.println(userServiceProxy.login("admin", "123") ? "登录成功" : "登录失败");userServiceProxy.logout();}
}
  • 对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:

--add-opens java.base/java.lang=ALL-UNNAMED--add-opens java.base/sun.net.util=ALL-UNNAMED

CGLIB 代理类命名格式
cw.study.spring.service.UserService$$EnhancerByCGLIB$$d609db49@2d6a9952class UserService$$EnhancerByCGLIB$$d609db49 extends UserService {}

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



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

相关文章

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

springboot实战学习(1)(开发模式与环境)

目录 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 (3)前端 二、开发模式 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 Validation:做参数校验Mybatis:做数据库的操作Redis:做缓存Junit:单元测试项目部署:springboot项目部署相关的知识 (3)前端 Vite:Vue项目的脚手架Router:路由Pina:状态管理Eleme

状态模式state

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/state 在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 在状态模式中,player.getState()获取的是player的当前状态,通常是一个实现了状态接口的对象。 onPlay()是状态模式中定义的一个方法,不同状态下(例如“正在播放”、“暂停

软件架构模式:5 分钟阅读

原文: https://orkhanscience.medium.com/software-architecture-patterns-5-mins-read-e9e3c8eb47d2 软件架构模式:5 分钟阅读 当有人潜入软件工程世界时,有一天他需要学习软件架构模式的基础知识。当我刚接触编码时,我不知道从哪里获得简要介绍现有架构模式的资源,这样它就不会太详细和混乱,而是非常抽象和易