跳至主要內容

SPI机制-扩展点

Mr.Hope大约 9 分钟

SPI机制-扩展点

扩展点结合拦截器的设计,oinone可以点、线、面一体化管理Function

扩展点的定义

扩展点是一种类似于SPI(Service Provider Interface)机制的服务发现机制,用于扩展函数逻辑,提供了一种灵活的扩展方式。扩展点允许系统在运行时动态地发现并加载实现了特定接口或注解的类,并在运行时调用它们的方法,从而扩展系统的功能。

扩展点的作用

在日常开发中,随着对业务理解的深入,往往还在一些逻辑中会预留扩展点,以便日后应对不同需求时可以灵活替换某一小块逻辑。

注解详解

定义扩展点

@Ext
@Ext(value="")

这段代码定义了一个名为 Ext 的注解,它具有以下属性:

  • value: 扩展点所扩展函数所在类,默认为 Empty.class

作用范围:

  • 注解标识在类上。

扩展点命名空间的确定遵循以下逻辑:

  1. 在接口上使用 @Ext 声明扩展点命名空间。
  2. 如果在当前接口上找不到 @Ext 注解,则会向上左遍历查找,直到找到第一个带有 @Ext 注解的接口,并返回其 value 注解值作为扩展点命名空间。
  3. 如果整个继承链中都找不到带有 @Ext 注解的接口,则返回扩展点全限定类名作为命名空间。

扩展点命名空间为: pro.shushi.pamirs.demo.api.extpoint.PetCatItemQueryCatTypeExtpoint

@ExtPoint
@ExtPoint(displayName = "",summary = "")

这段代码定义了一个名为 ExtPoint 的注解,它具有以下属性:

  • displayName: 展示名称。
  • summary: 描述。

作用范围:

  • ExtPoint 注解标识在方法上。
@ExtPoint.name
@ExtPoint.name(value = "")

这段代码定义了一个名为 name 的注解,它具有以下属性:

  • value: 技术名称,默认为方法名。

作用范围:

  • 注解标识在方法上。

扩展点技术名称为: 先取@ExtPoint.name,若为空则取扩展点接口方法名。

@ExtPoint.Implement
@ExtPoint.Implement(displayName = "",summary = "",expression = "",priority = 99)

这段代码定义了一个名为 Implement 的注解,用于供扩展点接口实现类使用。它具有以下属性:

  • displayName: 展示名称。
  • summary: 描述。
  • expression: 表达式,默认为 "true"
  • priority: 优先级,默认为 99

作用范围:

  • 注解标识在方法上。

调用扩展点

方法一: 使用命名空间和扩展点名称调用 Ext.run(namespace, fun, 参数)方法二: 使用函数式接口调用 Ext.run(函数式接口, 参数)

这两种方法都是调用扩展点的方式,只是在调用语法和参数传递方面略有不同。

  • 方法一
    • 使用命名空间和扩展点名称作为参数,可能更直观地指定要调用的扩展点。
    • 可以在调用时明确指定要调用的扩展点的命名空间和函数名称。
    • 适用于需要明确指定扩展点的场景,例如根据配置文件或其他参数动态确定要调用的扩展点。
  • 方法二
    • 使用函数式接口作为参数,可能更灵活地定义扩展点的调用逻辑。
    • 可以通过函数式接口的实现来决定具体要调用的扩展点。
    • 适用于需要动态确定调用逻辑或根据条件选择调用不同扩展点的场景。

选择方法取决于具体的需求和场景。如果您需要在调用时明确指定要调用的扩展点,或者需要通过命名空间和函数名称来确定扩展点,那么方法一可能更合适。如果您希望通过代码逻辑来动态确定调用的扩展点,或者需要灵活地定义调用逻辑,那么方法二可能更适合。

构建第一个扩展点

自定义扩展点(举例)

在我们日常开发中,随着对业务理解的深入,往往还在一些逻辑中会预留扩展点,以便日后应对不同需求时可以灵活替换某一小块逻辑。 在模型的继承open in new window一文中的PetCatItemQueryService,是独立新增函数只作公共逻辑单元。现在我们给它的实现类增加一个扩展点。在PetCatItemQueryServiceImpl的queryPage方法中原本会先查询PetCatType列表,我们这里假设这个逻辑随着业务发展未来会发生变化,我们可以预先预留【查询萌猫类型扩展点】

Step1 新增扩展点定义PetCatItemQueryCatTypeExtpoint

package pro.shushi.pamirs.demo.api.extpoint;
...//import

@Ext
public interface PetCatItemQueryCatTypeExtpoint {

    @ExtPoint(displayName = "查询萌猫类型扩展点")
    List<PetCatType> queryCatType();

}

Step2 修改PetCatItemQueryServiceImpl(需要显示调用)

我们这里用了第二种调用方式

  1. 根据PetCatItemQueryCatTypeExtpoint的全限定类名为扩展点的命名空间(namespace)
  2. 根据queryCatType的方法名为扩展点的技术名称(name)
  3. 根据namespace+name去找到匹配扩展点实现,并根据规则是否匹配,以及优先级唯一确定一个扩展点实现去执行逻辑
package pro.shushi.pamirs.demo.core.service;

……省略依赖包

@Fun(PetCatItemQueryService.FUN_NAMESPACE)
@Component
public class PetCatItemQueryServiceImpl implements PetCatItemQueryService {

    @Override
    @Function
    public Pagination<PetCatItem> queryPage(Pagination<PetCatItem> page, IWrapper<PetCatItem> queryWrapper) {
        List<PetCatType> typeList = Ext.run(PetCatItemQueryCatTypeExtpoint::queryCatType, new Object[]{});
        if(!CollectionUtils.isEmpty(typeList)) {
            List<Long> typeIds = typeList.stream().map(PetCatType::getId).collect(Collectors.toList());
            queryWrapper = Pops.<PetCatItem>f(queryWrapper).from(PetCatItem.MODEL_MODEL).in(PetCatItem::getTypeId, typeIds).get();
        }
        return new PetCatItem().queryPage(page,queryWrapper);
    }
}

Step3 新增扩展点实现PetCatItemQueryCatTypeExtpointOne

  1. 扩展点命名空间要与扩展点定义一致,用@Ext(PetCatItemQueryCatTypeExtpoint.class)
  2. @ExtPoint.Implement声明这是在@Ext声明的命名空间下,且技术名为queryCatType的扩展点实现
package pro.shushi.pamirs.demo.core.extpoint;
...//import

@Ext(PetCatItemQueryCatTypeExtpoint.class)
public class PetCatItemQueryCatTypeExtpointOne implements PetCatItemQueryCatTypeExtpoint {

    @Override
    @ExtPoint.Implement(displayName = "查询萌猫类型扩展点的默认实现")
    public List<PetCatType> queryCatType() {
        PamirsSession.getMessageHub().info("走的是第一个扩展点");
        List<PetCatType> typeList = new PetCatType().queryList();
        return typeList;
    }
}

Step4 测试扩展点的优先级

package pro.shushi.pamirs.demo.core.extpoint;
..//import

@Ext(PetCatItemQueryCatTypeExtpoint.class)
public class PetCatItemQueryCatTypeExtpointTwo implements PetCatItemQueryCatTypeExtpoint {

    @Override
    @ExtPoint.Implement(priority = 95,displayName = "优先级取胜")
    public List<PetCatType> queryCatType() {
        PamirsSession.getMessageHub().info("走的是第二个扩展点");
        List<PetCatType> typeList = new PetCatType().queryList();
        return typeList;
    }
}

默认扩展点(举例)

当前端通过GraphQL直接发起对Oinone后端Function的请求时,Oinone会默认执行三个内置扩展点,分别是前置扩展点、覆盖扩展点和后置扩展点。 这意味着在Oinone后端执行Function之前、之中和之后,会有三个固定的扩展点来执行一些额外的逻辑或处理。这些扩展点的作用如下:

  1. 前置扩展点
  • 在执行Function之前调用的扩展点。通常用于执行一些预处理逻辑,例如参数校验、权限验证、日志记录等。
  1. 覆盖扩展点
  • 在执行Function时调用的扩展点。它允许覆盖原始的Function逻辑,实现定制化的业务需求。
  1. 后置扩展点
  • 在执行Function之后调用的扩展点。通常用于执行一些后处理逻辑,例如结果处理、清理资源等。

这些内置扩展点的存在使得Oinone系统具有更高的灵活性和可扩展性。开发者可以通过实现特定的扩展点接口来定义自己的扩展逻辑,并注册到对应的扩展点中,从而实现对Function执行过程的定制化处理。 总之,内置扩展点为Oinone系统提供了一种可扩展的机制,使得开发者能够在不修改原始Function代码的情况下,对其行为进行定制化处理,满足不同的业务需求。

默认扩展点与函数的关联关系

  1. 命名空间关联
  • 扩展点与所扩展函数的命名空间一致。这意味着扩展点的命名空间应与要扩展的函数的命名空间相匹配。
  1. 技术名称规则
  • 前置扩展点、重载扩展点和后置扩展点的技术名称的规则是:所扩展函数的函数编码(fun)加上“Before”、“Override”和“After”后缀。
  • 例如,如果要扩展的函数的函数编码为 fun,那么前置扩展点的技术名称可能是 funBefore,重载扩展点的技术名称可能是 funOverride,后置扩展点的技术名称可能是 funAfter
  1. 方法体内调用扩展点
  • 方法体内调用扩展点直接使用接口调用。因此,技术名称可以任意定义,只需要在同一命名空间下唯一即可。

通过这样的规则,扩展点与函数之间建立了关联,并且可以通过命名空间和技术名称来区分和调用不同的扩展点。这样的设计使得系统具有更高的灵活性和可扩展性,能够满足不同业务场景下的需求。

Step1 新增扩展点定义PetShopSayhelloOverrideExtpoint

package pro.shushi.pamirs.demo.api.extpoint;
...//import

@Ext(PetShop.class)
public interface PetShopSayhelloOverrideExtpoint {

    @ExtPoint(displayName = "覆盖PetShop的sayHello执行逻辑")
    public PetShop sayHelloOverride(PetShop shop);

}

Step2 新增扩展点实现PetShopSayhelloOverrideExtpointImpl

package pro.shushi.pamirs.demo.core.extpoint;

...//import

@Ext(PetShop.class)
public class PetShopSayhelloOverrideExtpointImpl implements PetShopSayhelloOverrideExtpoint {

    @ExtPoint.Implement(displayName = "覆盖PetShop的sayHello执行逻辑")
    public PetShop sayHelloOverride(PetShop shop){
        PamirsSession.getMessageHub().info("OverrideExtpoint Hello:"+shop.getShopName());
        return shop;
    }
}

总结

Oinone提供了默认扩展点来为Function提供三种默认的扩展点,包括前置扩展点、覆盖扩展点和后置扩展点。通过自定义扩展点,可以在Function的逻辑内部任意插入扩展点,使得Function作为oinone的逻辑管理单元的可管理性得到了极大的提升。结合拦截器的设计,Oinone能够实现点、线、面一体化管理Function的能力。

重点是:

  1. 默认扩展点: Oinone提供了三种默认的扩展点,分别是前置扩展点、覆盖扩展点和后置扩展点。这些扩展点可以通过编程调用来实现特定的逻辑。
  2. 自定义扩展点: 开发者可以自定义扩展点,并将其插入到Function的逻辑内部,以满足特定的业务需求。这种灵活的扩展性使得Oinone的逻辑管理变得更加高效和可控。
  3. 拦截器设计: 结合拦截器的设计,Oinone可以实现对Function的点、线、面一体化管理。拦截器可以在Function执行前、执行中和执行后插入特定的逻辑,从而实现对Function的定制化处理和管理。

注:默认扩展点,不是由前端发起而是后端编程调用,默认不会生效。