Skip to main content

MatchStrategy--基于SPI的代码分析

· One min read
Apache ShenYu Contributor

Apache Shenyu 网关的各个Plugin(包括Dubbo, gRPC,Spring-cloud等) 中,routing参数均设计为可以接受多个条件的组合。 为了实现这样的目的,遵循其SPI的机制进行将参数及行为抽象为如下三部分,这些SPIshenyu-plugin-base模组中实现

  • ParameterData-参数资料
  • PredictJudge-断言
  • MatchStrategy-匹配策略

相对而言,匹配策略是需要扩展点最少的部分。想象一下,对多个条件的组合判断,最常见的几种规则是:全部都满足、至少满足一个条件、至少满足第一个,或者大部分满足等等。 并且要做到对各种plugin的不同类型的参数,如IP, header, uri等。针对这些需求,如何将MatchStrategy设计得简单易用且容易扩展?

MatchStrategy#

MatchStrategy的实现代码在shenyu-plugin-base模组中,基于Apache ShenyuSPI创建机制, 设计上结合了工厂模式和策略模式,整体MatchStrategy的设计类图如下下:

MatchStrategy-class-diagram

以接口MatchStrategy为基础,设计实现类,并由抽象类AbstractMatchStrategy实现公共方法,由工厂类MatchStrategyFactory提供创建和外部调用功能。

MatchStrategy Interface#

首先来看MatchStrategy SPI接口的定义:

@SPIpublic interface MatchStrategy {
    Boolean match(List<ConditionData> conditionDataList, ServerWebExchange exchange);}

@SPI annotation代表这是一个SPI接口。ServerWebExchangeorg.springframework.web.server.ServerWebExchange ,代表HTTPrequest-response 的交互内容。ConditionData的代码如下,更多说明可以参考PredicateJudge代码分析中的说明,

public class ConditionData {
    private String paramType;    private String operator;
    private String paramName;    private String paramValue;}

AbstractMatchStrategy#

在抽象类AbstractMatchStrategy中,定义MatchStrategy的公共方法, 用buildRealData方法中,用ParameterData工厂类ParameterDataFactory,将多种参数如 Ip, Cookie, Header,uri等资料都以统一的接口方法来呈现。这些参数格式及规则的修改,不会影响到对参数规则匹配MatchStrategy的调用。

public abstract class AbstractMatchStrategy {
    public String buildRealData(final ConditionData condition, final ServerWebExchange exchange) {        return ParameterDataFactory.builderData(condition.getParamType(), condition.getParamName(), exchange);    }}

实现类及Profile#

基于上述接口定义, shenyu-plugin-base 模组提供了两个MatchStrategy实现类

  • AndMatchStrategy-多个条件 AND

  • OrMatchStrategy- 多个条件 OR

    并在SHENYU_DIRECTORY目录下的配置文件中,对实作类做了配置。在系统启动时会由顶层SPIkey-value形式加载并cache起来。

and=org.apache.shenyu.plugin.base.condition.strategy.AndMatchStrategyor=org.apache.shenyu.plugin.base.condition.strategy.OrMatchStrategy

两个实现类AndMatchStrategy 继承AbstractMatchStrategy 并实做了MatchStrategy

AndMatchStrategy- “与”的关系#

由于PredicateJudge封装了条件判断的多样性,ConditionDataParameData封装了多种参数。那么对于多个条件的匹配来说,采用Stream流处理及lamda表达式,非常简洁高效达成了:全部条件都满足,即"AND"的逻辑。

@Joinpublic class AndMatchStrategy extends AbstractMatchStrategy implements MatchStrategy {
    @Override    public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {        return conditionDataList                .stream()                .allMatch(condition -> PredicateJudgeFactory.judge(condition, buildRealData(condition, exchange)));    }}

OrMatchStrategy是同样的实现方式,实现: 至少满足一个条件"OR"的规则,在此不做赘述。

MatchStrategyFactory#

这是MatchStrategy的工厂类,实现了两个方法,一个是newInstance()方法根据策略代码和名称,返回由SPI ExtensionLoader按key来加载对应的MatchStrategy实现类。

    public static MatchStrategy newInstance(final Integer strategy) {        String matchMode = MatchModeEnum.getMatchModeByCode(strategy);        return ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode);    }

MatchModeEnum 中定义了match策略的code和name。 调用时由策略名称,如"and","or",根据启动时SPI加载的key-value资料,找到对应的实现类:

AND(0, "and"),  OR(1, "or");

另一个是match()方法,调用实作类的match方法。

    public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {        return newInstance(strategy).match(conditionDataList, exchange);    }

调用方式#

shenyu-plugin模组的各个plugin的基类AbstractShenyuPlugin 中,定义了两个选择的方法:filterSelectorfilterRule 它们都调用了MatchStrategyFactory 方法,下面是AbstractShenyuPluginfilterSelector方法的代码:

    private Boolean filterSelector(final SelectorData selector, final ServerWebExchange exchange) {        if (selector.getType() == SelectorTypeEnum.CUSTOM_FLOW.getCode()) {            if (CollectionUtils.isEmpty(selector.getConditionList())) {                return false;            }            return MatchStrategyFactory.match(selector.getMatchMode(), selector.getConditionList(), exchange);        }        return true;    }

这段代码中,先检测参数匹配条件SelectorData是否为空,之后调用MatchStrategyFactorymatch方法,工厂方法将调用对应的实作类的match方法。同理,如下是AbstractShenyuPluginfilterRule 方法

    private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) {        return ruleData.getEnabled() && MatchStrategyFactory.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange);    }

也同样是调用MatchStrategyFactorymatch方法,看上去是不是特别的简洁甚至是简单? 在PredicteJudge代码分析文中,对shenyu-plugin如何做参数调用方面做了更进一步的描述。

Summary#

由于应用了Apache shenyuSPI框架,使得整体上具有松耦合、易于扩展的特点。在多个参数规则策略方面,MatchStrategy提供了良好的设计,虽然目前只提供了两个AND 和OR的实现类,但未来可以很轻松地扩展为更多MatchStrategy规则,例如 firstOf:即必须满足第一个条件,或mostOf-满足大部分条件等更多复杂策略,而其他调用部分的代码完全不受影响。

有兴趣的读者可以去阅读Shenyu plugin的源码了解更多内容。