代理模式的实现

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.mzph.cn/bicheng/41742.shtml

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

相关文章

Springboot学习之用EasyExcel4导入导出数据(基于MyBatisPlus)

一、POM依赖 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><m…

AI Earth应用—— 在线使用sentinel数据VV和VH波段进行水体提取分析(昆明抚仙湖、滇池为例)

AI Earth 本文的主要目的就是对水体进行提取,这里,具体的操作步骤很简单基本上是通过,首页的数据检索,选择需要研究的区域,然后选择工具箱种的水体提取分析即可,剩下的就交给阿里云去处理,结果如下: 这是我所选取的一景影像: 详情 卫星: Sentinel-1 级别: 1 …

基于机器学习(支持向量机,孤立森林,鲁棒协方差与层次聚类)的机械振动信号异常检测算法(MATLAB 2021B)

机械设备异常检测方法流程一般如下所示。 首先利用传感器采集机械运行过程中的状态信息&#xff0c;包括&#xff0c;振动、声音、压力、温度等。然后采用合适的信号处理技术对采集到机械信号进行分析处理&#xff0c;提取能够准确反映机械运行状态的特征。最后采用合理的异常决…

C++ 什么是虚函数?什么是纯虚函数,以及区别?(通俗易懂)

&#x1f4da; 当谈到虚函数时&#xff0c;通常是指在面向对象编程中的一种机制&#xff0c;它允许在派生类中重写基类的函数&#xff0c;并且能够通过基类指针或引用调用派生类中的函数。 目录 前言 &#x1f525; 虚函数 &#x1f525; 纯虚函数 &#x1f525; 两者区别…

mac|idea导入通义灵码插件

官方教程&#xff1a;通义灵码下载安装指南_智能编码助手_AI编程_云效(Apsara Devops)-阿里云帮助中心 下载插件&#xff1a; ⇩ TONGYI Lingma - JetBrains 结果如下&#xff1a; 选择apply、ok&#xff0c;会出现弹窗&#xff0c;点击登录 可以实现&#xff1a;生成单元测…

《C++20设计模式》代理模式

文章目录 一、前言二、实现1、UML类图2、实现 一、前言 这代理模式和装饰器模式很像啊。都是套一层类。&#x1f630; 主要就是功能差别 装饰器&#xff1a; 为了强化原有类的功能。代理模式&#xff1a; 不改变原有功能&#xff0c;只是强化原有类的潜在行为。 我觉的书上有…

【基于R语言群体遗传学】-8-代际及时间推移对于变异的影响

上一篇博客&#xff0c;我们学习了在非选择下&#xff0c;以二项分布模拟遗传漂变的过程&#xff1a;【基于R语言群体遗传学】-7-遗传变异&#xff08;genetic variation&#xff09;-CSDN博客 那么我们之前有在代际之间去模拟&#xff0c;那么我们就想知道&#xff0c;遗传变…

KVM虚机调整磁盘大小(注:需重启虚拟机)

1、将磁盘大小由15G调整为25G [rootkvm ~]# virsh domblklist kvm-client #显示虚拟机硬盘列表 [rootkvm ~]# qemu-img resize /var/lib/libvirt/images/tesk-disk.qcow2 10G #扩容 [rootkvm ~]# qemu-img info /var/lib/libvirt/images/test-disk.qcow2 #查看信息 注&…

奥威BI方案:多行业、多场景,只打高端局

奥威BI方案&#xff0c;确实以其卓越的性能和广泛的应用领域&#xff0c;在高端数据分析市场中占据了一席之地。以下是对奥威BI方案的详细解析。 奥威BI方案是一款针对多行业、多场景的全面数据分析解决方案&#xff0c;它结合了大数据、云计算等先进技术&#xff0c;为企业提…

看互联网大厂如何落地AI-Agent(3)

vivo一站式AI智能体构建平台的演进实践 引言 在AI技术的浪潮中&#xff0c;vivo互联网产品平台架构团队负责人张硕分享了vivo在构建一站式AI智能体平台方面的演进实践和深刻洞见。 背景与挑战 vivo面临的挑战包括创造商业价值、降低学习成本、合规性、以及LLM&#xff08;大…

hnust 1816: 算法10-9:简单选择排序

hnust 1816: 算法10-9&#xff1a;简单选择排序 题目描述 选择排序的基本思想是&#xff1a;每一趟比较过程中&#xff0c;在n-i1(i1,2,…,n-1)个记录中选取关键字最小的记录作为有序序列中的第i个记录。 在多种选择排序中&#xff0c;最常用且形式最为简单的是简单选择排序。…

收银系统源码-收银台副屏广告

1. 功能描述 门店广告&#xff1a;双屏收银机&#xff0c;副屏广告&#xff0c;主屏和副屏同步&#xff0c;总部可统一控制广告位&#xff0c;也可以给门店开放权限&#xff0c;门店独立上传广告位&#xff1b; 2.适用场景 新店开业、门店周年庆、节假日门店活动宣传&#x…

【HICE】DNS反向解析

反向解析&#xff1a;IP ----> 主机名 1.更改主配置文件 2.:更改反向的信息 3.重启服务 4.测试解析是否成功

论文辅导 | 基于多尺度分解的LSTM⁃ARIMA锂电池寿命预测

辅导文章 模型描述 锂电池剩余使用寿命&#xff08;Remaining useful life&#xff0c;RUL&#xff09;预测是锂电池研究的一个重要方向&#xff0c;通过对RUL的准确预测&#xff0c;可以更好地管理和维护电池&#xff0c;延长电池使用寿命。为了能够准确预测锂电池的RUL&…

待研究课题记录

最近了解到两个新的有趣的节点&#xff0c;但是对于实际效果不是很确定&#xff0c;所以这里记录下&#xff0c;后续慢慢研究&#xff1a; 扰动注意力引导 Perturbed Attention Guidance GitHub - KU-CVLAB/Perturbed-Attention-Guidance: Official implementation of "…

CTS单测某个模块和测试项

1 &#xff0c;测试单个模块命令 run cts -m <模块名> 比如&#xff1a;run cts -m CtsUsbTests模块名可以从测试报告中看&#xff0c;如下&#xff1a; 2&#xff0c; 测试单个测试项 run cts -m <模块名> -t <test_name> 比如&#xff1a;run cts -m ru…

Linux程序地址空间

1. 进程地址空间 简单来说&#xff0c;就是从高地址往低地址&#xff0c;内存分区分别是&#xff1a; 内核空间&#xff1a;命令行参数argv和环境变量env等栈区&#xff1a;大部分局部变量&#xff0c;栈区内存往低处增长堆区&#xff1a;用于动态内存管理&#xff0c;堆区内存…

C # @逐字字符串

逐字字符串 代码 namespace TestAppConsole {class program{static void Main(string[] args){int a 0;int b 9;string c "2ui923i9023";//Console.Write(sizeof(int));string d "\t8282jjksk";string e "\t8282jjksk";Console.WriteLine(…

Java——继承(Inheritance)

一、继承简要介绍 1、继承是什么 在Java中&#xff0c;继承是一种面向对象编程的重要特性&#xff0c;它允许一个类&#xff08;子类或派生类&#xff09;继承另一个类&#xff08;父类或基类&#xff09;的属性和方法。继承的目的是实现代码的重用和设计的层次化。 子类通常…

LT8711GX 国产芯片 Type-C/DP1.4转HDMI2.1 用于加密狗 对接站

描述 LT8711GX是一款高性能的Type-C/DP1.4到HDMI2.1转换器&#xff0c;设计用于将USBType-C源或DP1.4源连接到HDMI2.1收发器。 该LT8711GX集成了一个符合DP1.4标准的接收器和一个符合HDMI2.1标准的发射器。此外&#xff0c;还包括一个CC控制器&#xff0c;用于CC通信&#xff0…