Spring Plugin与策略模式:打造动态可扩展的应用

目录

一、策略模式

二、Spring Plugin

        2.1 Spring Plugin 实现策略模式开发

        2.2 策略模式优缺点

三、Spring Plugin 原理


一、策略模式

        策略模式是一种设计模式,它允许程序在运行中动态的选择不同的行为方式进行动态执行。策略模式的核心思想是将行为封装在一个个独立的类中,这些类实现了相同的接口或抽象类,客户端可以通过接口来调用不同的实现,而不知道具体的实现细节。下面来看一个具体的案例。

        现在的移动支付非常的便捷,而且有很多支付方式,假如让你负责支付路由的设计该如何设计,如何实现支付渠道的选择的呢?

        比如用户支付时可以选择支付宝、微信、银行卡,那系统底层是如何进行操作的,后期如果在加入新的支付方式,该如何进行扩展呢?

        当然如果你使用 if else 肯定是能实现的,但这种代码可读性差、可维护性差,而且不利于扩展,使用策略模式就能优雅的解决这些问题。

二、Spring Plugin

        Spring Plugin 是 Spring 框架的一个扩展,用于实现插件化开发。它提供了插件注册、加载、卸载等功能。Spring Plugin 提供了一种简单而有效的方式来实现插件化开发,使得应用程序能够更加灵活和易于维护。

        下面通过 Spring Plugin 来实现上面提到的支付路由的策略模式。

        2.1 Spring Plugin 实现策略模式开发

        引入依赖

<dependency><groupId>org.springframework.plugin</groupId><artifactId>spring-plugin-core</artifactId><version>指定版本</version>
</dependency>

        定义支付方式接口

public interface PaymentStrategy extends Plugin<String> {/*** 支付路由选择** @param paymentReq 待处理的订单信息, 入参中携带支付标识* @return*/PayResult pay(PaymentReq paymentReq);
}

        具体的支付实现

// 支付宝支付实现
@Service
public class AliPayService implements PaymentStrategy {@Overridepublic PayResult pay(PaymentReq paymentReq) {// 模拟支付宝支付流程return new PayResult();}@Overridepublic boolean supports(String payment) {// 支付方式是否为支付宝,这里简化一些,正常情况下需要使用枚举return "alipay".equals(payment);}
}// 微信支付实现
@Service
public class WechatPayService implements PaymentStrategy {@Overridepublic PayResult pay(PaymentReq paymentReq) {// 模拟微信支付流程return new PayResult();}@Overridepublic boolean supports(String payment) {// 支付方式是否为微信,这里简化一些,正常情况下需要使用枚举return "wechatpay".equals(payment);}
}

        假如后期要加入银联支付方式,相信你一定知道如何实现了吧。

        定义插件配置

@Configuration
@EnablePluginRegistries({PaymentStrategy.class})
public class StrategyConfig {}

        使用支付方式进行支付操作

@RestController
public class PaymentController {@Autowiredprivate PluginRegistry<PaymentStrategy, String> registry;@PostMapping(value = "/pay")public PayResult pay(PaymentReq req) {PaymentStrategy strategy = registry.getRequiredPluginFor(req.getPaymentType());return strategy.pay(req);}
}

        上述即时使用 Spring Plugin 实现策略模式的案例,是不是很简单呢。

        2.2 策略模式优缺点

        策略模式的优点很明显,有以下优点

  1. 扩展性:使用策略模式时,如果要添加新的策略十分方便也很简单,不用修改原有的代码,扩展性好。
  2. 解耦:客户端调用时只需要知道策略接口,而具体的实现不必担心。
  3. 动态性:可以在运行时动态进行不同策略的切换,提高了灵活性和适应性。

        但是也有一定的缺点,为了实现每个策略类,都需要一个新的类进行独立的封装,增加了复杂性。但是与其扩展性来说,这点实际上是可以忽略的。

三、Spring Plugin 原理

        开启 Spring Plugin 功能的入口是 @EnablePluginRegistries 注解,先看一下其实现。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(PluginRegistriesBeanDefinitionRegistrar.class)
public @interface EnablePluginRegistries {/*** The {@link Plugin} types to register {@link PluginRegistry} instances for. The registries will be named after the* uncapitalized plugin type extended with {@code Registry}. So for a plugin interface {@code SamplePlugin} the* exposed bean name will be {@code samplePluginRegistry}. This can be used on the client side to make sure you get* the right {@link PluginRegistry} injected by using the {@link Qualifier} annotation and referring to that bean* name. If the auto-generated bean name collides with one already in your application you can use the* {@link Qualifier} annotation right at the plugin interface to define a custom name.* * @return*/Class<? extends Plugin<?>>[] value();}

        该注解声明了需要开启插件化能力的接口,并且导入了PluginRegistriesBeanDefinitionRegistrar,它是一个 ImportBeanDefinitionRegistrar,会在 Spring Boot 启动的时候执行 registerBeanDefinitions 方法。registerBeanDefinitions 方法实现如下:

@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnablePluginRegistries.class.getName());if (annotationAttributes == null) {LOG.info("No EnablePluginRegistries annotation found on type {}!", importingClassMetadata.getClassName());return;}Class<?>[] types = (Class<?>[]) annotationAttributes.get("value");for (Class<?> type : types) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PluginRegistryFactoryBean.class);builder.addPropertyValue("type", type);RootBeanDefinition beanDefinition = (RootBeanDefinition) builder.getBeanDefinition();beanDefinition.setTargetType(getTargetType(type));Qualifier annotation = type.getAnnotation(Qualifier.class);// If the plugin interface has a Qualifier annotation, propagate that to the bean definition of the registryif (annotation != null) {AutowireCandidateQualifier qualifierMetadata = new AutowireCandidateQualifier(Qualifier.class);qualifierMetadata.setAttribute(AutowireCandidateQualifier.VALUE_KEY, annotation.value());beanDefinition.addQualifier(qualifierMetadata);}// DefaultString beanName = annotation == null //? StringUtils.uncapitalize(type.getSimpleName() + "Registry") //: annotation.value();registry.registerBeanDefinition(beanName, builder.getBeanDefinition());}}

        registerBeanDefinitions 从 EnablePluginRegistries 解析出插件接口,然后注册成     PluginRegistryFactoryBean 类型的 BeanDefination。

        PluginRegistryFactoryBean 是一个 FactoryBean,所以注入 PluginRegistry 类型的时候实际是调用 PluginRegistryFactoryBean 的 getObject 返回的内容。

        

public class PluginRegistryFactoryBean<T extends Plugin<S>, S> extends AbstractTypeAwareSupport<T>implements FactoryBean<PluginRegistry<T, S>> {@NonNullpublic OrderAwarePluginRegistry<T, S> getObject() {return OrderAwarePluginRegistry.of(getBeans());}@NonNullpublic Class<?> getObjectType() {return OrderAwarePluginRegistry.class;}public boolean isSingleton() {return true;}
}

        注入的时候返回的类型是 OrderAwarePluginRegistry,注入调用 getObject 返回,里边调用了父类 AbstractTypeAwareSupport 的 getBeans 方法。

protected List<T> getBeans() {TargetSource targetSource = this.targetSource;if (targetSource == null) {throw new IllegalStateException("Traget source not initialized!");}ProxyFactory factory = new ProxyFactory(List.class, targetSource);return (List<T>) factory.getProxy();
}public void afterPropertiesSet() {ApplicationContext context = this.context;if (context == null) {throw new IllegalStateException("ApplicationContext not set!");}Class<?> type = this.type;if (type == null) {throw new IllegalStateException("No type configured!");}this.targetSource = new BeansOfTypeTargetSource(context, type, false, exclusions);
}

        由于实现了 InitializingBean 接口,初始化时会获取到 ApplicationContext 上下文,基于上下文的 type 封装成 BeansOfTypeTargetSource 赋值给 targetSource 变量,BeansOfTypeTargetSource 实现了 TargetSource,getTarget返回基于实际类型封装的增强类型。

class BeansOfTypeTargetSource implements TargetSource {@NonNull@SuppressWarnings({ "rawtypes", "unchecked" })public synchronized Object getTarget() throws Exception {Collection<Object> components = this.components == null //? getBeansOfTypeExcept(type, exclusions) //: this.components;if (frozen && this.components == null) {this.components = components;}return new ArrayList(components);}private Collection<Object> getBeansOfTypeExcept(Class<?> type, Collection<Class<?>> exceptions) {return Arrays.stream(context.getBeanNamesForType(type, false, eagerInit)) //.filter(it -> !exceptions.contains(context.getType(it))) //.map(it -> context.getBean(it)) //.collect(Collectors.toList());}
}

           getBeans 方法,会基于动态代理将 BeansOfTypeTargetSource 创建成 List 类型代理对象备用。然后回到 PluginRegistryFactoryBean 的 getObject 方法,会最终将插件接口实现封装成OrderAwarePluginRegistry 类型。

        也就是说通过 PluginRegistryFactoryBean 注入的 PluginRegistry 是包含了所有实现了插件接口实例的封装类型,我们常用到的有 getPlugins 和 getPluginFor 方法:

@Override
public List<T> getPlugins() {return Collections.unmodifiableList(super.getPlugins());
}@Override
public Optional<T> getPluginFor(S delimiter) {return super.getPlugins().stream()//.filter(it -> it.supports(delimiter))//.findFirst();
}

        到这里基本上就可以了解其工作原理了。

往期经典推荐:

从新手到高手:Spring AOP的进阶指南_springaop切面优先级-CSDN博客

Sentinel与Nacos强强联合,构建微服务稳定性基石的重要实践_sentinel nacos-CSDN博客

从0开始理解云原生架构_云原生发展历史-CSDN博客

TiDB高手进阶:揭秘自增ID热点现象与高级调优技巧_tidb 自增id-CSDN博客

SpringBoot项目并发处理大揭秘,你知道它到底能应对多少请求洪峰?_一个springboot能支持多少并发-CSDN博客

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

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

相关文章

spark的学习-05

SparkSql 结构化数据与非结构化数据 结构化数据就类似于excel表中的数据&#xff08;统计的都是结构化的数据&#xff09;一般都使用sparkSql处理结构化的数据 结构化的文件&#xff1a;JSON、CSV【以逗号分隔】、TSV【以制表符分隔】、parquet、orc 结构化的表&#xff1a;…

Android 源码的下载与编译

Android 源码的下载与编译 本章节主要介绍安卓系统的编译以及编译产物&#xff0c;根据我自己的经验只总结个人觉得重要的部分。 有价值的博客&#xff1a; https://blog.csdn.net/wuye110/article/details/8463409 https://juejin.cn/post/7288166472131018786 值得一看的…

基于Python的药房管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

Golang | Leetcode Golang题解之第542题01矩阵

题目&#xff1a; 题解&#xff1a; type point struct{x, y int }var dirs []point{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}func updateMatrix(mat [][]int) [][]int {var m, n len(mat), len(mat[0])var res make([][]int, m)var visited make([][]bool, m)var queue []poin…

vscode通过remote-ssh连接远程开发机

文章目录 安装扩展注意事项:tips其他参数安装扩展 安装VS Code和SSH-Remote扩展:首先,需要确保你已经在本地计算机上安装了VS Code,并且在扩展市场中搜索并安装了"Remote - SSH"扩展。配置SSH:在本地计算机上,打开VS Code的命令面板(使用快捷键"Ctrl+Shi…

Golang | Leetcode Golang题解之第552题学生出勤记录II

题目&#xff1a; 题解&#xff1a; const mod int 1e9 7type matrix [6][6]intfunc (a matrix) mul(b matrix) matrix {c : matrix{}for i, row : range a {for j : range b[0] {for k, v : range row {c[i][j] (c[i][j] v*b[k][j]) % mod}}}return c }func (a matrix) p…

关于CountDownLatch失效问题

一、项目背景 这几天要开发一个类似支付宝那种年度账单统计的功能&#xff0c;就是到元旦后支付完会把用户这一年的消费情况从各个维度&#xff08;我们把这一个维度称作一个指标&#xff09;统计分析形成一张报告展示给用户。 这个功能实现用到了CountDownLatch。假如统计分析…

ImportError: cannot import name ‘packaging‘ from ‘pkg_resources‘ 的参考解决方法

文章目录 写在前面一、问题描述二、解决方法参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04 ROS-Noetic 一、问题描述 自己在通过 pip install 安装module时 &#xff08;使用的是 pip install mmcv&#xff09;遇到如下问题&#xff1a; ImportError: cannot …

「Mac畅玩鸿蒙与硬件30」UI互动应用篇7 - 简易计步器

本篇将带你实现一个简易计步器应用&#xff0c;用户通过点击按钮增加步数并实时查看步数进度&#xff0c;目标步数为 10000 步。该项目示例展示了如何使用 Progress 组件和 Button 组件&#xff0c;并结合状态管理&#xff0c;实现交互式应用。 关键词 UI互动应用计步器Button…

多媒体信息检索

文章目录 一、绪论二、文本检索 (Text Retrieval)(一) 索引1.倒排索引2.TF-IDF (二) 信息检索模型 (IR模型&#xff0c;Information Retrieval)1.布尔模型 (Boolean模型)(1)扩展的布尔模型 (两个词)(2)P-Norm模型 (多个词) 2.向量空间模型 (Vector Space Model&#xff0c;VSM)…

在vscode中如何利用git 查看某一个文件的提交记录

在 Visual Studio Code (VSCode) 中&#xff0c;你可以使用内置的 Git 集成来查看某个文件的提交历史。以下是具体步骤&#xff1a; 使用 VSCode 内置 Git 功能 打开项目&#xff1a; 打开你的项目文件夹&#xff0c;确保该项目已经是一个 Git 仓库&#xff08;即项目根目录下…

vue2 -- el-form组件动态增减表单项及表单项验证

需求 在数据录入场景(如订单信息录入)中,可根据实际情况(如商品种类增加)动态添加表单项(如商品相关信息)。包含必填项验证和数据格式验证(如邮箱、电话格式),防止错误数据提交。 效果 代码一 <template><div>

使用 Elasticsearch 构建食谱搜索(一)

作者&#xff1a;来自 Elastic Andre Luiz 了解如何使用 Elasticsearch 构建基于语义搜索的食谱搜索。 简介 许多电子商务网站都希望增强其食谱搜索体验。正确使用语义搜索可以让客户根据更自然的查询&#xff08;例如 “something for Valentines Day - 情人节的礼物” 或 “…

SystemVerilog学习笔记(七):函数与任务

函数 函数的主要用途是编写一段可以随时调用n次的代码&#xff0c;只需调用函数名即可&#xff0c;不需要任何模拟时间来执行。函数是返回类型&#xff0c;仅返回函数声明中提到的单个值&#xff0c;如果未声明则返回一个位的值。 语法&#xff1a; initial begin functio…

物理验证Calibre LVS | SMIC Process过LVS时VNW和VPW要如何做处理?

SMIC家工艺的数字后端实现PR chipfinish写出来的带PG netlist如下图所示。我们可以看到标准单元没有VNW和VPW pin的逻辑连接关系。 前几天小编在社区星球上分享了T12nm ananke_core CPU低功耗设计项目的Calibre LVS案例&#xff0c;就是关于标准单元VPP和VBB的连接问题。 目前…

纯前端实现在线预览excel文件(插件: LuckyExcel、Luckysheet)

概述 在实际开发中&#xff0c;遇到需要在线预览各种文件的需求&#xff0c;最近遇到在线预览excel文件的需求&#xff0c;在此记录一下&#xff01;本文主要功能实现&#xff0c;用于插件 LuckyExcel &#xff0c;Luckysheet&#xff01;废话不多说&#xff0c;上代码&#xf…

LocalDate和LocalDateTime类

在Java 8中引入的LocalDate表示一个格式为yyyy-MM-dd的日期&#xff0c;如2024-06-13。它不存储时间或时区。我们可以从LocalDate中获取许多其他的日期字段&#xff0c;如年日(day-of-year)、周日(day-of-week)、月日(month-of-year)等等。 1 初始化 LocalDate以年月日的格式输…

信息安全工程师(82)操作系统安全概述

一、操作系统安全的概念 操作系统安全是指操作系统在基本功能的基础上增加了安全机制与措施&#xff0c;从而满足安全策略要求&#xff0c;具有相应的安全功能&#xff0c;并符合特定的安全标准。在一定约束条件下&#xff0c;操作系统安全能够抵御常见的网络安全威胁&#xff…

小程序源码-模版 100多套小程序(附源码)

一、搭建开发环境 搭建环境可以从这里开始&#xff1a; 微信小程序从零开始开发步骤&#xff08;一&#xff09;搭建开发环境 - 简书 二、程序示例 1、AppleMusic https://download.csdn.net/download/m0_54925305/89977187 2、仿B站首页 https://download.csdn.net/downlo…

安装baidubce库

直接pip install baidubce会带来一系列后续文件缺失问题&#xff0c;应该&#xff1a; pip install bce-python-sdk