目录
3.SoulWebHandler 中的 plugins 注入
1.SoulPlugin 接口
插件的接口 SoulPlugin 有很多实现,我们先关注以下这些,从名字上能看出来,这些就是 soul-admin 管理页面上插件列表里的代码实现。
2.AbstractSoulPlugin
下面,以 DividePlugin 为例,详细看看这优雅的设计。
类继承关系如图:
很明显,execute 方法是模板方法,看下其具体实现如下:
// AbstractSoulPlugin.java
/**
* Process the Web request and (optionally) delegate to the next
* {@code SoulPlugin} through the given {@link SoulPluginChain}.
*
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next plugin
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
String pluginName = named();
// 根据插件名称,从缓存中获取插件信息
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
...
// 如果缓存中没有插件信息,或者插件没有开启,执行插件链的下一个节点
return chain.execute(exchange);
}
我们先看下如果没有插件信息或者插件没有开启的逻辑,会执行 SoulPluginChain#execute() 方法。
SoulPluginChain 插件链接口,有一个实现类是 SoulWebHandler 的私有静态内部类 DefaultSoulPluginChain。
SoulWebHandler 实现了 WebHandler 接口,WebHandler 接口是 WebFlux 在处理一个 web 请求时的核心接口。
有一个私有属性 plugins,封装了 SoulPlugin 列表,并且使用了构造器注入:
// SoulWebHandler.java
private final List<SoulPlugin> plugins;
public SoulWebHandler(final List<SoulPlugin> plugins) {
this.plugins = plugins;
...
}
并且在 handle 方法中实例化一个 DefaultSoulPluginChain 对象,作为构造器参数传进去。
每次调用网关的请求,都会调用一次这个 handle 方法进行处理。
// SoulWebHandler.java
/**
* Handle the web server exchange.
*
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
@Override
public Mono<Void> handle(@NonNull final ServerWebExchange exchange) {
return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler);
}
然后调用了 DefaultSoulPluginChain 的 execute 方法:
// SoulWebHandler.java
private static class DefaultSoulPluginChain implements SoulPluginChain {
private int index;
private final List<SoulPlugin> plugins;
/**
* Instantiates a new Default soul plugin chain.
*
* @param plugins the plugins
*/
DefaultSoulPluginChain(final List<SoulPlugin> plugins) {
this.plugins = plugins;
}
/**
* Delegate to the next {@code WebFilter} in the chain.
*
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
@Override
public Mono<Void> execute(final ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < plugins.size()) {
// 从插件列表中按顺序获取插件
SoulPlugin plugin = plugins.get(this.index++);
// 判断这个插件是否跳过
Boolean skip = plugin.skip(exchange);
if (skip) {
// 如果跳过,调用自身方法,也就是委托给插件链中下一个插件执行
return this.execute(exchange);
}
// 如果没有跳过,那么执行具体插件的 execute 方法,就跳回前面讲过的 AbstractSoulPlugin 的 execute 方法了
return plugin.execute(exchange, this);
}
return Mono.empty();
});
}
}
分析到这里,再整体看下 AbstractSoulPlugin 的 execute 方法:
// AbstractSoulPlugin.java
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
// 获取插件名称
String pluginName = named();
// 根据插件名称获取插件数据
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
if (pluginData != null && pluginData.getEnabled()) { // 插件存在并且开启了才会进入
// 根据插件名称获取选择器列表数据
final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
if (CollectionUtils.isEmpty(selectors)) { // 处理选择器列表为空时逻辑
return handleSelectorIsNull(pluginName, exchange, chain);
}
// 匹配选择器
final SelectorData selectorData = matchSelector(exchange, selectors);
if (Objects.isNull(selectorData)) { // 处理匹配后选择器为空时逻辑
return handleSelectorIsNull(pluginName, exchange, chain);
}
// 根据设置,记录日志
selectorLog(selectorData, pluginName);
// 根据选择器 ID 获取 规则数据列表
final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
if (CollectionUtils.isEmpty(rules)) { 处理规则列表为空时逻辑
return handleRuleIsNull(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);
}
if (Objects.isNull(rule)) { // 处理规则为空时逻辑
return handleRuleIsNull(pluginName, exchange, chain);
}
// 根据设置,记录日志
ruleLog(rule, pluginName);
// 这里会调用具体子类的覆写方法
return doExecute(exchange, chain, selectorData, rule);
}
// 如果缓存中没有插件信息,或者插件没有开启,执行插件链的下一个节点
return chain.execute(exchange);
}
// 选择器为空时默认逻辑:执行插件链下一个插件逻辑。如果方法被子类覆写,需要执行子类方法
protected Mono<Void> handleSelectorIsNull(final String pluginName, final ServerWebExchange exchange, final SoulPluginChain chain) {
return chain.execute(exchange);
}
// 抽象方法,需要子类实现
protected abstract Mono<Void> doExecute(ServerWebExchange exchange, SoulPluginChain chain, SelectorData selector, RuleData rule);
1个网关请求发送过来后,execute 方法会从选择器和规则进行匹配,最后会调用每个插件子类的 doExecute 方法。
3.SoulWebHandler 中的 plugins 注入
到这里,可能会有同学犯嘀咕,SoulWebHandler 中的 plugins 是什么时候注入的呢?
在 SoulConfiguration 类中有提供一个 webHandler 的 Bean,使用了 Spring 4.3 提供的 ObjectProvider,把所有的 SoulPlugin 实现类的 Bean 收集汇总到方法入参 plugins 里。
// SoulConfiguration.java
@Configuration
@ComponentScan("org.dromara.soul")
@Import(value = {ErrorHandlerConfiguration.class, SoulExtConfiguration.class, SpringExtConfiguration.class})
@Slf4j
public class SoulConfiguration {
/**
* Init SoulWebHandler.
*
* @param plugins this plugins is All impl SoulPlugin.
* @return {@linkplain SoulWebHandler}
*/
@Bean("webHandler")
public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
// 按照 getOrder 升序排列
final List<SoulPlugin> soulPlugins = pluginList.stream()
.sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
// 返回 SoulWebHandler 示例对象
return new SoulWebHandler(soulPlugins);
}
...
}
这里排序的依据是 SoulPlugin#getOrder() 方法返回的接口,以 DividePlugin 为例,代码如下,对应着 PluginEnum 枚举类中的 code 字段值,也就是在初始化时,插件列表的顺序已经定下来了。
// DividePlugin.java
@Override
public int getOrder() {
return PluginEnum.DIVIDE.getCode();
}
4.DividePlugin
我们看下 DividePlugin 的 doExecute 方法:
// DividePlugin.java
@Override
// ServerWebExchange 有点儿类似 Context 模式下的上下文信息
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
// 拿到业务系统服务街店列表信息
final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
if (CollectionUtils.isEmpty(upstreamList)) {
log.error("divide upstream configuration error: {}", rule.toString());
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
// 负载均衡
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
if (Objects.isNull(divideUpstream)) {
log.error("divide has no upstream");
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// set the http url
String domain = buildDomain(divideUpstream);
String realURL = buildRealURL(domain, soulContext, exchange);
// 将服务节点 URL、超时时间、重试次数等数据封装到 ServerWebExchange 的 attributes 字段里
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
// set the http timeout
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
// 完成后,执行下一个插件逻辑
return chain.execute(exchange);
}
这里关键代码是把服务节点的信息,保存在了 ServerWebExchange 的 attributes 字段里。
debug 时可以知道 divide 下一个插件是 WebClientPlugin:
// WebClientPlugin.java
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
// 将业务节点数据从 ServerWebExchange 中获取
String urlPath = exchange.getAttribute(Constants.HTTP_URL);
if (StringUtils.isEmpty(urlPath)) {
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
long timeout = (long) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_TIME_OUT)).orElse(3000L);
int retryTimes = (int) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_RETRY)).orElse(0);
log.info("The request urlPath is {}, retryTimes is {}", urlPath, retryTimes);
HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
}
把服务节点信息从 exchange 中获取,然后发送请求。
到这里就把对于网关服务的请求,委托给实际业务服务器节点进行处理并返回信息了。
今天先分析到这里,WebFlux 基础知识还是要恶补一下。