【Soul源码阅读】21.插件之大体流程

本文深入剖析了Soul网关的插件执行流程,从SoulPlugin接口到AbstractSoulPlugin的模板方法,再到SoulWebHandler中plugins的注入和执行逻辑。重点讲解了DividePlugin如何通过选择器和规则匹配,以及如何处理请求并委托给下一个插件。整个过程展示了Soul网关如何优雅地处理请求和路由。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.SoulPlugin 接口

2.AbstractSoulPlugin

3.SoulWebHandler 中的 plugins 注入

4.DividePlugin


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 基础知识还是要恶补一下。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值