Soul网关源码阅读(十)自定义简单插件编写

Soul网关源码阅读(十)自定义简单插件编写


简介

    综合前面所分析的插件处理流程相关知识,此次我们来编写自定义的插件:统计请求在插件链中的经历时长

编写准备

    首先我们先探究一下,一个Plugin是如何加载到上篇文章分析中的 plugins 中的,plugins 代码如下:

    我们查看下 plugins 的值,发现global也在里面,也就是所有的plugin都是在里面

public class SoulConfiguration {

    @Bean("webHandler")
    public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
        List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
        // global plugin 也在里面
        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()));
        return new SoulWebHandler(soulPlugins);
    }
}

    在上面的调用栈已经中断了,一直翻不到什么有用的东西,换换思路,我们在 globalPlugin 构造函数上打上断点,重启

    启动后,果然调用栈更新,我们看看调用栈,看到上面的 SoulConfiguration 调用是在 globalPlugin 之前,所以没啥有用的东西

    我们查看下 globalPlugin 构造函数的调用触发上一节,发现是下面这个: GlobalPluginConfiguration

@Configuration
@ConditionalOnClass(GlobalPlugin.class)
public class GlobalPluginConfiguration {

    @Bean
    public SoulPlugin globalPlugin(final SoulContextBuilder soulContextBuilder) {
        return new GlobalPlugin(soulContextBuilder);
    }
}

    我们仔细看看这个类,它是Spring Configuration,生成bean后注入进去,后面Spring会自己进行操作装配之类

    我们注意到这个bean返回的是 SoulPlugin ,还记得我们前面文章分析的所有Plugin都是继承于这个的,所以 List 就会自动装配到所有的 plugin。这个细节我也不是很懂,Spring还是不够熟系,后面需要补一补

    但看到这,我们大致思路就有了:

  • 1.写一个自定义插件
  • 2.写一个自定义插件的Spring配置,注入进去

自定义插件编写

    首先说明下,插件的编写应该遵循Soul网关的规范,还是应该写到Soul-Plugin这个模块中,但我们只是试验验证,就随意一点,直接写在Soul-Bootstrap中

    PS:时间有点小紧张,研究规范编写也伤时间,下次一定

工程结构

    此次需要编写两个文件:

  • 自定义插件:TimeRecordPlugin
  • 自定义插件配置:TimeRecordConfiguration

    目录结构大致如下:直接在源码的Soul-Bootstrap模块下

├─src
│  ├─main
│  │  ├─java
│  │  │  └─org
│  │  │      └─dromara
│  │  │          └─soul
│  │  │              └─bootstrap
│  │  │                  ├─configuration : 放入自定义插件配置
│  │  │                  ├─filter
│  │  │                  └─plugin :放入自定义插件
│  │  └─resources

自定义插件编写

    首先继承 SoulPlugin ,这样能正常注入到datalist中

    然后编写相应的处理函数,在处理函数中,我们在请求第一次进入到插件的时候,在exchange中放入当前的系统时间

    模仿 WebClientResponsePlugin ,在plugin链执行返回后,我们取出之前的系统时间,用当前系统时间减去,得到请求在plugin链中的经历时长

    order方面需要注意, globalPlugin的order为0,通过前面文章的分析,它进行的操作也不小,我们这个插件得在它前面,那我们的order就设置为-1

    这样,一个自定义插件就写好了,大致代码如下:

import lombok.extern.slf4j.Slf4j;
import org.dromara.soul.plugin.api.SoulPlugin;
import org.dromara.soul.plugin.api.SoulPluginChain;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Slf4j
public class TimeRecordPlugin implements SoulPlugin {

    private final String TIME_RECORD = "time_record";

    @Override
    public Mono<Void> execute(ServerWebExchange exchange, SoulPluginChain chain) {
        exchange.getAttributes().put(TIME_RECORD, System.currentTimeMillis());

        return chain.execute(exchange).then(Mono.defer(() -> {
            Long startTime = exchange.getAttribute(TIME_RECORD);

            if (startTime == null) {
                log.info("Get start time error");
                return Mono.empty();
            }

            long timeRecord = System.currentTimeMillis() - startTime;
            log.info("Plugin time record: " + timeRecord + " ms");
            return Mono.empty();
        }));
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

自定义插件配置

    灰常的简单,我们不需要任何东西,所以没有啥参数传入,直接new一个返回即可,代码如下:

import org.dromara.soul.bootstrap.plugin.TimeRecordPlugin;
import org.dromara.soul.plugin.api.SoulPlugin;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnClass(TimeRecordPlugin.class)
public class TimeRecordConfiguration {

    @Bean
    public SoulPlugin timeRecordPlugin() {
        return new TimeRecordPlugin();
    }
}

运行测试

    我们把Soul-admin、Soul-Bootstrap、Soul-Example-Http给启动起来

    访问:http://127.0.0.1:9195/http/order/findById?id=1111

    查看日志,看到明显自定义插件的日志打印,NICE!

o.d.s.plugin.httpclient.WebClientPlugin  : The request urlPath is http://192.168.101.104:8188/order/findById?id=1111, retryTimes is 0
o.d.s.bootstrap.plugin.TimeRecordPlugin  : Plugin time record: 9 ms
o.d.soul.plugin.base.AbstractSoulPlugin  : resilience4j selector success match , selector name :http_limiter
o.d.soul.plugin.base.AbstractSoulPlugin  : resilience4j rule success match , rule name :http_limiter
o.d.soul.plugin.base.AbstractSoulPlugin  : divide selector success match , selector name :/http
o.d.soul.plugin.base.AbstractSoulPlugin  : divide rule success match , rule name :/http/order/findById
o.d.s.plugin.httpclient.WebClientPlugin  : The request urlPath is http://192.168.101.104:8188/order/findById?id=1111, retryTimes is 0
o.d.s.bootstrap.plugin.TimeRecordPlugin  : Plugin time record: 10 ms

总结

    Soul网关的主要处理分析基本快结束了,下篇写一个总结就准备开始分析另外一个重要的模块:数据同步

    此篇的自定义插件编写还是比较简单,没有涉及到选择器和规则,但想做类似Divide之类的插件也不是不可以,那就直接把URI判断规则写死

    因为请求的所有数据都可以获取的,不用到后台只是规则不能动态变化而已

    有兴趣的老哥可以尝试写一下,还是挺有意思的,哈哈

Soul网关源码分析文章列表

Github

掘金

posted @ 2021-01-21 17:28  lw007  阅读(238)  评论(0编辑  收藏  举报