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 还是不够熟系,后面需要补一补
但看到这,我们大致思路就有了:
自定义插件编写
首先说明下,插件的编写应该遵循 Soul 网关的规范,还是应该写到 Soul-Plugin 这个模块中,但我们只是试验验证,就随意一点,直接写在 Soul-Bootstrap 中
PS:时间有点小紧张,研究规范编写也伤时间,下次一定
工程结构
此次需要编写两个文件:
目录结构大致如下:直接在源码的 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
掘金
评论