MatchStrategy--基于SPI的代码分析
Apache Shenyu
网关的各个Plugin
(包括Dubbo
, gRPC
,Spring-cloud
等) 中,routing
参数均设计为可以接受多个条件的组合。 为了实现这样的目的,遵循其SPI
的机制进行将参数及行为抽象为如下三部分,这些SPI
在shenyu-plugin-base模组中实现
ParameterData
-参数资料PredictJudge
-断言MatchStrategy
-匹配策略
相对而言,匹配策略是需要扩展点最少的部分。想象一下,对多个条件的组合判断,最常见的几种规则是:全部都满足、至少满足一个条件、至少满足第一个,或者大部分满足等等。 并且要做到对各种plugin
的不同类型的参数,如IP
, header
, uri
等。针对这些需求,如何将MatchStrategy
设计得简单易用且容易扩展?
#
MatchStrategyMatchStrategy
的实现代码在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
的源码了解更多内容。