内容目录
一、什么是插件化开发二、如何实现插件化开发三、spring-plugin实现原理四、总结与思考五、参考
插件化开发(Plugin Development)是一种软件开发模式,它将一个应用程序的功能拆分为模块,并允许在运行时动态加载、卸载和扩展这些模块,以增强应用程序的功能。插件化开发使得应用程序具有高度的可扩展性和灵活性,可以根据需要添加或删除功能,而无需修改核心代码。
在插件化开发中,应用程序的核心框架或主程序通常提供了一组基本的功能和接口,供插件进行扩展。插件则是独立开发的模块,它们可以独立于主程序进行开发、测试和部署。插件可以包含特定的功能、特性、视图、逻辑处理等,通过插件机制与主程序进行交互。
插件化开发的好处包括:
简单来说,插件化开发是一种能够提供可扩展性、灵活性和定制化的软件开发模式。通常用在多通道解决方案接入场景,比如支付渠道接入、多平台能力集成等。
想实现插件化开发,基于一些流行的开源框架是一个不错的选择,可以节省研发成本和提高接入能效,以下是一些常见的插件化开发框架:
这些是常见的插件化开发框架,还有其他更多可用的框架,此处不再一一枚举,可以根据具体需求选择合适的框架进行插件化开发。需要注意的是,每个框架都有其特定的特性和使用方式,您可以根据项目的需求、团队的技术栈和实践经验来选择最适合的框架。
我们选择其中的spring-plugin来作为接入示例,源于踏实它是spring大家族中的一员,无缝兼容。
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
定义插件接口,并继承Plugin接口:
public interface PaymentPlugin extends Plugin<String> {
String getName(); // 获取支付渠道名称
PayResponse pay(Order order); // 发起支付请求
// 其他支付相关方法...
}
我们模拟支付宝和微信支付:
//支付宝
@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:
@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);
}
}
需要使用EnablePluginRegistries注解开启插件注册能力:
@Configuration
@EnablePluginRegistries(PaymentPlugin.class)
public class PaymentPluginConfig {
}
最后在应用程序中可以注入PaymentPluginRegistry工具,并使用它来获取特定支付方式的支付插件,执行支付操作:
@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是一种更实用的插件开发方法,它提供了插件实现扩展核心系统功能的核心灵活性,但当然不提供核心OSGi功能,如动态类加载或插件的运行时安装和部署。尽管SpringPlugin因此不如OSGi强大,但它满足了简单场景构建模块化可扩展应用程序的需求。
并且从运行原理角度来看,它是策略模式的一种典型实现,那么它的实现原理是什么,我们从源码维度做一下分析。
开启spring-plugin插件化能力是@EnablePluginRegistries注解,先看一下其实现:
@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方法做了什么事情:
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的实现:
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重要内容:
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返回基于实际类型封装的增强类型:
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类型:
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方法:
@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方法代替:
public PaymentPlugin getPlugin(String method) {
return this.pluginRegistry.getPluginFor(method);
}
到这里插件注入和使用原理基本分析清楚了,继续看一下PluginRegistriesBeanDefinitionRegistrar的registerBeanDefinitions方法如何被调用的。
ImportBeanDefinitionRegistrar的解析调用也是用到了核心类ConfigurationClassPostProcessor,与ImportSelector以及DeferredImportSelector解析调用类似,参考《ConfigurationClassPostProcessor原理》,此处不再做过多重复分析,两个地方需要注意。
ConfigurationClassParse的processImports方法:
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方法:
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实现插件化开发所存在或者说没有解决的问题。
理想状态下,基于插件化的业务开发,需要支持动态加载和卸载能力,也就是说主项目中定义插件接口能力,插件模块按照接口规范开发一整套能力,然后主项目定义了扩展加载点,把插件实现打包放到指定位置,然后主项目服务中手动或者自动的方式触发热加载,在不重启的前提下加载插件能力到运行空间,目前还没有很好的实现。
插件可以理解为一个最小颗粒度完备的空间,包含依赖以及版本,需要加载后能够使用主项目服务中的通用依赖,并且自己持有的依赖在被加载后不会对其他插件以及主项目造成冲突,比如插件引入了一个jar的依赖与主项目冲突了,被加载之后可能造成主服务无法运行或者插件无法正常加载,这种情况下需要使用一些高级用法,基于自定义类加载器来实现插件的依赖与主项目以及其他插件形成隔离。
插件化开发可能引入一定的性能开销,特别是在动态加载和卸载插件时。如果插件数量庞大或者插件逻辑复杂,可能会影响应用程序的性能。
插件化开发可能引入安全性问题。插件的代码执行在应用程序的上下文中,如果插件没有正确限制其访问权限,可能会导致安全漏洞,如访问敏感数据或执行恶意代码。
https://github.com/spring-projects/spring-plugin
https://www.cnblogs.com/strongmore/p/15248730.html
本文分享自 PersistentCoder 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!