MatchStrategy -- analyze the design based on SPI
In most of the plugins
( such as Dubbo
, gRPC
,Spring-cloud
, etc) of Apache Shenyu
, the routing
parameters are designed to support the combination of multiple conditions. In order to realize such requirements, the parameters and behaviors are abstracted to three parts according to its SPI
mechanism, and implemented in shenyu-plugin-base module.
ParameterData
-parametersPredictJudge
-predicateMatchStrategy
-matching strategy
Relatively speaking, the MatchStrategy
is the part that needs the least extension points. For the combined judgement of multiple conditions, the common selection rules are: All conditions are matched, at least one is matched, at least the first is met, or most of conditions satisfied. As we will need to handle various types of parameters, for example: IP
, header
, uri
, etc.
How to make the MatchStrategy
to be simple to use and extensible?
#
MatchStrategyThe implementation of MatchStrategy
is in shenyu-plugin-base module. It is based on the SPI creation mechanism, and has used factory pattern and strategy design pattern. The class diagram of MatchStrategy
is
showed as follows.
Based on the interface MatchStrategy
we design the implementation classes, and the abstract class AbstractMatchStrategy
supplies common method, while the factory class MatchStrategyFactory
provides creation functions.
#
MatchStrategy InterfaceFirst, let's look at the MatchStrategy
SPI
interface
@SPIpublic interface MatchStrategy {
Boolean match(List<ConditionData> conditionDataList, ServerWebExchange exchange);}
The annotation @SPI
means that this is an SPI
interface. Where ServerWebExchange
is org.springframework.web.server.ServerWebExchange
, represents the request-response interactive content of HTTP. Following is the code of ConditionData
, the more detail about this class can refer to code analysis of PredicteJudge
public class ConditionData {
private String paramType; private String operator;
private String paramName; private String paramValue;}
#
AbstractMatchStrategySecond, let's look at the abstract class AbstractMatchStrategy
,it has defined a buildRealData
method,In this method it wraps various parameters to a unified interface through the functionality of ParameterDataFactory
, which is the factory class of ParameterData
. It supports a variety of types of parameters , such as Ip
, Cookie
, Header
,uri
, etc. Modifications of such parameters will not impact the calling of matching rules of MatchStrategy
.
public abstract class AbstractMatchStrategy {
public String buildRealData(final ConditionData condition, final ServerWebExchange exchange) { return ParameterDataFactory.builderData(condition.getParamType(), condition.getParamName(), exchange); }}
#
Implementation class and profileNow, let's look at the two implementation class based on the above interface in shenyu-plugin-base module , that is:
AndMatchStrategy
-AND
-All conditions are matchedOrMatchStrategy
-OR
-at least one is matchThe properties file containing the SPI implementation is shown as follows, which located at the
SHENYU_DIRECTORY
directory. When starting up, the top-level SPI classes will read the key-value and load the classes and cache them.
and=org.apache.shenyu.plugin.base.condition.strategy.AndMatchStrategyor=org.apache.shenyu.plugin.base.condition.strategy.OrMatchStrategy
These two implementation classes inherit AbstractMatchStrategy
class and implement MatchStrategy
interface.
#
AndMatchStrategy- “AND” relationSince the PredicateJudge
interface can encapsulate different variety of Predicates , for example EqualsPredicateJudge, EndsWithPredicateJudge and so on, the ConditionData
and ParamData
passed to it can present with variety of parameters, for treating of multiple conditions. So usingstream
and lambda
expression, it can be very simple and efficient to process "AND" logic (all conditions must be matched).
@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))); }}
The OrMatchStrategy
similarly implements the "OR" logic- at least one is match.
#
MatchStrategyFactoryThis is the factory class of MatchStrategy
,there are two methods, one is newInstance()
, which will return the MatchStrategy
implementation class instance cached by the SPI
ExtensionLoader
indexed by the key-value.
public static MatchStrategy newInstance(final Integer strategy) { String matchMode = MatchModeEnum.getMatchModeByCode(strategy); return ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode); }
the matchMode
will be the name of strategy, the value will be "and" or "or". The MatchModeEnum
defines the code and name of match strategy as follows.
AND(0, "and"), OR(1, "or");
Another method is match()
method, which will invoke the match()
method of implementation class.
public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) { return newInstance(strategy).match(conditionDataList, exchange); }
#
How it worksAbstractShenyuPlugin
is the base class of plugins
in shenyu-plugin
module. In this class two selection method are defined: filterSelector()
and filterRule()
, Both of them call the match()
method of MatchStrategyFactory
. The code of filterSelector()
is shown as follows.
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; }
In filterSelector
() method, after validation of the SelectorData
, calls the match
method of MatchStrategyFactory
, and then this factory class will invokes the match
method of corresponding implementation class.
private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) { return ruleData.getEnabled() && MatchStrategyFactory.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange); }
In filterRule()
it is also calls the match()
method of MatchStrategyFactory
. Does it look particularly concise or even simple? In the code analysis of PredicteJudge
, you can see more detail about parameter processing in shenyu-plugin
.
#
SummaryDue to the use of SPI
mechanism of Apache Shenyu
, the parameter selection module has the characteristic of loose coupling and extensibility. In terms of the combination of multiple conditions, MatchStrategy
provides a good design. Although currently only two implementation classes are present, it can be easily used to develop more complex MatchStrategy
rules in the future, such as "firstOf
"-first condition must matched, or "mostOf
"- most of the conditions must be matched, etc.
Interested readers can read the source code of 'shenyu-plugin'
to learn more.