前言
在前一篇文章《Soul网关源码学习(7)- 代理转发流程概览2》 中,我们理清楚了不同协议的代理转发流程,知道了每种协议处理的步骤和关键节点在哪里。那么现在就开始对这些节点逐个进入详细的分析,而 SoulWebHandler 作为 Request 的第一站自然就是我们首先要攻克的对象。
WebHandler 简介
在分析 SoulWebHandler 之前,我们需要先了解一下 WebHandler,因为 SoulWebHandler 是 WebHandler 的自定义实现。那 WebHandler 又是来自哪里呢?到底是什么呢?WebHandler 是 Spring WebFlux 的API!
根据 Spring 官方的说法:Spring WebFlux 是一套全新的 Reactive Web 栈技术,实现完全非阻塞,支持 Reactive Streams 背压等特性,并且运行环境不限于 Servlet 容器(Tomcat、Jetty、Undertow),如 Netty 等。
所以既然是全新的 Web 栈技术,也就是 Spring WebFlux 脱离了传统的 Servlet API,而 WebHandler 就是其提供类似 Servlet 功能的一个接口。如果自己手动实现过 Servlet 编程的应该对下面这个东西不陌生:
//在曾经的过去我们是这样写接口处理的
public class MyServlet implements Servlet {
...
@Override
public void service(ServletRequest paramServletRequest,
ServletResponse paramServletResponse) throws ServletException,
IOException {
// 处理你的请求 doGet、doPost
}
...
}
WebHandler 在 Spring WebFlux 中起到的就是类似上面的作用,只不过处理请求的方法有 service 变成了 handle。
WebHandler 在 Spring WebFlux 中的加载是比较复杂的,这里不做详细介绍,有兴趣的同学可以自己去了解一下,这里只介绍一下如果我们想实现一个自己的 WebHandler 需要注意上什么。
WebHandler Bean 在 WebFlux 中是唯一的,而且 Bean Name 必须是 “webHandler”,你可以实现多个,但是只有 Bean Name 是 “webHandler” 会起作用。
//这是 Spring Web 下的类,会在Spring WebFlux 加载过程中被调用
public final class WebHttpHandlerBuilder {
public static final String WEB_HANDLER_BEAN_NAME = "webHandler";
//WebHander Bean 会在这里实例化,可以看到它是通过 "webHandler" 名称去依赖查找的
public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder(
context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class), context);
...
}
}
接着我们来看一下 soul 是如何注入自定义 WebHandler的:
public class SoulConfiguration {
// 这是 soul 注入自定义 WebHandler的方法
// 实现类就是我们今天的主角 SoulWebHandler, 注意 BeanName
@Bean("webHandler")
public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
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);
}
}
到这里我们就了解了 SoulWebHandler 之所以是网关请求入口的原因,还有它是如何被装载的。
SoulWebHandler 详细分析
构造方法
要分析一个类,那首先就是先来分析其构造方法:
public SoulWebHandler(final List<SoulPlugin> plugins) {
//注入插件
this.plugins = plugins;
// 定义 Reactor 调度线程池
String schedulerType = System.getProperty("soul.scheduler.type", "fixed");
if (Objects.equals(schedulerType, "fixed")) {
int threads = Integer.parseInt(System.getProperty(
"soul.work.threads", "" + Math.max((Runtime.getRuntime().availableProcessors() << 1) + 1, 16)));
heduler = Schedulers.newParallel("soul-work-threads", threads);
} else {
scheduler = Schedulers.elastic();
}
}
插件注入
通过构造方法的注释,我相信小伙伴也不难理解,现在可能的疑问就是插件又是哪里加载的呢?
我们在次回到上面 WebHandler 的 @Bean 方法,该方法的参数是 ObjectProvider<List< SoulPlugin>>,这里又可能会有两个疑问:
-
为什么使用 ObjectProvider,而不是直接使用List?
使用ObjectProvider可以避免强依赖导致的依赖对象不存在异常。由于 Soul 的插件是设计成可插拔的,因此这里的 SoulPlugin 的 Bean 是有可能一个都不存在的,这时候如果直接使用 List 作为参数注入,就有可能会在装载过程中抛出异常。而 ObjectProvider#getIfAvailable 就提供了一个安全的依赖查找方法,如果此时容器中不存在相应的 Bean 则只会返回空,而不会抛出异常。
-
SoulPlugin 都在哪里被定义了?
这个问题容易很容易解答,我们以 Dubbo 插件为例:
@Configuration @ConditionalOnClass(ApacheDubboPlugin.class) public class ApacheDubboPluginConfiguration { @Bean public SoulPlugin apacheDubboPlugin(final ObjectProvider<DubboParamResolveService> dubboParamResolveService) { return new ApacheDubboPlugin(new ApacheDubboProxyService(dubboParamResolveService.getIfAvailable())); } ... }
通过上面代码可以看到两点:第一,插件通过 @Bean 方法加载;第二,通过条件注解 @ConditionalOnClass 来实现条件装配,只有插件被依赖了才会被满足装配的条件。
执行方法
public Mono<Void> handle(@NonNull final ServerWebExchange exchange) {
//设置度量的相关记录,这里是记录开始时间,后面返回记录结束时间
MetricsTrackerFacade.getInstance().counterInc(MetricsLabelEnum.REQUEST_TOTAL.getName());
Optional<HistogramMetricsTrackerDelegate> startTimer = MetricsTrackerFacade.getInstance().histogramStartTimer(MetricsLabelEnum.REQUEST_LATENCY.getName());
//调用插件连,这里是关键
return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
.doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}
handler 是 WebHandler 的接口方法,作用类似 Servlet 中的 service 方法,负责处理请求,下一步就是进入插件链的执行流程了,到这里就和我们上一篇的文章连接起来了。最后我们再对插件连的调用代码做一个简单的讲解,也就是上面代码注释了“关键”的地方。
- subscribeOn:表示执行流程在构造函数时候定义的线程池(调度器)上执行。
- doOnSuccess:度量记录代理转发流程的结束时间。doOnSuccess 会在 Mono成功完成时触发(结果为T或null),这意味着无论数据状态如何,处理本身都成功完成。
- excute:
public Mono<Void> execute(final ServerWebExchange exchange) { //代码很简单,就是返回一个空的 Mono,插件链执行的如何都是返回以空的 Mono //空的 Mono 意味着不管插件链执行成功或者异常都能走到上面的doOnSuccess return Mono.defer(() -> { if (this.index < plugins.size()) { //...省略插件链调用代码 } return Mono.empty(); }); }
总结
到这里我们就把网关入口 SoulWebHandler 的代码分析完毕了,其实 SoulWebHandler 本身的代码逻辑很简单,文章更多时候分析的是代码扩展出去的其他知识点。其实,这也是学习源码的主要目的之一,我们借助分析成熟的框架来扩展和巩固自己的知识体系。一个成熟的开源框架往往都会使用到大量当下流行的技术,这些技术有的可能是我们的知识盲点,也有的可能是我们熟悉但是缺乏实践的,都可以通过学习这些开源框架来促使我们想办法去掌握它们。
这章节介绍完入口后,那下面就是主干道了,后面我们将继续分析请求进了“村口”后该何去何从。