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 策略模式优缺点
策略模式的优点很明显,有以下优点
- 扩展性:使用策略模式时,如果要添加新的策略十分方便也很简单,不用修改原有的代码,扩展性好。
- 解耦:客户端调用时只需要知道策略接口,而具体的实现不必担心。
- 动态性:可以在运行时动态进行不同策略的切换,提高了灵活性和适应性。
但是也有一定的缺点,为了实现每个策略类,都需要一个新的类进行独立的封装,增加了复杂性。但是与其扩展性来说,这点实际上是可以忽略的。
三、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博客