(apache-shenyu 2.4.3版本)apache shenyu前身soul网关,是一款java中spring5新引入的project-reactor的webflux,reactor-netty等为基础实现的高性能网关,现已进入apache孵化器,作者yu199195 (xiaoyu) (github.com)
从当前本章开始,后续开始撸一遍shenyu的plugin逻辑,plugin是shenyu将网关的所有业务以及框架提供能力的抽象,将所有可以使用的能力都抽象为plugin,并提供了热插拔,自由扩展的功能,包括自定义代码扩展的热插拔(通过实现指定接口的jar放到指定路径)
本文来学习apache-shenyu的资源匹配逻辑
我们可以将指定的一个资源进行逻辑上的匹配


我们使用springCloud的后端服务,以及nacos注册中心,以及websocket同步数据,可以看apache-shenyu之启动项目老年人教程 - 简书 (jianshu.com)
可以看上图2,matchType 可以使用and/or代表下面conditions的组合方式

我们可以看到condition可以作用到资源上的很多选项

作用方式也很多,然后后面就是我们可以填入对应值即可,就可以根据这些条件组合起来判断网关是否会转发到后端接口
下面来看看apache-shenyu 如何抽象这些逻辑

先来看看 matchType MatchStrategy接口

@SPI
public interface MatchStrategy {
// 这是一个 spi 接口
Boolean match(List<ConditionData> conditionDataList, ServerWebExchange exchange);
}
来看看策略工厂
public final class MatchStrategyFactory {
private MatchStrategyFactory() {
}
// 通过当前工厂,参数为策略选择参数,通过SPI选择一个子类
public static MatchStrategy newInstance(final Integer strategy) {
String matchMode = MatchModeEnum.getMatchModeByCode(strategy);
return ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode);
}
// 为了使用方便,直接使用静态方法暴露 machType的业务方法,那么只需要传入策略参数内部选择子类执行
public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
return newInstance(strategy).match(conditionDataList, exchange);
}
}
就看一个 or 即可
@Join
public class OrMatchStrategy extends AbstractMatchStrategy implements MatchStrategy {
@Override
public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
return conditionDataList
.stream()
// or其实就是 anyMatch all就是 allMatch 哈哈哈
// 这里有两个 和他其逻辑组合的玩意,judge方法其实就是匹配方式,buildRealData就是要匹配的选项
.anyMatch(condition -> PredicateJudgeFactory.judge(condition, buildRealData(condition, exchange)));
}
}
再来看看匹配选项 ParameterData 接口

@SPI
public interface ParameterData {
// 还是一个 spi接口 ,默认返回空字符串
default String builder(final String paramName, final ServerWebExchange exchange) {
return "";
}
}
public final class ParameterDataFactory {
private ParameterDataFactory() {
}
// 还是通过策略工厂选择子类
public static ParameterData newInstance(final String paramType) {
return ExtensionLoader.getExtensionLoader(ParameterData.class).getJoin(paramType);
}
// 还是通过静态方法直接暴露业务方法
public static String builderData(final String paramType, final String paramName, final ServerWebExchange exchange) {
return newInstance(paramType).builder(paramName, exchange);
}
}
下面我分别看看 子类实现
@Join
public class CookieParameterData implements ParameterData {
@Override
public String builder(final String paramName, final ServerWebExchange exchange) {
// 获取cookie的某一个值 然后后续的 matchType 和 匹配方式由MatchStrategy和PredicateJudge来做
List<HttpCookie> cookies = exchange.getRequest().getCookies().get(paramName);
if (CollectionUtils.isEmpty(cookies)) {
return "";
}
return cookies.get(0).getValue();
}
}
// --------- domain---------------
@Join
public class DomainParameterData implements ParameterData {
@Override
public String builder(final String paramName, final ServerWebExchange exchange) {
return exchange.getRequest().getURI().getHost();
}
}
//----------------- header -----------
@Join
public class HeaderParameterData implements ParameterData {
@Override
public String builder(final String paramName, final ServerWebExchange exchange) {
List<String> headers = exchange.getRequest().getHeaders().get(paramName);
if (CollectionUtils.isEmpty(headers)) {
return "";
}
return headers.get(0);
}
}
//------------ host ----------
@Join
public class HostParameterData implements ParameterData {
// 不同于domain 一个是当前host一个是 远程调用的 host,具体不去了解了
@Override
public String builder(final String paramName, final ServerWebExchange exchange) {
return HostAddressUtils.acquireHost(exchange);
}
}
// 还有 ip,param, uri,getMethodValue(post 还是 get等等),
// --------- post parameter--------
@Join
public class PostParameterData implements ParameterData {
// 这个比较特殊,是获取 shenyu封装的参数
@Override
public String builder(final String paramName, final ServerWebExchange exchange) {
ShenyuContext shenyuContext = exchange.getAttribute(Constants.CONTEXT);
return (String) ReflectUtils.getFieldValue(shenyuContext, paramName);
}
}
然后我们再来看看 PredicateJudge 匹配方式
@SPI
@FunctionalInterface
public interface PredicateJudge {
// 是一个spi接口,并且是函数式接口,可以不用写一个类噢
Boolean judge(ConditionData conditionData, String realData);
}
工厂类
public final class PredicateJudgeFactory {
private PredicateJudgeFactory() {
}
public static PredicateJudge newInstance(final String operator) {
return ExtensionLoader.getExtensionLoader(PredicateJudge.class).getJoin(processSpecialOperator(operator));
}
public static Boolean judge(final ConditionData conditionData, final String realData) {
if (Objects.isNull(conditionData) || StringUtils.isBlank(realData)) {
return false;
}
return newInstance(conditionData.getOperator()).judge(conditionData, realData);
}
// 虽然前端使用 = 表示相等,后端需要用 equals
private static String processSpecialOperator(final String operator) {
return "=".equals(operator) ? "equals" : operator;
}
}
实现类们不一一列举了
@Join
public class ContainsPredicateJudge implements PredicateJudge {
@Override
public Boolean judge(final ConditionData conditionData, final String realData) {
return realData.contains(conditionData.getParamValue().trim());
}
}
通过上述代码可以看出 shenyu通过三个spi接口 + 工厂将这三个业务参数的使用抽象出来,实际还通过spi指定了策略工厂key对应的实现类

因为我们@ShenyuSpringCloudClient 一个注解是一个客户端维度,但是注到类上,我们可以通过不同的rule 1:1的映射到对应接口上,如果注到方法上,我们多个rule的规则就是可以有多个uri可以映射到这一个方法上,使用上是非常灵活的,就看我们如何使用
主要是区分 注解到类和方法上的区别即可。
我们来看看调用方
// 获取 一个@ShenyuSpringCloudClient 的所有rule
List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
if (CollectionUtils.isEmpty(rules)) {
return handleRuleIfNull(pluginName, exchange, chain);
}
RuleData rule;
if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
//get last
rule = rules.get(rules.size() - 1);
} else {
// 一般使用这个方法
rule = matchRule(exchange, rules);
}
private RuleData matchRule(final ServerWebExchange exchange, final Collection<RuleData> rules) {
// 可以看到一个@ShenyuSpringCloudClient 的所有规则,只会选择findFirst() 第一个 true的返回
return rules.stream().filter(rule -> filterRule(rule, exchange)).findFirst().orElse(null);
}
private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) {
// 规则开关 && 上述我们的 MatchStrategy PredicateJudge ParameterData 组合规则返回值
return ruleData.getEnabled() && MatchStrategyFactory.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange);
}
总结
- 多个维度的client注册及使用,整类以及单个方法
- rule动态开关
- 多个rule返回第一个true
- 抽象出MatchStrategy PredicateJudge ParameterData 以及SPI + 策略工厂 组合使用简化代码以及提高扩展性
网友评论