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 {

    @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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Works With线上开发者大会将提供物联网行业深入的专业知识和技能

Silicon Labs2024年Works With线上开发者大会定于11月20日至21日举行&#xff0c;将汇集全球各地的物联网开发人员、设备制造商、无线技术专家、工程师和商业领袖&#xff0c;观众可免费注册参加。同时&#xff0c;为了方便中文观众&#xff0c;所有在线视频均配有中文字幕。 芯…

一文读懂 Web 安全

Web 安全是互联网中不可或缺的一个领域&#xff0c;这个领域中诞生了大量的黑帽子与白帽子&#xff0c;他们都是安全领域的王者&#xff0c;在平时里&#xff0c;他们利用各种巧妙的技术互相博弈&#xff0c;时不时就会掀起一场 Web 安全浪潮&#xff0c;真可谓神仙打架&#x…

iOS问题记录 - 503 Service Temporarily Unavailable

文章目录 前言开发环境问题描述问题分析解决方案最后 前言 最近有个项目经历了大改动&#xff0c;本地测试没什么问题&#xff0c;于是准备通过打包机打包用于内部测试的包&#xff0c;然后问题就来了。 开发环境 Xcode: 16.1Fastlane: 2.219.0 问题描述 问题出在登录苹果…

数据网格能替代数据仓库吗?

一、数据网格是什么&#xff1f; 数据网格&#xff1a;是一种新兴的数据管理架构和理念&#xff0c;主要用于解决大规模、复杂数据环境下的数据管理和利用问题。 核心概念&#xff1a; 1、数据即产品&#xff1a;将数据看作一种产品&#xff0c;每个数据域都要对其生产的数据负…

Dolphinscheduler配置dataX离线采集任务写入hive实践(二)

这里写目录标题 一、 写入hive 配置1.1 权限报错信息 &#xff1a;1.2 hive 中文件格式1.3 注意区别以下建表语句A、构建ORC 格式分区表B. 构建默认文件格式分区表C.构建非分区表 二、dataX 配置hive 分区表导入 配置2.1 检查hive 表分区是否存在 一、 写入hive 配置 dataX 写…

机器学习——损失函数、代价函数、KL散度

&#x1f33a;历史文章列表&#x1f33a; 机器学习——损失函数、代价函数、KL散度机器学习——特征工程、正则化、强化学习机器学习——常见算法汇总机器学习——感知机、MLP、SVM机器学习——KNN机器学习——贝叶斯机器学习——决策树机器学习——随机森林、Bagging、Boostin…

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

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

ODOO学习笔记(2):核心功能是哪些?

Odoo的核心功能主要包括以下几个方面&#xff1a; 客户关系管理&#xff08;CRM&#xff09;&#xff1a;Odoo的CRM工具管理公司与客户和潜在客户的所有关系和互动&#xff0c;提供一个集中式系统来管理销售活动&#xff0c;从潜在客户培育到达成交易。CRM模块包括报告和分析功…

JUC学习

JUC 1、什么是JUC&#xff1f; ​ JUC是java.util.concurrent包的简称&#xff0c;在Java5.0添加&#xff0c;目的就是为了更好的支持高并发任务。在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义…

Kafka面试题解答(二)

1.怎么尽可能保证 Kafka 的可靠性 kafka是可能会出现数据丢失问题的&#xff0c;Leader维护了一个动态的in-sync replica set&#xff08;ISR&#xff09;&#xff0c;意为和 Leader保持同步的FollowerLeader集合(leader&#xff1a;0&#xff0c;isr:0,1,2)。如果Follower长时…

如何在算家云搭建Aatrox-Bert-VITS2(音频生成)

一、模型介绍 ‌ Aatrox - Bert -VITS2 模型是一种基于深度学习的语音合成系统&#xff0c;结合了 BERT 的预训练能力和 VITS2 的微调技术&#xff0c;旨在实现高质量的个性化语音合成。 二、模型搭建流程 1. 创建容器实例 进入算家云的“应用社区”&#xff0c;点击搜索找到…

PDF编辑工具Adobe Acrobat DC 2023安装教程(附安装包)

Adobe Acrobat DC 2023 是 Adobe 公司推出的一款功能强大的 PDF 文档处理软件。它不仅支持创建、编辑和签署 PDF 文件&#xff0c;还提供了丰富的工具来管理和优化这些文件。以下是 Acrobat DC 2023 的一些主要特点&#xff1a; 1.PDF 创建与编辑&#xff1a;用户可以直接从多…

【李白打酒加强版——DP】

题目 思路 三个注意点&#xff1a;k是偶数的状态才能是遇到店之后的状态、f[n-1][m][k]状态非法、不要越界 代码 #include <bits/stdc.h> using namespace std; const int N 110, mod 1e97; int f[N][N][N]; int main() {int n, m;cin >> n >> m;f[0][0]…

HarmonyOS ArkTS 下拉列表组件

Entry Component struct Index {defaultValue: string 下拉列表;// 定义选项数组&#xff0c;包含 value 和可选的 labeloptions: Array<SelectOption> [{ value: aaa },{ value: bbb },{ value: ccc },{ value: ddd },{ value: eee },{ value: fff },{ value: ggg },{…

云上盛宴-腾讯云双11活动玩法攻略

大家好呀&#xff01;我是reload。今天来给大家分享一下腾讯云双十一活动玩法攻略。一年一度的双十一购物盛宴又要来了&#xff0c;为了应对各种秒杀抢购而导致的用户访问量激增&#xff0c;平台可以通过云计算技术的弹性伸缩能力&#xff0c;迅速增加服务器资源&#xff0c;确…

【ubuntu18.04】使用U盘制作ubuntu18.04启动盘操作说明

打开show application 打开Startup Disk 选择镜像 双击选择ubuntu的iso镜像 镜像下载地址 Ubuntu 18.04.6 LTS (Bionic Beaver) 制作镜像 注意&#xff1a; 制作镜像会格式化U盘&#xff0c;记得备份资料 点击Make Startup Disk,弹出如下对话框 点击Yes 输入管理员密码&a…

html+js+css实现拖拽式便签留言

前些日子在网上冲浪时&#xff0c;看到一个便签式留言墙&#xff0c;让人耳目一新。心想这个看着不错&#xff0c;额想要。于是便开始搜寻是否有相应开源插件&#xff0c;想将其引入自己的博客中。但是搜寻了一圈&#xff0c;都没有符合预期的,要么功能不符合。有的功能符合&am…

前端 call、bind、apply的实际使用

目录 一、call 1、继承的子类可以使用父类的方法 2、可以接收任意参数 二、call、apply、bind比较 1、案例一 2、案例二 三、总结 这个三个方法都是改变函数的this指向的方法。 一、call 1、继承的子类可以使用父类的方法 function Animal(){//this 指向小catthis.eat…

【C++笔记】C++三大特性之继承

【C笔记】C三大特性之继承 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】C三大特性之继承前言一.继承的概念及定义1.1 继承的概念1.2继承的定义1.3继承基类成员访问方式的变化1.4继承类模板 二.基类和派生类间的转…

点击文本将内容填入tinymce-vue 富文本编辑器的光标处

富文本编辑器组件 <template><div ref"tinymceBox" class"tinymce-box"><Editor id"myEditor" v-model"contentValue" :init"init" :disabled"disabled" blur"inputBlur" click"o…