Apache-shenYu源码阅读08- 选择器\规则的匹配

1,078 阅读5分钟

回顾

在学习Soul如何实现的Http请求调用一章中,已经明确知道在AbstractSoulPlugin的模板方法已经实现了对选择器及规则的匹配,具体方法如下所示

//过滤选择器
    private Boolean filterSelector(final SelectorData selector, final ServerWebExchange exchange) {
        if (selector.getType() == SelectorTypeEnum.CUSTOM_FLOW.getCode()) {
            if (CollectionUtils.isEmpty(selector.getConditionList())) {
                return false;
            }
            return MatchStrategyUtils.match(selector.getMatchMode(), selector.getConditionList(), exchange);
        }
        return true;
    }
//过滤规则
    private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) {
        return ruleData.getEnabled() && MatchStrategyUtils.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange);
    }

在上述代码中明显可以看到调用了MatchStrategyUtils的match方法,在此处也得出了结果即规则匹配和选择器匹配代码底层实现逻辑一致。

第一阶段源码梳理

从调用链开始一步步梳理

MatchStrategyUtils 匹配策略工具类

该类下只有一个方法match方法

public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        //获取匹配的模式
        String matchMode = MatchModeEnum.getMatchModeByCode(strategy);
        //获取该匹配模式下对应的实现类
        MatchStrategy matchStrategy = ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode);
        //调用匹配真实方法判断是否匹配
        return matchStrategy.match(conditionDataList, exchange);
    }

在上述代码中涉及到三个类分别是MatchModeEnum、MatchStrategy、ExtensionLoader

MatchModeEnum 匹配模式枚举

/**
     * And match mode enum.
     */
    AND(0, "and"),

    /**
     * Or match mode enum.
     */
    OR(1, "or");

这两个枚举值很熟悉,对应的页面应用(规则也是再类似的此处)

ps: 注意这里的匹配方式指代的是编辑框内的条件匹配方式,假设我一个选择器\规则有10个条件且匹配方式是AND方式,那代表必须全部满足,如果匹配方式是OR方式,代表满足其中一个即可。

MatchStrategy 匹配策略接口

注意注意SPI....SPI

@SPI
public interface MatchStrategy {

    Boolean match(List<ConditionData> conditionDataList, ServerWebExchange exchange);
}

ps: 针对于match方法传参的conditionDataList,针对于页面的数据在

其对应的底层实现有两个 这也恰好印证了MatchModeEnum

ExtensionLoader 扩展加载

在该类的注释中你能明显看到(github.com/apache/dubb…

那该类的主要作用是什么呢?

此处本人也不太熟悉,但是根据调用方法及其返回可以的得知加载了某些类,根据某些条件获得一个实例(非本次分析重点)

ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode);

这里是分隔线 阶段总结

在上述梳理中我们大体知道了MatchStrategyUtils.match方法做了哪些事?

  • 获取匹配模式
  • 加载对应的匹配模式实例
  • 核心方法 判断是否匹配 matchStrategy.match

但是有一点没有梳理到位那就是ConditionData,接下来先梳理一下该类的参数

    //参数类型 有对应枚举 ParamTypeEnum
    private String paramType;

    //匹配方式 有对应枚举 OperatorEnum
    private String operator;

    //参数名称 除post、query、header类型下有实际含义 
    private String paramName;

    //参数值 
    private String paramValue;

针对页面如下 针对表结构selector_condition、rule_condition

第二阶段源码梳理

接下来了解一下AndMatchStrategy、OrMatchStrategy匹配模式下的具体实现。

AND 模式实现代码
public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        return conditionDataList
                .stream()
                .allMatch(condition -> OperatorJudgeFactory.judge(condition, buildRealData(condition, exchange)));
    }
OR 模式实现代码
public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
        return conditionDataList
                .stream()
                .anyMatch(condition -> OperatorJudgeFactory.judge(condition, buildRealData(condition, exchange)));
    }

通过对比发现两处只有一个不一样的地方就是stream().allMatch与stream.anyMatch,allMatch代表函数全部成功才算成功,anyMatch代表函数有一个成功代表成功,这也映射到了AND模式匹配和OR模式匹配。

AndMatchStrategy、OrMatchStrategy类中发现在具体的判断规则有OperatorJudgeFactory.judge方法,还使用了AbstractMatchStrategy的buildRealData函数 。

先探索buildRealData方法具体做了哪些事情。

String buildRealData(final ConditionData condition, final ServerWebExchange exchange) {
        String realData = "";
        ParamTypeEnum paramTypeEnum = ParamTypeEnum.getParamTypeEnumByName(condition.getParamType());
        switch (paramTypeEnum) {
            case HEADER:
                final HttpHeaders headers = exchange.getRequest().getHeaders();
                final List<String> list = headers.get(condition.getParamName());
                if (CollectionUtils.isEmpty(list)) {
                    return realData;
                }
                realData = Objects.requireNonNull(headers.get(condition.getParamName())).stream().findFirst().orElse("");
                break;
            case URI:
                realData = exchange.getRequest().getURI().getPath();
                break;
            case QUERY:
                final MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
                realData = queryParams.getFirst(condition.getParamName());
                break;
            case HOST:
                realData = HostAddressUtils.acquireHost(exchange);
                break;
            case IP:
                realData = HostAddressUtils.acquireIp(exchange);
                break;
            case POST:
                final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
                realData = (String) ReflectUtils.getFieldValue(soulContext, condition.getParamName());
                break;
            default:
                break;
        }
        return realData;
    }

可以从代码看出 就是根据对应的paramType从exchange中获取对应的属性值,此处不深究。

第三阶段源码梳理

接下来看一下OperatorJudgeFactory的judge是如何进行的判断逻辑

private static final Map<String, OperatorJudge> OPERATOR_JUDGE_MAP = Maps.newHashMapWithExpectedSize(4);

    static {
        OPERATOR_JUDGE_MAP.put(OperatorEnum.EQ.getAlias(), new EqOperatorJudge());
        OPERATOR_JUDGE_MAP.put(OperatorEnum.MATCH.getAlias(), new MatchOperatorJudge());
        OPERATOR_JUDGE_MAP.put(OperatorEnum.LIKE.getAlias(), new LikeOperatorJudge());
        OPERATOR_JUDGE_MAP.put(OperatorEnum.REGEX.getAlias(), new RegExOperatorJudge());
    }

    public static Boolean judge(final ConditionData conditionData, final String realData) {
        if (Objects.isNull(conditionData) || StringUtils.isBlank(realData)) {
            return false;
        }
        return OPERATOR_JUDGE_MAP.get(conditionData.getOperator()).judge(conditionData, realData);
    }

这里看到了OperatorEnum.XXX,和页面展示的匹配条件一致。

从整体结构可以看出是非常典型的简单工厂+策略,具体的实现交给了OperatorJudge接口的子类。 大体扫了一眼

  • EQ 就是进行equals匹配

  • LIKE 进行 contains匹配

  • REGEX 进行正则规则匹配

  • MATCH 针对于URI的类型是进行了正则匹配,别的类型进行的contains匹配。

总结

到此为止,基于选择器\规则的匹配逻辑已经梳理完成。

  • MatchStrategyUtils选择对应的模式实现
  • 各个模式实现时根据paramType获取对应的此次调用的属性值
  • 调用OperatorJudgeFactory.judge方法
  • 根据匹配的表达式operator选择对应的OperatorJudge的接口实现
  • 获取匹配结果

在上述文章中有些细节并没有梳理到位,就需要各位进行深挖了,在这里整理了一个同步数据所涉及的类图。

在匹配模式AND和OR那你可能有个疑问那就是,匹配模式不能是我满足这三个条件与的关系,并且满足另外两个条件或的关系吗?

答案是当然可以,明天将基于MatchStrategy--SPI扩展上述问题的答案。