(apache-shenyu 2.4.3版本)apache shenyu前身soul網(wǎng)關(guān)畔裕,是一款java中spring5新引入的project-reactor的webflux铣鹏,reactor-netty等為基礎(chǔ)實(shí)現(xiàn)的高性能網(wǎng)關(guān)挣跋,現(xiàn)已進(jìn)入apache孵化器汗唱,作者yu199195 (xiaoyu) (github.com)
從當(dāng)前本章開始垢油,后續(xù)開始擼一遍shenyu的plugin邏輯臊恋,plugin是shenyu將網(wǎng)關(guān)的所有業(yè)務(wù)以及框架提供能力的抽象怖侦,將所有可以使用的能力都抽象為plugin,并提供了熱插拔距帅,自由擴(kuò)展的功能右锨,包括自定義代碼擴(kuò)展的熱插拔(通過實(shí)現(xiàn)指定接口的jar放到指定路徑)
本文來學(xué)習(xí)apache-shenyu的資源匹配邏輯
我們可以將指定的一個(gè)資源進(jìn)行邏輯上的匹配
我們使用springCloud的后端服務(wù),以及nacos注冊(cè)中心锥债,以及websocket同步數(shù)據(jù)陡蝇,可以看apache-shenyu之啟動(dòng)項(xiàng)目老年人教程 - 簡(jiǎn)書 (jianshu.com)
可以看上圖2,matchType 可以使用and/or代表下面conditions的組合方式
我們可以看到condition可以作用到資源上的很多選項(xiàng)
作用方式也很多哮肚,然后后面就是我們可以填入對(duì)應(yīng)值即可登夫,就可以根據(jù)這些條件組合起來判斷網(wǎng)關(guān)是否會(huì)轉(zhuǎn)發(fā)到后端接口
下面來看看apache-shenyu 如何抽象這些邏輯
先來看看 matchType MatchStrategy接口
@SPI
public interface MatchStrategy {
// 這是一個(gè) spi 接口
Boolean match(List<ConditionData> conditionDataList, ServerWebExchange exchange);
}
來看看策略工廠
public final class MatchStrategyFactory {
private MatchStrategyFactory() {
}
// 通過當(dāng)前工廠,參數(shù)為策略選擇參數(shù)允趟,通過SPI選擇一個(gè)子類
public static MatchStrategy newInstance(final Integer strategy) {
String matchMode = MatchModeEnum.getMatchModeByCode(strategy);
return ExtensionLoader.getExtensionLoader(MatchStrategy.class).getJoin(matchMode);
}
// 為了使用方便恼策,直接使用靜態(tài)方法暴露 machType的業(yè)務(wù)方法,那么只需要傳入策略參數(shù)內(nèi)部選擇子類執(zhí)行
public static boolean match(final Integer strategy, final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
return newInstance(strategy).match(conditionDataList, exchange);
}
}
就看一個(gè) or 即可
@Join
public class OrMatchStrategy extends AbstractMatchStrategy implements MatchStrategy {
@Override
public Boolean match(final List<ConditionData> conditionDataList, final ServerWebExchange exchange) {
return conditionDataList
.stream()
// or其實(shí)就是 anyMatch all就是 allMatch 哈哈哈
// 這里有兩個(gè) 和他其邏輯組合的玩意潮剪,judge方法其實(shí)就是匹配方式涣楷,buildRealData就是要匹配的選項(xiàng)
.anyMatch(condition -> PredicateJudgeFactory.judge(condition, buildRealData(condition, exchange)));
}
}
再來看看匹配選項(xiàng) ParameterData 接口
@SPI
public interface ParameterData {
// 還是一個(gè) spi接口 ,默認(rèn)返回空字符串
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);
}
// 還是通過靜態(tài)方法直接暴露業(yè)務(wù)方法
public static String builderData(final String paramType, final String paramName, final ServerWebExchange exchange) {
return newInstance(paramType).builder(paramName, exchange);
}
}
下面我分別看看 子類實(shí)現(xiàn)
@Join
public class CookieParameterData implements ParameterData {
@Override
public String builder(final String paramName, final ServerWebExchange exchange) {
// 獲取cookie的某一個(gè)值 然后后續(xù)的 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 一個(gè)是當(dāng)前host一個(gè)是 遠(yuǎn)程調(diào)用的 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 {
// 這個(gè)比較特殊碳褒,是獲取 shenyu封裝的參數(shù)
@Override
public String builder(final String paramName, final ServerWebExchange exchange) {
ShenyuContext shenyuContext = exchange.getAttribute(Constants.CONTEXT);
return (String) ReflectUtils.getFieldValue(shenyuContext, paramName);
}
}
然后我們?cè)賮砜纯?PredicateJudge 匹配方式
@SPI
@FunctionalInterface
public interface PredicateJudge {
// 是一個(gè)spi接口,并且是函數(shù)式接口看疗,可以不用寫一個(gè)類噢
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;
}
}
實(shí)現(xiàn)類們不一一列舉了
@Join
public class ContainsPredicateJudge implements PredicateJudge {
@Override
public Boolean judge(final ConditionData conditionData, final String realData) {
return realData.contains(conditionData.getParamValue().trim());
}
}
通過上述代碼可以看出 shenyu通過三個(gè)spi接口 + 工廠將這三個(gè)業(yè)務(wù)參數(shù)的使用抽象出來,實(shí)際還通過spi指定了策略工廠key對(duì)應(yīng)的實(shí)現(xiàn)類
因?yàn)槲覀傽ShenyuSpringCloudClient 一個(gè)注解是一個(gè)客戶端維度两芳,但是注到類上摔寨,我們可以通過不同的rule 1:1的映射到對(duì)應(yīng)接口上,如果注到方法上怖辆,我們多個(gè)rule的規(guī)則就是可以有多個(gè)uri可以映射到這一個(gè)方法上是复,使用上是非常靈活的,就看我們?nèi)绾问褂?/p>
主要是區(qū)分 注解到類和方法上的區(qū)別即可竖螃。
我們來看看調(diào)用方
// 獲取 一個(gè)@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 {
// 一般使用這個(gè)方法
rule = matchRule(exchange, rules);
}
private RuleData matchRule(final ServerWebExchange exchange, final Collection<RuleData> rules) {
// 可以看到一個(gè)@ShenyuSpringCloudClient 的所有規(guī)則淑廊,只會(huì)選擇findFirst() 第一個(gè) true的返回
return rules.stream().filter(rule -> filterRule(rule, exchange)).findFirst().orElse(null);
}
private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) {
// 規(guī)則開關(guān) && 上述我們的 MatchStrategy PredicateJudge ParameterData 組合規(guī)則返回值
return ruleData.getEnabled() && MatchStrategyFactory.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange);
}
總結(jié)
- 多個(gè)維度的client注冊(cè)及使用,整類以及單個(gè)方法
- rule動(dòng)態(tài)開關(guān)
- 多個(gè)rule返回第一個(gè)true
- 抽象出MatchStrategy PredicateJudge ParameterData 以及SPI + 策略工廠 組合使用簡(jiǎn)化代碼以及提高擴(kuò)展性