前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于spring-plugin做插件化开发

基于spring-plugin做插件化开发

作者头像
叔牙
发布2023-08-09 15:06:01
3.6K0
发布2023-08-09 15:06:01
举报
文章被收录于专栏:一个执拗的后端搬砖工

内容目录

一、什么是插件化开发二、如何实现插件化开发三、spring-plugin实现原理四、总结与思考五、参考

一、什么是插件化开发

插件化开发(Plugin Development)是一种软件开发模式,它将一个应用程序的功能拆分为模块,并允许在运行时动态加载、卸载和扩展这些模块,以增强应用程序的功能。插件化开发使得应用程序具有高度的可扩展性和灵活性,可以根据需要添加或删除功能,而无需修改核心代码。

在插件化开发中,应用程序的核心框架或主程序通常提供了一组基本的功能和接口,供插件进行扩展。插件则是独立开发的模块,它们可以独立于主程序进行开发、测试和部署。插件可以包含特定的功能、特性、视图、逻辑处理等,通过插件机制与主程序进行交互。

插件化开发的好处包括:

  • 模块化和解耦:插件化开发将应用程序拆分为多个模块,使得各个模块之间相对独立,降低了耦合性,提高了代码的可维护性。
  • 动态扩展:插件可以在运行时动态加载和卸载,实现了应用程序的动态扩展能力。新的功能可以通过添加或更新插件来快速地集成到应用程序中,无需重新编译和部署整个应用程序。
  • 定制化和灵活性:插件化开发可以根据不同的需求选择加载或卸载特定的插件,实现了应用程序的定制化和灵活性。用户可以根据自己的需求选择合适的插件,使得应用程序功能更加符合其个性化需求。
  • 独立开发和测试:插件可以作为一个独立的模块进行开发和测试,这样可以降低开发和测试的复杂性。同时,插件的独立性也使得多个开发团队可以并行地开发不同的插件,提高了开发效率。

简单来说,插件化开发是一种能够提供可扩展性、灵活性和定制化的软件开发模式。通常用在多通道解决方案接入场景,比如支付渠道接入、多平台能力集成等。

二、如何实现插件化开发

想实现插件化开发,基于一些流行的开源框架是一个不错的选择,可以节省研发成本和提高接入能效,以下是一些常见的插件化开发框架:

  • OSGi:OSGi(Open Service Gateway Initiative)是一个面向 Java 的动态模块化系统,提供了完整的解决方案来实现插件化开发。它定义了模块、生命周期管理、依赖管理等概念,可以在运行时动态加载、卸载和更新模块。
  • Apache Felix:是一个由Apache软件基金会开发的基于OSGi标准的开源项目。它是一个轻量级、灵活且可扩展的Java应用程序框架,用于构建面向模块化的、可插拔的应用程序。开发人员可以将应用程序划分为独立的模块,每个模块都可以使用自己的依赖项和功能。这种模块化的设计使得应用程序更易于维护、扩展和重用
  • JSPF:JSPF(Java Simple Plugin Framework)是一个轻量级的 Java 插件框架,它提供了简单的机制来实现插件化开发。JSPF 基于 Java 的 SPI(Service Provider Interface)机制,并提供了插件的生命周期管理、依赖注入等功能。
  • Spring Plugin:Spring Plugin 是 Spring 框架的一个扩展,用于实现插件化开发。它提供了插件注册、加载、卸载等功能,并基于 Spring 的依赖注入机制,使插件之间更加松耦合。
  • JPF(Java Plugin Framework):JPF 是一个开源的 Java 插件框架,用于构建基于插件的 Java 应用程序。JPF 提供了插件的扩展点定义、加载、卸载等功能,支持插件之间的依赖关系和版本管理。

这些是常见的插件化开发框架,还有其他更多可用的框架,此处不再一一枚举,可以根据具体需求选择合适的框架进行插件化开发。需要注意的是,每个框架都有其特定的特性和使用方式,您可以根据项目的需求、团队的技术栈和实践经验来选择最适合的框架。

我们选择其中的spring-plugin来作为接入示例,源于踏实它是spring大家族中的一员,无缝兼容。

1.引入依赖
代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.plugin</groupId>
    <artifactId>spring-plugin-core</artifactId>
    <version>2.0.0.RELEASE</version>
</dependency>
2.定义插件接口

定义插件接口,并继承Plugin接口:

代码语言:javascript
复制
public interface PaymentPlugin extends Plugin<String> {
    String getName(); // 获取支付渠道名称


    PayResponse pay(Order order); // 发起支付请求
    
    // 其他支付相关方法...
}
3.编写实现

我们模拟支付宝和微信支付:

代码语言:javascript
复制
//支付宝
@Component
public class AlipayPlugin implements PaymentPlugin {
    @Override
    public String getName() {
        return "Alipay";
    }
    @Override
    public PayResponse pay(Order order) {
        // 实现支付宝支付逻辑
        return new PayResponse();
    }
    @Override
    public boolean supports(String delimiter) {
        return "alipay".equals(delimiter);
    }
    // 其他支付宝支付相关方法...
}


//微信支付
@Component
public class WechatPayPlugin implements PaymentPlugin {
    @Override
    public String getName() {
        return "WechatPay";
    }
    @Override
    public boolean supports(String method) {
        return "wechatpay".equals(method);
    }
    @Override
    public PayResponse pay(Order order) {
        // 实现微信支付逻辑
        return new PayResponse();
    }
    // 其他微信支付相关方法...
}

编写插件工具服务类PaymentPluginRegistry:

代码语言:javascript
复制
@Service
public class PaymentPluginRegistry implements InitializingBean {
    @Autowired
    PluginRegistry<PaymentPlugin, String> pluginRegistry;
    private  List<PaymentPlugin> plugins;
    @Override
    public void afterPropertiesSet() throws Exception {
        this.plugins = this.pluginRegistry.getPlugins();
    }
    /**
     * 是否适配
     *
     * @param method
     * @return
     */
    public PaymentPlugin getPlugin(String method) {
        for (PaymentPlugin plugin : plugins) {
            if (plugin.supports(method)) {
                return plugin;
            }
        }
        throw new IllegalArgumentException("Unsupported payment method: " + method);
    }
}
4.定义插件配置

需要使用EnablePluginRegistries注解开启插件注册能力:

代码语言:javascript
复制
@Configuration
@EnablePluginRegistries(PaymentPlugin.class)
public class PaymentPluginConfig {
    
}
5.使用

最后在应用程序中可以注入PaymentPluginRegistry工具,并使用它来获取特定支付方式的支付插件,执行支付操作:

代码语言:javascript
复制
@RestController
public class PaymentController {


    @Autowired
    private PaymentPluginRegistry pluginRegistry;


    @PostMapping("/pay")
    public PayResponse pay(@RequestBody Order order, @RequestParam("method") String method) {
        PaymentPlugin plugin = pluginRegistry.getPlugin(method);
        return plugin.pay(order);
    }
}

三、spring-plugin实现原理

spring-plugin是一种更实用的插件开发方法,它提供了插件实现扩展核心系统功能的核心灵活性,但当然不提供核心OSGi功能,如动态类加载或插件的运行时安装和部署。尽管SpringPlugin因此不如OSGi强大,但它满足了简单场景构建模块化可扩展应用程序的需求。

并且从运行原理角度来看,它是策略模式的一种典型实现,那么它的实现原理是什么,我们从源码维度做一下分析。

开启spring-plugin插件化能力是@EnablePluginRegistries注解,先看一下其实现:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(PluginRegistriesBeanDefinitionRegistrar.class)
public @interface EnablePluginRegistries {
  Class<? extends Plugin<?>>[] value();
}

该注解声明了需要开启插件化能力的接口,并且导入了PluginRegistriesBeanDefinitionRegistrar配置,它是一个ImportBeanDefinitionRegistrar,会在springboot启动的时候调用AbstractApplicationContext刷新上下文的时候调用其registerBeanDefinitions方法,我们先分析一下PluginRegistriesBeanDefinitionRegistrar的registerBeanDefinitions方法做了什么事情:

代码语言:javascript
复制

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。代码中有个细节,BeanDefination设置的目标类型是PluginRegistry。

也就是在使用插件能力的时候,注入类型是PluginRegistry,而PluginRegistryFactoryBean是一个FactoryBean,所以注入PluginRegistry类型的时候实际是调用PluginRegistryFactoryBean的getObject返回的内容。

先看一下PluginRegistryFactoryBean的实现:

代码语言:javascript
复制
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;
  }
}

注入的时候返回的类型是OrderAwarePluginRegistry,注入调用getObject返回,里边调用了父类AbstractTypeAwareSupport的getBeans方法。简单看一下AbstractTypeAwareSupport重要内容:

代码语言:javascript
复制
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接口,初始化的时候会调用afterPropertiesSet方法,该实现注入了ApplicationContext上下文,并且基于上下文和type封装成BeansOfTypeTargetSource赋值给targetSource变量,BeansOfTypeTargetSource是TargetSource类型,getTarget返回基于实际类型封装的增强类型:

代码语言:javascript
复制
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());
}

本质就是从ListableBeanFactory中获取插件接口实现列表封装返回。

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

代码语言:javascript
复制
public static <S, T extends Plugin<S>> OrderAwarePluginRegistry<T, S> of(List<? extends T> plugins,
    Comparator<? super T> comparator) {
  return new OrderAwarePluginRegistry<>(plugins, comparator);
}

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

代码语言:javascript
复制
@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();
}

分别是返回所有插件接口实现和符合条件的插件实现。其实上一小节中PaymentPluginRegistry工具类中可以直接使用getPluginFor方法代替:

代码语言:javascript
复制
public PaymentPlugin getPlugin(String method) {
    return this.pluginRegistry.getPluginFor(method);
}

到这里插件注入和使用原理基本分析清楚了,继续看一下PluginRegistriesBeanDefinitionRegistrar的registerBeanDefinitions方法如何被调用的。

ImportBeanDefinitionRegistrar的解析调用也是用到了核心类ConfigurationClassPostProcessor,与ImportSelector以及DeferredImportSelector解析调用类似,参考《ConfigurationClassPostProcessor原理》,此处不再做过多重复分析,两个地方需要注意。

ConfigurationClassParse的processImports方法:

代码语言:javascript
复制
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
    Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
    boolean checkForCircularImports) {


  if (checkForCircularImports && isChainedImportOnStack(configClass)) {
    this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
  }
  else {
    this.importStack.push(configClass);
    try {
      for (SourceClass candidate : importCandidates) {
        if (candidate.isAssignable(ImportSelector.class)) {
          //省略...
        }
        else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
          // Candidate class is an ImportBeanDefinitionRegistrar ->
          // delegate to it to register additional bean definitions
          Class<?> candidateClass = candidate.loadClass();
          ImportBeanDefinitionRegistrar registrar =
              ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                  this.environment, this.resourceLoader, this.registry);
          configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        }
        else {
          //省略...
        }
      }
    }
  }
}

这里是解析ImportBeanDefinitionRegistrar实现。另外一个地方是ConfigurationClassBeanDefinitionReader的loadBeanDefinitionsForConfigurationClass方法:

代码语言:javascript
复制
private void loadBeanDefinitionsForConfigurationClass(
    ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
  //省略...
  loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}


private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
  registrars.forEach((registrar, metadata) ->
      registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

这里就调用到了PluginRegistriesBeanDefinitionRegistrar的registerBeanDefinitions方法。springboot刷新上下文到调用registerBeanDefinitions的大致过程如下:

四、总结与思考

从spring-plugin的代码结构不难看出,它基本上算是spring家族中最小的框架,它的本质是基于软件开发中的策略模式来解耦业务逻辑,也就是其宣称的插件化开发模式,但是上述功能我们不使用spring-plugin,自己简单写个工厂结合策略模式也能实现,并且复杂度也没有spring-plugin高。任何软件设计都是有缺陷的,不能解决特定应用场景所有的问题,我们简单分析下上述基于spring-plugin实现插件化开发所存在或者说没有解决的问题。

1.动态加载问题

理想状态下,基于插件化的业务开发,需要支持动态加载和卸载能力,也就是说主项目中定义插件接口能力,插件模块按照接口规范开发一整套能力,然后主项目定义了扩展加载点,把插件实现打包放到指定位置,然后主项目服务中手动或者自动的方式触发热加载,在不重启的前提下加载插件能力到运行空间,目前还没有很好的实现。

2.完备隔离空间

插件可以理解为一个最小颗粒度完备的空间,包含依赖以及版本,需要加载后能够使用主项目服务中的通用依赖,并且自己持有的依赖在被加载后不会对其他插件以及主项目造成冲突,比如插件引入了一个jar的依赖与主项目冲突了,被加载之后可能造成主服务无法运行或者插件无法正常加载,这种情况下需要使用一些高级用法,基于自定义类加载器来实现插件的依赖与主项目以及其他插件形成隔离。

3.性能问题

插件化开发可能引入一定的性能开销,特别是在动态加载和卸载插件时。如果插件数量庞大或者插件逻辑复杂,可能会影响应用程序的性能。

4.安全性

插件化开发可能引入安全性问题。插件的代码执行在应用程序的上下文中,如果插件没有正确限制其访问权限,可能会导致安全漏洞,如访问敏感数据或执行恶意代码。

五、参考

https://github.com/spring-projects/spring-plugin

https://www.cnblogs.com/strongmore/p/15248730.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是插件化开发
  • 二、如何实现插件化开发
    • 1.引入依赖
      • 2.定义插件接口
        • 3.编写实现
          • 4.定义插件配置
            • 5.使用
            • 三、spring-plugin实现原理
            • 四、总结与思考
              • 1.动态加载问题
                • 2.完备隔离空间
                  • 3.性能问题
                    • 4.安全性
                    • 五、参考
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档