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

news2025/1/9 16:59:01

目录

一、策略模式

二、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 {

    @Override
    public PayResult pay(PaymentReq paymentReq) {
        // 模拟支付宝支付流程
        return new PayResult();
    }

    @Override
    public boolean supports(String payment) {
        // 支付方式是否为支付宝,这里简化一些,正常情况下需要使用枚举
        return "alipay".equals(payment);
    }
}

// 微信支付实现
@Service
public class WechatPayService implements PaymentStrategy {

    @Override
    public PayResult pay(PaymentReq paymentReq) {
        // 模拟微信支付流程
        return new PayResult();
    }

    @Override
    public boolean supports(String payment) {
        // 支付方式是否为微信,这里简化一些,正常情况下需要使用枚举
        return "wechatpay".equals(payment);
    }
}

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

        定义插件配置

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

}

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

@RestController
public class PaymentController {

    @Autowired
    private 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 方法实现如下:

@Override
	public 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 registry
			if (annotation != null) {
				AutowireCandidateQualifier qualifierMetadata = new AutowireCandidateQualifier(Qualifier.class);
				qualifierMetadata.setAttribute(AutowireCandidateQualifier.VALUE_KEY, annotation.value());
				beanDefinition.addQualifier(qualifierMetadata);
			}

			// Default
			String 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>> {


	@NonNull
	public OrderAwarePluginRegistry<T, S> getObject() {
		return OrderAwarePluginRegistry.of(getBeans());
	}


	@NonNull
	public 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.coloradmin.cn/o/2237943.html

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

相关文章

Word大珩助手:超大数字怎么读?35位数字?69位数字?

俄罗斯日前对谷歌开出了20000000000000000000000000000000000&#xff08;35位数字&#xff09;美元的罚款 这一数字远超全球GDP总和&#xff0c;消息一出很快就登上热搜。 面对这样一个庞大的数字&#xff0c;人们不禁好奇&#xff0c;这样的数字该如何读出来&#xff1f; …

asp.net文件防盗链

URLRewriter实现 可以参考下面的文章 代码 .net framework 新建asp.net framework的web项目&#xff0c;新建AntiTheftChainHandler using System.Web;namespace AntiTheftChainStu01.Handler {public class AntiTheftChainHandler : IHttpHandler{public bool IsReusable…

【含开题报告+文档+PPT+源码】基于SSM的蛋糕店销售管理系统的设计与实现

开题报告 在现代社会&#xff0c;蛋糕作为一种受欢迎的甜点&#xff0c;广泛应用于各种庆祝活动和节日。传统的蛋糕预订方式往往需要用户亲自到店面进行预订&#xff0c;预订流程繁琐&#xff0c;时间和地点限制也给用户带来了不便。随着智能手机和移动互联网的普及&#xff0…

政治经济学笔记

【拯救者】政治经济学速成&#xff08;基础习题&#xff09; 研究生产关系必须联系生产力和上层建筑 1.生产力与生产关系 生产力代表生产的物质内容&#xff0c;生产关系是生产的社会形式。生产力决定生产关系&#xff0c;生产关系对生产力具有 反作用 *其中的”反作用”指的是…

005.精读《B-Tree vs LSM-Tree》

文章目录 1. 引言&#xff1a;2. 精读2.1 性能指标2.2 B-tree2.3 LSM-tree2.4 性能对比 3. 写在最后 1. 引言&#xff1a; 在本期的技术深度解析中&#xff0c;我们将聚焦于数据领域的两个重要成员——B-Tree和LSM-Tree。这两种数据结构在数据管理系统中最为普遍且广泛采用的数…

关于 el-table 的合计行问题

目录 一.自定义合计行 二.合计行不展示&#xff0c;只有缩放/变大窗口或者F12弹出后台时才展示 三.合计行出现了表格滚动条下方 四.合计行整体样式的修改 五.合计行单元格样式修改 1.css 2.jsx方式 六.合计行单元格合并 一.自定义合计行 通过 show-summary 属性开启合计…

C++ | Leetcode C++题解之第554题砖墙

题目&#xff1a; 题解&#xff1a; class Solution { public:int leastBricks(vector<vector<int>>& wall) {unordered_map<int, int> cnt;for (auto& widths : wall) {int n widths.size();int sum 0;for (int i 0; i < n - 1; i) {sum wi…

如何使用 C# 编写一个修改文件时间属性的小工具?

下面是简鹿办公一个用 C# 编写的简单工具&#xff0c;它可以批量修改文件的创建时间、最后访问时间和最后修改时间。我们将使用 .NET Framework 或 .NET Core 来实现这个功能。 完整示例代码 1. 创建一个新的 C# 控制台应用程序 您可以使用 Visual Studio 或 .NET CLI 创建一个…

使用FTP与多个合作伙伴传文件,如何处理运维管理和数据安全问题

许多行业的企业使用FTP与外部客户、供应商等合作伙伴进行文件交换&#xff0c;如大型保险公司、研究所、IC设计企业、汽车制造厂商等。基于FTP可以满足企业与外部合作伙伴文件收发的基础需求&#xff0c;但在IT运维管理、数据安全保障及业务便利性上仍存在不同程度的缺陷和不足…

Simulink中Matlab function使用全局变量

目录 一. 引言二. 普通Matlab function使用全局变量三. Simulink中的Matlab function使用全局变量四. 如何利用Matlab function的全局变量施加随机噪声 一. 引言 最近发现了之前仿真中的一个问题&#xff0c;记录一下备忘。 Matlab function中有时候需要用到全局变量&#xf…

Jmeter的安装,设置中文,解决乱码问题

1.Jmeter安装 1-Jmeter如何下载 1---我这里提供一个下载快的方式 https://www.123684.com/s/lWZKVv-4jiav?提取码:4x4y 2---Jmeter官网下载地址 Apache JMeter - Download Apache JMeter 2-配置java环境 1---下载javaJDK 官方下载地址 https://www.oracle.com/java/techno…

深 度 学 习

神经网络基础 一、逻辑回归( Logic Regression ) 1 问题的模型 模型&#xff1a; 其中xx为输入量&#xff0c;y^​预测量&#xff0c;σ()激活函数。   逻辑回归主要用于二分类问题的拟合&#xff1a;0≤y^P(y1∣x)≤1&#xff0c;σ(z)如图&#xff1a; ​ 问题&#xff…

华为OD机试 - 最低位排序 - 数组(Java 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…

C语言:文件操作2(又一万字?)

关于文件操作这章内容&#xff0c;因为知道内容较多所以我分两篇发了&#xff0c;但是还是没料到第二篇还是这么多&#xff0c;达到了一万多字&#xff01;&#xff01;&#xff01;作者本人真的将知识点进行了超级详解分析并且举了很多例子来帮助读者理解&#xff0c;本文章较…

RabbitMQ队列详细属性(重要)

RabbitMQ队列详细属性 1、队列的属性介绍1.1、Type&#xff1a;队列类型1.2、Name&#xff1a;队列名称1.3、Durability&#xff1a;声明队列是否持久化1.4、Auto delete&#xff1a; 是否自动删除1.5、Exclusive&#xff1a;1.6、Arguments&#xff1a;队列的其他属性&#xf…

【大模型】相比现有智能体(Agent)系统,微软新推出的 Magnetic-One 值得一看吗?

微软最近发布的Magnetic-One智能体系统在开源社区引发了广泛关注&#xff0c;因其在性能、灵活性和扩展性方面表现出色&#xff0c;被誉为目前开源社区最强的智能体解决方案。本文将从评测结果、工作原理、与现有智能体系统的比较三个方面&#xff0c;全面解析Magnetic-One的独…

C++——左值和右值的本质区别

左值和右值好干嘛&#xff1f; 深入理解左值和右值可以帮助我们对代码进行优化 一、什么是左值和右值 左值&#xff1a;有某种存储支持的变量 右值&#xff1a;临时值&#xff08;字面量、函数的结果&#xff09; Ⅰ右值是字面量 int yy 22;22本身就是一个临时的&#xf…

Rust-AOP编程实战

文章本天成,妙手偶得之。粹然无疵瑕,岂复须人为?君看古彝器,巧拙两无施。汉最近先秦,固已殊淳漓。胡部何为者,豪竹杂哀丝。后夔不复作,千载谁与期? ——《文章》宋陆游 【哲理】文章本是不加人工,天然而成的,是技艺高超的人在偶然间所得到的。其实作者所说的“天成”…

深入了解支持向量机:机器学习中的经典算法

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

第01章 Linux概述及系统环境搭建

目标: ◆ 知道 Linux 是什么&#xff1f;有什么特点&#xff1f; ◆ 知道 Linux 内核及发行版的区别 ◆ 知道 Linux 的应用领域 ◆ 能够在虚拟机软件上新建虚拟机 ◆ 能够在虚拟机中挂载CentOS6.7光盘镜像 ◆ 能够根据需求安装CentOS6.7的操作系统 ◆ 能够对系统进行登录和关闭…