自研扩展点组件

459 阅读7分钟
  1. 组件概述

1.1 要解决的核心问题

在业务系统开发中,经常遇到这样的场景:同一个业务接口(如支付、风控、消息推送),需要根据不同的业务场景(如不同的客户、渠道、产品类型)切换到不同的实现算法。 传统的做法可能是使用大量的 if-elseswitch-case语句进行硬编码,导致代码臃肿、重复率高、可维护性差,且每次新增场景都需要修改原有代码,违反开闭原则。本组件旨在提供一套优雅、灵活、非侵入式的解决方案,使得业务逻辑能够根据场景码(BizScenario)自动路由到对应的实现类,从而实现业务的柔性扩展和高效治理。

1.2 设计目标

  • 解耦:将接口的定义与其具体实现完全分离。
  • 动态路由:在运行时,根据传入的业务场景信息,动态选择并执行合适的实现。
  • 开闭原则:系统支持扩展新的实现类,而无需修改现有的核心代码和路由逻辑。
  • 易于集成:通过与Spring框架无缝集成,实现扩展点的自动扫描和注册。
  1. 核心设计理念

该组件的设计深受策略模式和微内核架构的影响。其核心思想是将每个业务场景的具体实现视为一个可插拔的“策略”或“插件”。框架本身作为一个“微内核”,负责管理这些插件的生命周期和路由策略,而不关心插件的具体业务逻辑。

image.png 3. ## 核心模块详解

3.1 元数据与建模(BizScenario, ExtensionCoordinate

这是框架的基石,用于精确描述一个扩展点实例。

  • BizScenario(业务场景):定义了路由的维度。通过 domain(领域)、scene(场景)等属性,唯一确定一个业务身份。其 getUniqueIdentity()方法生成的路由键是寻址的关键。
  • ExtensionCoordinate(扩展点坐标):是 BizScenario和 扩展点接口类型 的组合。它构成了存储和查找扩展点的唯一键(Key),其正确重写的 equalshashCode方法是保证在 Map中正确工作的前提。

3.2 注册中心(ExtensionRepository

这是框架的服务注册表,扮演着核心的存储角色。

  • 职责:维护一个 ConcurrentHashMap<ExtensionCoordinate, ExtensionPoint>,用于存储所有注册到容器中的扩展点实例。
  • 特点:线程安全,提供了最基本的注册(registerExtPoint)和查询(getExtension)方法。

3.3 自动注册器(ExtensionAutoRegister

这是框架与Spring容器集成的关键,实现了“服务自动发现”。

  • 时机:在Spring容器启动完成后,通过 @PostConstruct注解触发。

  • 过程:

    • 扫描:利用Spring的 ApplicationContext,获取所有带有 @Extension注解的Bean。
    • 注册:遍历这些Bean,并将其交给 ExtensionRegister完成具体的注册逻辑。

3.4 注册逻辑核心(ExtensionRegister

这是“服务注册” 逻辑的核心,负责处理每个扩展点实现类的元信息并将其注册到仓库中。

  • 关键步骤:

    • 解析注解:从实现类上解析 @Extension注解,获取 domainscene信息,构建 BizScenario对象。
    • 计算扩展点接口:通过 calculateExtensionPoint方法,遍历实现类的所有接口,找到那个直接或间接继承自标记接口 ExtensionPoint的接口。该接口的全限定名是路由的另一半关键信息。
    • 查重与注册:将 BizScenario和接口类型组合成 ExtensionCoordinate,检查是否已存在,若无则注册到 ExtensionRepository

3.5 执行引擎(ExtensionExecutor

这是框架面向用户的API网关,是“服务发现与调用” 的入口。

  • 设计:继承自 AbstractComponentExecutor,模板方法模式封装了执行流程。

  • 核心方法:<T> T locateComponent(Class<T> targetClz, BizScenario bizScenario)

  • 双保险路由策略:

    • 首先尝试根据精确的 BizScenario查找实现。
    • 若未找到,则尝试使用默认的 BizScenario(即 domainscene为默认值)进行查找,这为实现全局默认逻辑提供了可能。
    • 若仍未找到,则抛出异常。
  1. 如何使用:四步上手

4.1 第一步:引入jar包

4.2 第二步:定义扩展点接口

接口需要直接或间接继承标记接口 ExtensionPoint

public interface PaymentService extends ExtensionPoint {
    PayResponse pay(PayRequest request);
}

4.3 第三步:实现扩展点(使用 @Extension注解)

为实现类添加 @Extension注解,并指定其所属的业务场景。

@Component
@Extension(domain = "payment", scene = "wechat", value = "微信支付场景")
public class WechatPaymentService implements PaymentService {
    @Override
    public PayResponse pay(PayRequest request) {
         // 微信支付的具体逻辑
        return new PayResponse("Wechat Pay Success");
    }
}
@Component
@Extension(domain = "payment", scene = "alipay", value = "支付宝支付场景")
public class AlipayPaymentService implements PaymentService {
    @Override
    public PayResponse pay(PayRequest request) {
         // 支付宝支付的具体逻辑
        return new PayResponse("Alipay Pay Success");
    }
}

4.4 第四步:在代码中调用执行

在Service或Controller中,注入 ExtensionExecutor,通过业务场景执行相应逻辑。

@RestController
public class PaymentController {
    @Resource
    private ExtensionExecutor extensionExecutor;
    @PostMapping("/pay")
    public PayResponse pay(@RequestParam String channel, @RequestBody PayRequest request){
         // 1. 构建业务场景
        BizScenario bizScenario = new BizScenario.Builder()
                .domain("payment")
                .scene(channel)  // 如 "wechat", "alipay" 
                .build();
         // 2. 执行扩展点
        return extensionExecutor.execute(PaymentService.class, bizScenario, paymentService -> paymentService.pay(request));
    }
}

4.5 第五步:享受自动化带来的便利

无需手动管理 PaymentService的实现类。框架会自动扫描、注册它们。当需要新增一种支付方式(例如“云闪付”)时,只需:

  1. 创建一个新的 YunshanPaymentService实现类并加上 @Extension(domain = "payment", scene = "unionpay")注解。
  2. 重新启动应用即可。
  1. 总结与优势

5.1 设计模式汇总

设计模式解决的核心问题在组件中的价值
策略模式避免大量的if-else分支核心价值:实现基于场景码的动态路由
工厂模式对象创建的复杂性统一管理核心组件的实例化
模板方法算法骨架的复用封装统一的扩展点执行流程
注册表模式服务的集中管理提供高效的服务查找和路由

5.2 架构价值

  • 架构清晰:各组件职责单一,符合单一职责原则。
  • 非侵入性:业务实现类只需添加一个注解,对原有代码无侵入。
  • 高可扩展性:新增业务场景实现无需修改任何框架和路由代码。
  • 与Spring生态无缝集成:利用Spring的IoC容器进行Bean管理和依赖注入。
  • 类型安全:在编译期即可保证类型匹配,避免运行时类型转换错误。

5.3 简化调用与提升开发效率

函数式编程的结合使得组件调用变得非常简洁,直接提升了开发效率。

  • Lambda表达式简化调用:使用者无需编写繁琐的匿名内部类,可以直接用Lambda表达式或方法引用简洁地表达业务逻辑。这使得代码非常紧凑,可读性更强。
// 传统的命令式风格可能需要这样:
ExtensionPoint component = executor.locateComponent(SomeInterface.class, bizScenario);
SomeResult result = component.doSomething(input);

// 而使用函数式设计,只需要一行:
SomeResult result = executor.execute(SomeInterface.class, bizScenario, comp -> comp.doSomething(input));

设计思路:模板方法封装固定流程:将“定位组件 -> 执行逻辑”这个固定流程封装在 execute方法中,使用者只需关注核心业务逻辑(即 FunctionConsumer中的代码)。这极大地减少了模板代码,让业务逻辑更加内聚和清晰。

5.4 支持多样化的业务场景

通过区分有返回值和无返回值的操作,您的设计能够灵活应对不同的业务场景。

  • Function<T, R>用于有返回值的操作,例如查询、计算、数据转换等。
  • Consumer<T>用于无返回值的操作,例如发送消息、更新状态、触发流程等。这种区分使得组件API更加精确,符合不同场景的语义要求。

5.5 进阶用法

这套框架非常适用于具有多租户、多渠道、多业务线等需要根据上下文进行动态路由的场景,能极大提升代码的整洁度和团队的开发效率。