MatchStrategy--基于SPI的代码分析
Apache Shenyu 网关的各个Plugin(包括Dubbo, gRPC,Spring-cloud等) 中,routing参数均设计为可以接受多个条件的组合。 为了实现这样的目的,遵循其SPI的机制进行将参数及行为抽象为如下三部分,这些SPI 在shenyu-plugin-base模组中实现
ParameterData-参数资料PredictJudge-断言MatchStrategy-匹配策略
相对而言,匹配策略是需要扩展点最少的部分。想象一下,对多个条件的组合判断,最常见的几种规则是:全部都满足、至少满足一个条件、至少满足第一个,或者大部分满足等等。 并且要做到对各种plugin的不同类型的参数,如IP, header, uri等。针对这些需求,如何将MatchStrategy设计得简单易用且容易扩展?
MatchStrategy#
MatchStrategy的实现代码在shenyu-plugin-base模组中,基于Apache Shenyu的SPI创建机制, 设计上结合了工厂模式和策略模式,整体MatchStrategy的设计类图如下下:
以接口MatchStrategy为基础,设计实现类,并由抽象类AbstractMatchStrategy实现公共方法,由工厂类MatchStrategyFactory提供创建和外部调用功能。
MatchStrategy Interface#
首先来看MatchStrategy SPI接口的定义:
@SPIpublic interface MatchStrategy {
Boolean match(List<ConditionData> conditionDataList, ServerWebExchange exchange);}@SPI annotation代表这是一个SPI接口。ServerWebExchange 是 org.springframework.web.server.ServerWebExchange ,代表HTTP的 request-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-多个条件 ANDOrMatchStrategy- 多个条件 OR并在
SHENYU_DIRECTORY目录下的配置文件中,对实作类做了配置。在系统启动时会由顶层SPI以key-value形式加载并cache起来。
and=org.apache.shenyu.plugin.base.condition.strategy.AndMatchStrategyor=org.apache.shenyu.plugin.base.condition.strategy.OrMatchStrategy 两个实现类AndMatchStrategy 继承AbstractMatchStrategy 并实做了MatchStrategy。
AndMatchStrategy- “与”的关系#
由于PredicateJudge封装了条件判断的多样性,ConditionData和ParameData封装了多种参数。那么对于多个条件的匹配来说,采用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 中,定义了两个选择的方法:filterSelector 和filterRule 它们都调用了MatchStrategyFactory 方法,下面是AbstractShenyuPlugin中filterSelector方法的代码:
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是否为空,之后调用MatchStrategyFactory的match方法,工厂方法将调用对应的实作类的match方法。同理,如下是AbstractShenyuPlugin中 filterRule 方法
private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) { return ruleData.getEnabled() && MatchStrategyFactory.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange); }也同样是调用MatchStrategyFactory的match方法,看上去是不是特别的简洁甚至是简单? 在PredicteJudge的代码分析文中,对shenyu-plugin如何做参数调用方面做了更进一步的描述。
Summary#
由于应用了Apache shenyu的SPI框架,使得整体上具有松耦合、易于扩展的特点。在多个参数规则策略方面,MatchStrategy提供了良好的设计,虽然目前只提供了两个AND 和OR的实现类,但未来可以很轻松地扩展为更多MatchStrategy规则,例如 firstOf:即必须满足第一个条件,或mostOf-满足大部分条件等更多复杂策略,而其他调用部分的代码完全不受影响。
有兴趣的读者可以去阅读Shenyu plugin的源码了解更多内容。