Soul网关源码学习(20)- WebFlux 核心组件的装配 和 请求流程

前言

之所以写这篇文章,是因为最近和别的小伙伴进行技术分享的时候,自己因为时间有限,一拍脑袋想出来的主题。至于为什么放到 soul 的系列文章里,那是因为:

  • 第一、我真正开始研究WebFlux 是从学习soul 源码开始的,soul 是基于 webflux 来写的。
  • 第二、我会结合 soul 里面的一些代码进行讲解
  • 第三、学习源码,不一定只学习源码框架本身的技术,扩展其他知识点也是非常的重要的,通过学习源码来提高和完善自己的知识体系。

一个简单的WebFlux demo

在开始分析 WebFlux 的相关知识之前,我们通过一个小的 demo 来简单体验一下其用法:

/**
     * 注解方法
     */
    @GetMapping("orders/{id}")
    public CommResult<Order> findOrderById(@PathVariable("id") Long id) {
		...
        return result;
    }
    /**
     * 函数式端点
     */
    @Bean
    public RouterFunction<ServerResponse> routerFunction() {
        return route(GET("/persons/{id}"),this::findPersonById);
    }

    private Mono<ServerResponse> findPersonById(ServerRequest serverRequest) {
		...
        return ServerResponse
                .status(HttpStatus.OK)
                .body(Mono.just(result), CommResult.class);
    }

上面是 Webflux 接口的两种写法:注解和函数式断点,其中注解的用法和 spring webMVC 是一样的,接下来我们简单测试一下:
在这里插入图片描述

WebFlux 核心组件的装配

测试完 demo 后,我们正式开始 WebFlux 核心组件装配的分析,主要分下面几个个阶段:

  • 寻找 XXXXAutoConfiguration
  • 装配了哪些核心组件
  • 小总结

Spring boot 的自动装载的外部化配置文件是 /resources/META-INF/spring.factories,所以我们第一时间想到的就是去依赖包 spring-boot-starter-webflux 下看一下这个文件,但是很遗憾,当我们在 idea 下展开依赖包的时候,发现只有一个 mainifest.mf 文件,没办法了解更多的信息。
在这里插入图片描述
看来直接在 idea 上追踪这个问题有点麻烦,不过我们可以直接去官方的 githup 仓库 spring-projects/spring-boot 看源码,我们直接找到子模块 spring-boot-starters/spring-boot-starter-webflux ,发现其只有一个 build.gradle 文件,我们点击查看该文件内容:

dependencies {
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-json"))
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-reactor-netty"))
	api("org.springframework:spring-web")
	api("org.springframework:spring-webflux")
}

通过上面的依赖,猜测Spring boot 的官方子模块的自动配置可能统一在 spring-boot-starters:spring-boot-starter 中。因此,我们接着找到同在 spring-boot-starters 目录下的子模块 spring-boot-starters:spring-boot-starter,同样其也只是有一个 build.gradle 文件:

dependencies {
	api(project(":spring-boot-project:spring-boot"))
	api(project(":spring-boot-project:spring-boot-autoconfigure"))
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-logging"))
	api("jakarta.annotation:jakarta.annotation-api")
	api("org.springframework:spring-core")
	api("org.yaml:snakeyaml")
}

没办法,只能继续猜,通过上面这些依赖包的名称来看的话,spring-boot-autoconfigure 的嫌疑无疑是最大的,因此,我接着跳到子模块 spring-boot-autoconfigure ,在这里我们终于看到 /resources/META-INF/spring.factories 文件了,打开 ctrl + F 搜索 “webflux”,找到:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
...

通过上面的内容我们找到了 WebFlux 自动装载的配置类 WebFluxAutoConfiguration:

public class WebFluxAutoConfiguration {
	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.webflux.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {return new OrderedHiddenHttpMethodFilter();}

	@Configuration(proxyBeanMethods = false)
	@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
	@Import({ EnableWebFluxConfiguration.class })
	public static class WebFluxConfig implements WebFluxConfigurer {...}

	@Configuration(proxyBeanMethods = false)
	public static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration {...}
}

通过上面的代码可以看到 WebFluxAutoConfiguration 通过 WebFluxConfig 导入了 EnableWebFluxConfiguration,后者继承了 DelegatingWebFluxConfiguration,而 DelegatingWebFluxConfiguration 又继承了 WebFluxConfigurationSupport,最终我们在 WebFluxConfigurationSupport 看到了一个似乎很面熟的东西:

// 她让我想起了当年的大明湖畔的 DispatcherSerlvet
// 所以很容易猜到它就是前端控制器
// 第一个核心组件
public DispatcherHandler webHandler() {
	return new DispatcherHandler();
}

通过上面的方法,我们可以的看到 DispatcherHandler BeanName 是 “webHandler”,这个让我们联想到 soul 的什么了呢?

public class SoulConfiguration {
	// SoulWebHandler 就是替换了 spring Webflux 的默认 webHandler bean
	// 也就是 SoulWebHandler 替换了原来 DispatcherHandler  的作用,因为 soul 是网关,有自己的控制策略。
    @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);
    }
	
	//但是 soul 还是额外创建了一个 DispatcherHandler Bean,beanName 是 dispatcherHandler
	//这个有什么用呢?这个待会分析!
    @Bean("dispatcherHandler")
    public DispatcherHandler dispatcherHandler() {
        return new DispatcherHandler();
    }
 }

但是目前仅仅只是找到一个 DispatcherHandler 而已,其他人呢?还有 DispatcherHandler 是用来干啥的,或者说它下一步要装载到哪里?注入到什么地方?

这时候,我们可以来看一下 Spring WebFlux 的官方文档,在这里我们看到 WebHandler 上面还有一个关键的 API HttpHandler,这里有一个摘录一端说明:

For server request processing there are two levels of support.

  • HttpHandler: Basic contract for HTTP request handling with non-blocking I/O and Reactive Streams back pressure, along with adapters for Reactor Netty, Undertow, Tomcat, Jetty, and any Servlet 3.1+ container.
  • WebHandler API: Slightly higher level, general-purpose web API for request handling, on top of which concrete programming models such as annotated controllers and functional endpoints are built.

从上面的英文可以看到 HttpHandler 是比 WebHandler 更加底层的一个 API,也就是说很可能是由 HttpHandler 来调用 WebHandler (请求由下往上),那 DispatcherHandler 作为 WebHandler 的一个实现,也很有可能会被 HttpHandler 的具体实现所持有。所以我们通过 Idea (ctrl + alt + 鼠标左键)来找一下其有哪些实现类,发现有好几个,但是我们稍微看一下它们的实现的话,应该不难猜出 HttpWebHandlerAdapter 就是我们要找的,因为它持有一个 WebHandher 对象,当然我们也可以分别在这些类的handler 方法中断点一下,然后发送一个测试请求,看看断点会在哪里停留。

总之,我们找到了调用 DispatcherHandher 的地方了,那下一步我们要找 HttpWebHandlerAdapter 是在哪里被装配的,并且看看注入的 webHandler 是否就是 DispatcherHandler?

我们继续通过 idea 进行追踪,发现其只在 WebHttpHandlerBuilder#build 方法中被创建:

public HttpHandler build() {
	WebHandler decorated;
	decorated = new FilteringWebHandler(this.webHandler, this.filters);
	decorated = new ExceptionHandlingWebHandler(decorated,  this.exceptionHandlers);
	HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
	...
}

从上面的代码可以看到最后注入的 WebHandler 的实现是ExceptionHandlingWebHandler,不是 DispatcherHandler,那我们在看一下 ExceptionHandlingWebHandler 的 handler 方法:

public Mono<Void> handle(ServerWebExchange exchange) {
	Mono<Void> completion;
	try {
		completion = super.handle(exchange);
	}
	//后面是异常处理
	...
	return completion;
}

其实从名称就可以知道,这是一个负责处理异常的对象(通过零个或者多个 WebExceptionHandler 来进行处理),也就是说最后真正的请求处理是委派给了其 decorated 对象,从上面代码来看也就是 FilteringWebHandler 对象。

同样,从名称也可以猜到 FilteringWebHandler 干的是过滤器的活(类似 servlet 里面的 Filter),我们也来看一下其 handle 方法:

public FilteringWebHandler(WebHandler handler, List<WebFilter> filters) {
	super(handler);
	//webHandler 注入到FilterChain
	this.chain = new DefaultWebFilterChain(handler, filters);
}

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
	return this.chain.filter(exchange);
}

DefaultWebFilterChain#filter:

public Mono<Void> filter(ServerWebExchange exchange) {
	return Mono.defer(() ->
			this.currentFilter != null && this.chain != null ?
					invokeFilter(this.currentFilter, this.chain, exchange) :
					this.handler.handle(exchange));
}

从上面代码可以看到,它会先通过 DefaultWebFilterChain 去链式调用 每个WebFilter 对象,最后再委派 delegate 执行接下来的任务,而 FilteringWebHandler 的 delegate = this.webHandler,那它到底是谁?

好了,主角闪耀登场 :

// webHandler 是通过构造函数注入的
private WebHttpHandlerBuilder(WebHandler webHandler, ApplicationContext applicationContext) {
	Assert.notNull(webHandler, "WebHandler must not be null");
	this.webHandler = webHandler;
	this.applicationContext = applicationContext;
}

public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
	//WebHttpHandlerBuilder 会再这里被构造
	//webHandler 通过根据名称 “webHandler” 进行依赖查找,默认情况下就是 DispatcherHandler ,soul 的是 SoulWebHandler
	WebHttpHandlerBuilder builder = new WebHttpHandlerBuilder(
			context.getBean(WEB_HANDLER_BEAN_NAME, WebHandler.class), context);
	...
}

所以到这里我们就知道了 DispatcherHandler 到底是如何和 HttpWebHanderlAdapter 关联起来的,最后我们再通过 idea 追踪到静态方法 WebHttpHandlerBuilder#applicationContext 被调用的地方:

@AutoConfigureAfter({ WebFluxAutoConfiguration.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class HttpHandlerAutoConfiguration {
	@Configuration
	public static class AnnotationConfig {
		...
		@Bean
		public HttpHandler httpHandler() {
			return WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
		}
	}
}

到这里我们就找到另外一条自动装配的主线 HttpHandlerAutoConfiguration,我们注意到其注解声明了必须在 WebFluxAutoConfiguration 之后进行装配,因为它要等待 DispatcherHandler 等相关资源先初始化。

最后我们由回到 spring-boot-autoconfigure 的 resources/META-INF/spring.factories 文件,通过搜索也能找到 HttpHandlerAutoConfiguration 的配置信息:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\

所以到这里我们就知道了 HttpHandler 和 WebHandler 到底是怎么被装配的,还有它们之间的关系。

那又有新问题了,HttpHandler 又被谁调用的,也就是 HttpWebHandlerAdapter 到底是被谁调用的呢?简直就是鸡生蛋,蛋生鸡,没完没了!

我们继续通过 idea 追踪下去,看看到底是哪些类使用了 HttpHandler,我们发现有好多类:NettyReactiveWebServerFactory、JettyReactiveWebServerFactory、TomcatReactiveWebServerFactory等。但是通过这些类的名称,我们得到了一个信息,HttpHandler 往下就是具体的 Web 容器了,也就是说有具体的 Web 容器来接受请求,然后通过调用 HttpWebHandlerAdapter#handler 来进行后续的处理。这样我们也就明白了 为什么 HttpHandler 的具体实现是一个适配器,因为它要适配不同的底层容器,Spring webFLux 不仅仅可以使用 NettyReactiveWebServer 作为底层Web 容器,也可以使用 tomcat、jetty等 servlet 3.1的容器。

下面我们以 tomcat 和 netty 为底层容器分别 debug 一下:

启动阶段

使用tomcat

要使用 tomcat 作为 web 容器 需要添加相应的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>

我们可以在下面方法打个断点,确定启动的时候是否真的使用 tomcat 作为容器

public WebServer getWebServer(HttpHandler httpHandler) {
	Tomcat tomcat = new Tomcat();
	...
	//关键:HttpWebHandlerAdapter  被注入
	TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
	prepareContext(tomcat.getHost(), servlet);
	return new TomcatWebServer(tomcat, getPort() >= 0);
}

接着我们通过跟踪上面代码中的 TomcatHttpHandlerAdapter,在其父类 ServletHttpHandlerAdapter 中找到了关键的信息:

//ServletHttpHandlerAdapter 是一个 Servlet !!!
public class ServletHttpHandlerAdapter implements Servlet {
	...
	@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		//把 HttpServletRequest 转换成 ServerHttpRequest
		ServerHttpRequest httpRequest;
		try {
			httpRequest = createRequest(((HttpServletRequest) request), asyncContext);
		}
		catch (URISyntaxException ex) {
			...
			return;
		}
		//把 HttpServletResponse  转换成 ServerHttpResponse
		ServerHttpResponse httpResponse = createResponse(((HttpServletResponse) response), asyncContext);
		if (httpRequest.getMethod() == HttpMethod.HEAD) {
			httpResponse = new HttpHeadResponseDecorator(httpResponse);
		}
		//委派 HttpWebHandlerAdapter 执行请求后续的处理
		this.httpHandler.handle(httpRequest, httpResponse).subscribe(subscriber);
	}
}

使用Netty Webserver

Webflux 默认使用的就是 Netty Webserver,所以我们只需要把上面的 Tomcat 的依赖去掉即可,接着我们在下面的方法打个断点,同时保留上面tomcat 的断点,重启服务,看看究竟会进入哪个方法。

@Override
public WebServer getWebServer(HttpHandler httpHandler) {
	HttpServer httpServer = createHttpServer();
	//关键:HttpWebHandlerAdapter  被注入
	ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(
			httpHandler);
	return new NettyWebServer(httpServer, handlerAdapter, this.lifecycleTimeout);
}

不出意外的话,应该是会进入上面的方法,接着我们跟踪到上面代码中关键类 ReactorHttpHandlerAdapter,找到其 apply 方法:

@Override
	public Mono<Void> apply(HttpServerRequest request, HttpServerResponse response) {
		NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(response.alloc());
		try {
			adaptedRequest = new ReactorServerHttpRequest(request, bufferFactory);
			adaptedResponse = new ReactorServerHttpResponse(response, bufferFactory);
		}
		...
		//
		return this.httpHandler.handle(adaptedRequest, adaptedResponse)
				.doOnError(ex -> logger.warn("Handling completed with error: " + ex.getMessage()))
				.doOnSuccess(aVoid -> logger.debug("Handling completed with success"));
	}

到这里我们就了解了 HttpWebHandlerAdapter 到底是如何被底层 web 容器所调用的,所以到最后我们可以用下面一幅图来小结 Spring WebFLux 核心组件的装载过程:

在这里插入图片描述

WebFlux 请求流程

学习完 Spring WebFlux 的核心组件装载流程后,我们再来学习其请求流程到底是怎么样的。

其实请求流程粗略上看,可以看作上面装载流程的倒序:从web容器 -> HttpWebHandlerAdapter -> ExceptionHandlingWebHandler -> FilteringWebHandler -> DispatcherHandler (默认)

DispatcherHandler

所以这里我们关键分析 DispatcherHandler 的实现:

public class DispatcherHandler implements WebHandler, ApplicationContextAware {...}

通过上面的代码可以看到,DispatcherHandler 实现了两个接口:WebHandler 和 ApplicationContextAware 。我们先来看一下 ApplicationContextAware 接口到底做了啥?

@Override
public void setApplicationContext(ApplicationContext applicationContext) {initStrategies(applicationContext);}
//主要工作就是根据类型 进行依赖查找。
protected void initStrategies(ApplicationContext context) {
	//beansOfTypeIncludingAncestors 方法会把相应类型的 Bean 的集合找出来,同时会处理 BeanFactory的层次性问题,就是会把父BeanFactory 的 Bean 也找出来。
	Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
			context, HandlerMapping.class, true, false);
	...
	Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
			context, HandlerAdapter.class, true, false);
	...
	Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
			context, HandlerResultHandler.class, true, false);
	...
}

从上面代码可以看到 DispatcherHandler 会查找三个关键类型的 Bean 的集合:HandlerMapping、HandlerAdapter、HandlerResultHandler。
我们先通过名字,先大概猜一下其作用:

  • HandlerMapping:路径映射,根据请求的资源路径映射到具体的执行(方法或者函数式接口)
  • HandlerResultHandler:翻译一下就是“结果处理”。
  • HandlerAdapter:适配,应该就是处理请求了。因为负责处理请求的方式不只一种,可以是注解的方法,也可以是函数式接口(lambda 表达式),也就是 通过 HandlerMapping 映射返回的对象是不一样的, 要把它们适配成统一的API。

ps:其实在 soul 里面也能找到很多这种方式,比如 HystrixPlugin 的 Command 接口

然后我们再来看 DispatcherHandler 的 handle 方法,该方法是 WebHandler 的接口方法:

	public Mono<Void> handle(ServerWebExchange exchange) {
		...
		return Flux.fromIterable(this.handlerMappings)
				//映射:注解返回的是 HttpMethod 对象;函数式断点返回的是 HandlerFunction 对象
				.concatMap(mapping -> mapping.getHandler(exchange))
				.next()
				.switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
				//适配 调用
				.flatMap(handler -> invokeHandler(exchange, handler))
				//结果处理
				.flatMap(result -> handleResult(exchange, result));
	}

我们可以使用最开始测试的 WebFlux demo,通过请求注解接口来 debug 一下,我们可以发现其对应的实现:

  • HandlerMapping:实现是 RequestMappingHandlerMapping,根据请求返回一个 HttpMethod
  • HandlerAdapter:实现是 RequestMappingHandlerAdapter,根据 HttpMethod 描述,通过反射去调用相应的方法处理请求
  • HandlerResultHandler:实现是ResponseBodyResultHandler,对返回结果进行处理

再通过函数式断点接口 debug 一下:

  • HandlerMapping:RouterFunctionMapping,根据路径返回一个 HandlerFunction
  • HandlerAdapter:HandlerFunctionAdapter,调用 HandlerFunction
  • HandlerResultHandler:ServerResponseResultHandler,对返回结果进行处理

我们最后来 soul 熔断插件 resilience4j 如何进行 fallback 操作的:

    default Mono<Void> fallback(ServerWebExchange exchange, String uri, Throwable t) {
		...
        DispatcherHandler dispatcherHandler = SpringBeanUtils.getInstance().getBean(DispatcherHandler.class);
        ServerHttpRequest request = exchange.getRequest().mutate().uri(Objects.requireNonNull(UriUtils.createUri(uri))).build();
        ServerWebExchange mutated = exchange.mutate().request(request).build();
        return dispatcherHandler.handle(mutated);
    }

可以看到其是使用了 Beaname 为 “dispatcherHandler ” 的 DispatcherHandler 对象,因为 原来 BeanName 为 “webHandler” 的 DispatcherHandler 对象已经被 SoulWebHandler 对象覆盖了。

所以 soul 的请求可以总结为下面两个路线:
代理请求:

  • web容器 -> HttpWebHandlerAdapter -> ExceptionHandlingWebHandler -> FilteringWebHandler -> SoulWebHandler -》插件链

fallback 请求:

  • web容器 -> HttpWebHandlerAdapter -> ExceptionHandlingWebHandler -> FilteringWebHandler -> DispatcherHandler -》 controller 方法 或者 HandleFunction

总结

到这里,关于 WebFlux 核心组件的装配和请求流程的分析就写完了,其实这部分内容要深究细节的话,远不止于此,而且我相信上面所说的也肯定有不少漏洞,所以如果发现有不妥的地方,欢迎指正,希望大家共同进步!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值