代理模式的实现

1. 引言

1.1 背景

代理模式(Proxy Pattern)是一种常用的设计模式,它允许通过一个代理对象来控制对另一个对象的访问。在面向对象编程的框架中,代理模式被广泛应用,尤其在Spring框架的AOP(面向切面编程)功能中,用于实现横切关注点的模块化,如事务管理、日志记录、安全检查等。

通过代理模式,我们能够将复杂的功能从主要业务逻辑中剥离出来,使代码更简洁易读。同时,它还能够在不修改原有代码的基础上,动态地增加新功能或优化现有功能,使代码更灵活可扩展。

1.2 目的

本文将详细介绍代理模式的基本概念、实现步骤。通过本篇文章,你将能够理解代理模式的工作原理,并学会如何在实际项目中有效地利用它。

2. 何为代理模式?

代理模式就像是生活中的中介或代理人,他们代表我们处理一些事务,让我们能够更方便、更高效地完成任务。想象一下,你想要买一套房子,但你没有时间或者不熟悉购房流程,这时候你可能会找一个房产中介来帮你处理这些事情。房产中介就是这个场景中的“代理”,而你则是“客户端”,房子和卖家则是“真实对象”。

在这个例子中,房产中介(代理)会:

- 代表你与卖家沟通:中介会代替你与卖家协商价格、查看房子状况等,这样你就不需要亲自去做这些事情。
- 提供额外的服务:中介可能会提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程。
- 控制访问:中介可能会筛选掉一些不符合你要求的房子,只向你推荐合适的房源,这样你就不需要自己去处理大量的信息。
- 延迟处理:如果你暂时没有时间去看房,中介可以先帮你预约,等到你有时间再去,这样就避免了资源的浪费。


通过房产中介这个代理,你可以在不直接与卖家接触的情况下,完成购房的任务,同时还能享受到额外的服务和便利。

在软件开发中,代理模式也是类似的道理。比如,一个处理敏感数据的应用程序,可以通过代理来控制对数据的访问,确保只有授权的用户才能查看或修改数据。或者,一个需要处理大量计算的应用,可以通过代理来实现计算的延迟加载,只在真正需要时才进行计算,从而提高系统的效率。

2.1 代理模式的主要角色

  1. Subject(主体):定义了RealSubjectProxy的共同接口,这样在任何使用RealSubject的地方都可以使用Proxy
  2. RealSubject(真实主体):定义了Proxy所代表的真实对象。
  3. Proxy(代理):持有一个RealSubject的引用,并提供与RealSubject相同的接口,这样代理就可以代替RealSubject。代理对象可以在调用RealSubject的方法前后执行额外的操作。
// 1. 主接口
public interface Subject {void house();
}// 2. 真实对象类
public class RealSubject implements Subject {@Overridepublic void house() {System.out.println("买家:筛选出想要的房子");}
}// 3. 代理类
public class Proxy implements Subject {private RealSubject realSubject;@Overridepublic void house() {if (realSubject == null) {realSubject = new RealSubject();}preRequest();realSubject.house();postRequest();}private void preRequest() {System.out.println("房产中介: 帮你筛选掉一些不符合要求的房子,只向你推荐合适的房源");}private void postRequest() {System.out.println("房产中介: 提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程");}
}// 4. 客户端代码
public class Client {public static void main(String[] args) {Subject proxy = new Proxy();proxy.house();}
}

2.2 代理模式的应用场景

  • 远程代理:代表一个位于不同地址空间的对象。
  • 虚拟代理:根据需要创建开销很大的对象。
  • 保护代理:控制对原始对象的访问,用于对象应该有不同的访问权限的情况。
  • 智能引用:在访问对象时执行额外的操作,例如计算引用次数。

2.3 代理模式的主要类型

代理模式通常可以分为两种主要类型:静态代理和动态代理。

静态代理(Static Proxy)

静态代理是指在编译时就已经确定的代理类。静态代理类需要手动编写,它实现了与目标对象相同的接口,并在代理类中持有一个目标对象的引用。

静态代理的优点是实现简单,容易理解,但缺点是每当需要代理一个新的接口或类时,都需要手动创建一个新的代理类,这会导致代码冗余和维护成本增加。

代理模式的默认实现通常是静态代理,因为静态代理是最直观和最容易理解的方式:

// 1. 主接口
public interface Subject {void hourse();
}// 2. 真实对象类
public class RealSubject implements Subject {@Overridepublic void hourse() {System.out.println("买家:筛选出想要的房子");}
}// 3. 代理类
public class Proxy implements Subject {private RealSubject realSubject;@Overridepublic void hourse() {if (realSubject == null) {realSubject = new RealSubject();}preRequest();realSubject.hourse();postRequest();}private void preRequest() {System.out.println("房产中介: 帮你筛选掉一些不符合要求的房子,只向你推荐合适的房源");}private void postRequest() {System.out.println("房产中介: 提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程");}
}// 4. 客户端代码
public class Client {public static void main(String[] args) {Subject proxy = new Proxy();proxy.hourse();}
}

动态代理(Dynamic Proxy)

动态代理是指在运行时动态生成的代理类。动态代理不需要手动编写代理类,而是通过Java的反射机制在运行时创建代理对象。

动态代理的优点是灵活性高,可以为任意接口或类创建代理对象,而无需手动编写代理类。

Java提供了两种动态代理的实现方式:基于接口的动态代理(使用java.lang.reflect.Proxy类)和基于子类的动态代理(使用CGLIB库)。

我们还是基于买房卖房中介的场景,假设我们有一个房地产中介系统,其中有两个角色:买家和卖家。买家和卖家都可以通过中介进行交易。我们希望在交易过程中添加一些额外的功能,比如记录交易日志、检查交易资格等。

1. 基于接口的动态代理(买家)

基于接口的动态代理是Java标准库中提供的实现方式,使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public interface Buyer {void buyHouse(String houseId);
}public class RealBuyer implements Buyer {@Overridepublic void buyHouse(String houseId) {System.out.println("买方:需要买的房子的ID是: " + houseId);}
}public class BuyerInvocationHandler implements InvocationHandler {private Object realBuyer;public BuyerInvocationHandler(Object realBuyer) {this.realBuyer = realBuyer;}@Overridepublic Object invoke(Object obj, Method method, Object[] args) throws Throwable {System.out.println("买方中介BuyerInvocationHandler:检查买方资格");Object result = method.invoke(realBuyer, args);System.out.println("买方中介BuyerInvocationHandler:日志记录买方的需求");return result;}
}

 2. 基于子类的动态代理(卖家)

基于子类的动态代理通常使用第三方库,如CGLIB(Code Generation Library)。CGLIB通过生成目标类的子类来实现代理。

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class Seller {public void sellHouse(String houseId) {System.out.println("卖家: 需要售卖的房子ID: " + houseId);}
}public class SellerMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("卖方中介SellerMethodInterceptor:检查卖方资格");Object result = methodProxy.invokeSuper(obj, args);System.out.println("卖方中介SellerMethodInterceptor:日志记录卖方的需求");return result;}
}

3. 这样,买方和卖方和中介(代理类)的关系就串起来了,形成一个完整的房地产中介系统

public class Client {public static void main(String[] args) {// 买家代理RealBuyer realBuyer = new RealBuyer();InvocationHandler buyerHandler = new BuyerInvocationHandler(realBuyer);Buyer buyerProxy = (Buyer) Proxy.newProxyInstance(realBuyer.getClass().getClassLoader(),realBuyer.getClass().getInterfaces(),buyerHandler);buyerProxy.buyHouse("123");// 卖家代理Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Seller.class);enhancer.setCallback(new SellerMethodInterceptor());Seller sellerProxy = (Seller) enhancer.create();sellerProxy.sellHouse("456");}
}

3. Spring框架中代理模式实现AOP

在Spring框架中,代理模式被广泛应用于AOP(面向切面编程)。在此之前,先简单的过一下什么是AOP编程吧,好有个清晰的认知。

3.1 何为面向切面编程?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过允许程序员模块化横切关注点(cross-cutting concerns)来提高代码的模块性。横切关注点是指那些影响多个模块的功能,如日志记录、事务管理、安全性检查等,这些功能通常会跨越多个模块,导致代码重复和耦合度增加。

3.2 AOP的核心概念

AOP通过以下几个核心概念来实现横切关注点的模块化:

  1. 切面(Aspect):一个模块化的横切关注点。切面可以包含多个通知(advice)和切入点(pointcut)。

  2. 通知(Advice):定义了切面在特定连接点(join point)上执行的动作。通知有多种类型,如前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)等。

  3. 切入点(Pointcut):定义了通知应该应用的连接点的集合。切入点通过匹配特定的方法或代码位置来确定通知的执行时机。

  4. 连接点(Join Point):程序执行过程中的一个特定点,如方法调用、异常抛出等。在AOP中,连接点是通知可以插入的地方。

  5. 引入(Introduction):允许向现有类添加新的方法或字段,从而在不修改现有代码的情况下扩展类的功能。

  6. 目标对象(Target Object):包含连接点的对象,也就是被代理的对象。

  7. 代理(Proxy):在目标对象上应用切面后创建的对象。代理对象负责在调用目标对象的方法时插入通知。

3.3 AOP的主要优点包括

代码重用:通过将横切关注点模块化为切面,可以在多个模块中重用这些功能。
降低耦合度:将横切关注点从业务逻辑中分离出来,降低了代码的耦合度。
提高可维护性:模块化的横切关注点使得代码更易于理解和维护。

3.4 AOP的实现流程

Spring AOP支持两种类型的代理:基于接口的动态代理和基于子类的动态代理(CGLIB)。

我们依旧以买家卖家和中介为例,分为四步实现:

1. 定义买卖双方的接口和各自的实现类

2.  创建一个切面类

3. 配置Spring上下文

4. 在客户端测试代理模式是否生效

首先,我们定义买家和卖家接口及其实现类:

public interface Buyer {void buyHouse(String houseId);
}public class RealBuyer implements Buyer {@Overridepublic void buyHouse(String houseId) {System.out.println("买方:需要买的房子的ID是: " + houseId);}
}public interface Seller {void sellHouse(String houseId);
}public class RealSeller implements Seller {@Overridepublic void sellHouse(String houseId) {System.out.println("卖家: 需要售卖的房子ID: " + houseId);}
}

接下来,我们创建切面类,用于在买家和卖家操作前后添加额外的逻辑:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;@Aspect
@Component
public class TransactionAspect {@Before("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")public void beforeTransaction() {System.out.println("TransactionAspect: 检查是否有交易资格");}@After("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")public void afterTransaction() {System.out.println("TransactionAspect: 日志记录交易双方的需求");}
}

我们需要配置Spring上下文,启用AOP和组件扫描:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@EnableAspectJAutoProxy
public class AppConfig {@Beanpublic Buyer buyer() {return new RealBuyer();}@Beanpublic Seller seller() {return new RealSeller();}@Beanpublic TransactionAspect transactionAspect() {return new TransactionAspect();}
}

最后,我们编写一个测试类来验证代理模式是否生效:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Client {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Buyer buyer = context.getBean(Buyer.class);Seller seller = context.getBean(Seller.class);buyer.buyHouse("123");seller.sellHouse("456");}
}

虽然Spring AOP默认使用JDK动态代理来实现AOP,但也可以通过配置强制使用CGLIB来实现。

依旧以买家卖家和中介为例,分为四步实现:

1. 配置Spring上下文

2.  定义切面类

3. 定义目标类

4. 在客户端测试代理模式是否生效

首先,我们需要在Spring配置类中启用AOP并设置proxyTargetClass属性为true,以强制使用CGLIB代理。

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {// 其他配置
}

接下来,我们创建一个切面类,并使用@Aspect注解标记。这个切面类将包含在买卖双方操作前后执行的通知。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;@Aspect
@Component
public class TransactionAspect {@Before("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")public void beforeTransaction() {System.out.println("TransactionAspect: 检查是否有交易资格");}@After("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")public void afterTransaction() {System.out.println("TransactionAspect: 日志记录交易双方的需求");}
}

定义买家和卖家类,这些类不需要实现任何接口。

package com.example.service;import org.springframework.stereotype.Service;@Service
public class Buyer {public void buyHouse(String houseId) {System.out.println("买方:需要买的房子的ID是: " + houseId);}
}@Service
public class Seller {public void sellHouse(String houseId) {System.out.println("卖家: 需要售卖的房子ID: " + houseId);}
}

最后,我们编写一个测试类来验证CGLIB代理是否生效。

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.service.Buyer;
import com.example.service.Seller;public class Client {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Buyer buyer = context.getBean(Buyer.class);Seller seller = context.getBean(Seller.class);buyer.buyHouse("123");seller.sellHouse("456");}
}

4. 总结

讲了这么多,我们也可以看出来代理模式是一种强大的设计模式,它通过引入代理对象来控制对目标对象的访问,并可以在不改变目标对象的情况下添加额外的功能。代理模式在许多框架和库中得到了广泛应用,如Spring AOP、Hibernate等。通过合理使用代理模式,可以提高代码的模块化、可维护性和灵活性。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Mongodb oplog的作用及如何评估和更改保留时间

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG数据库运维(如安装迁移,性能优化、故障应急处理等) 公众号:老苏畅谈运维 欢迎关注本人公众号,更多精彩与您分享。oplog …

【论文解读】AGENTLESS:揭开基于LLM的软件工程代理的神秘面纱,重塑软件工程自动化新基线

📜 文献卡 英文题目: Agentless: Demystifying LLM-based Software Engineering Agents;作者: Chunqiu Steven Xia; Yinlin Deng; Soren Dunn; Lingming ZhangDOI: 10.48550/arXiv.2407.01489摘要翻译: 大型语言模型(LLM)的最新进展显著推进…

Python + OpenCV 开启图片、写入储存图片

这篇教学会介绍OpenCV 里imread()、imshow()、waitKey() 方法,透过这些方法,在电脑中使用不同的色彩模式开启图片并显示图片。 imread() 开启图片 使用imread() 方法,可以开启图片,imread() 有两个参数,第一个参数为档…

基于顺序表的通讯录实现

一、前言 基于已经学过的顺序表,可以实现一个简单的通讯录。 二、通讯录相关头文件 //Contact.h #pragma once#define NAME_MAX 20 #define TEL_MAX 20 #define ADDR_MAX 20 #define GENDER_MAX 20typedef struct PersonInfo {char name[NAME_MAX];char gender[G…

Hugging face Transformers(2)—— Pipeline

Hugging Face 是一家在 NLP 和 AI 领域具有重要影响力的科技公司,他们的开源工具和社区建设为NLP研究和开发提供了强大的支持。它们拥有当前最活跃、最受关注、影响力最大的 NLP 社区,最新最强的 NLP 模型大多在这里发布和开源。该社区也提供了丰富的教程…

C++友元函数和友元类的使用

1.友元介绍 在C++中,友元(friend)是一种机制,允许某个类或函数访问其他类的私有成员。通过友元,可以授予其他类或函数对该类的私有成员的访问权限。友元关系在一些特定的情况下很有用,例如在类之间共享数据或实现特定的功能。 友元可以分为两种类型:类友元和函数友元。…

高级计算机体系结构--期末教材复习

Chap2 性能评测和并行编程性能评测并行编程为什么需要三次 barrier改进方法 Chap3 互连网络交换和路由二维网格中 XY 路由 死锁、活锁及饿死死锁避免的方法:虚通道、转弯模型二维网格中最小 西向优先、北向最后和负向优先算法转弯模型:超立方体的部分自适…

前端面试题19(vue性能优化)

Vue.js应用的性能优化是一个多方面的过程,涉及初始化加载、运行时渲染以及用户交互等多个环节。以下是一些关键的Vue性能优化策略,包括详细的说明和示例代码: 1. 懒加载组件 对于大型应用,可以使用懒加载来减少初始加载时间。Vu…

JavaWeb----JSPJSTL

目录 JSP显隐注释在JSP中写java程序JSP的指令标签JSP中的四大域对象简易版用户登录EL表达式 JSTL条件动作标签if标签 choose\when\otherwise标签迭代标签格式化动作标签 用户登录实例查看是否安装了mysql用户登录界面后台实现 JSP JSP全名是Java Server Pages,它是建…

电机控制杂谈——增量式的预测电流控制的优势在哪?

1.前言 前几天看到这么个问题。“模型预测控制如何消除静态误差” 评论说用增量式的预测控制。 这个回答让我想起来我大四下看的这篇论文。现在都一百多被引用了。 但是苦于当时能力有限,没办法复现这个文章。 所以现在想重新验证一下。 2.静态误差和电机磁链有…

[CP_AUTOSAR]_分层软件架构_内容详解

目录 1、软件分层内容1.1、Microcontroller Abstraction Layer1.2、ECU Abstraction Layer1.2.1、I/O HW Abstraction1.2.2、Communication Hardware Abstraction1.2.3、Memory Hardware Abstraction1.2.4、Onboard Device Abstraction1.2.5、Crypto Hardware Abstraction 1.3、…

Apache Seata分布式事务启用Nacos做配置中心

本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。 本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。 Seata分布式事务启用Nacos做配置中心 Seata分布式事务启用Nacos做配置中心 项目地址 本文作…

便携式气象站:探索自然的智慧伙伴

在探索自然奥秘、追求科学真理的道路上,气象数据始终是我们不可或缺的指引。然而,传统的气象站往往庞大而笨重,难以在偏远地区或移动环境中灵活部署。 便携式气象站,顾名思义,是一种小巧轻便、易于携带和安装的气象观测…

VitePress美化

参考资料: https://blog.csdn.net/weixin_44803753/article/details/130903396 https://blog.csdn.net/qq_30678861/category_12467776.html 站点信息修改 首页部分的修改基本都在.vitepress/config.mts,这个文件内修改。 title 站点名称 description 描述 top…

Vben:表格的表头和表格的内容对不齐,以及解决方法

文章目录 一、问题描述二、解决方法 一、问题描述 基于Vue-Vbne-admin框架进行前端开发的时候,调用表格useTable函数实现表格之后,发现表格的表头和表格的内容对不齐。如下图所示。针对这种情况,本文记录了解决方法。 调用的模块如下&#x…

【力扣 - 每日一题】3099. 哈沙德数 | 模拟 (Go/C++)

题目内容 如果一个整数能够被其各个数位上的数字之和整除,则称之为 哈沙德数(Harshad number)。给你一个整数 x 。如果 x 是 哈沙德数 ,则返回 x 各个数位上的数字之和,否则,返回 -1 。 示例 1&#xff1…

使用 ESP32-WROOM + DHT11 做个无屏温湿度计

最近梅雨天,有个房间湿度很大,而我需要远程查看温湿度,所以无所谓有没有显示屏,某宝上的温湿度计都是带屏的,如果连WIFI查看温湿度操作也比较麻烦,还需要换电池,实在不能满足我的需求&#xff0…

SpringBoot新手快速入门系列教程四:创建第一个SringBoot的API

首先我们用IDEA新建一个项目,请将这些关键位置按照我的设置设置一下 接下来我将要带着你一步一步创建一个Get请求和Post请求,通过客户端请求的参数,以json格式返回该参数{“message”:"Hello"} 1,先在IDE左上角把这里改为文件模式…

笔记:SpringBoot+Vue全栈开发

笔记:SpringBootVue全栈开发 1. 开发环境热部署2. SpringBoot RestController的使用3. SpringBoot实现文件上传4. 配置拦截器5. Restful服务Swagger6. 使用MyBatis-Plus进行数据库操作7. 多表查询、条件查询及分页查询 1. 开发环境热部署 使用spring-boot-devtools…

泛微开发修炼之旅--31海康威视综合安防管理系统组织机构同步代码方案及源码

31海康威视综合安防管理系统组织机构同步代码方案及源码 一、使用场景 我们在一个项目中有一个和海康威视综合安防管理系统进行组织机构同步接口,接下来我们看下实现的源码 31海康威视综合安防管理系统组织机构同步代码方案及源码