写点什么

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

用户头像
关注
发布于: 2021 年 01 月 21 日

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;
@Slf4jpublic 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 0o.d.s.bootstrap.plugin.TimeRecordPlugin  : Plugin time record: 9 mso.d.soul.plugin.base.AbstractSoulPlugin  : resilience4j selector success match , selector name :http_limitero.d.soul.plugin.base.AbstractSoulPlugin  : resilience4j rule success match , rule name :http_limitero.d.soul.plugin.base.AbstractSoulPlugin  : divide selector success match , selector name :/httpo.d.soul.plugin.base.AbstractSoulPlugin  : divide rule success match , rule name :/http/order/findByIdo.d.s.plugin.httpclient.WebClientPlugin  : The request urlPath is http://192.168.101.104:8188/order/findById?id=1111, retryTimes is 0o.d.s.bootstrap.plugin.TimeRecordPlugin  : Plugin time record: 10 ms
复制代码


总结


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


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


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


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


Soul 网关源码分析文章列表


Github



掘金



发布于: 2021 年 01 月 21 日阅读数: 22
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
Soul网关源码阅读(十)自定义简单插件编写