在Spring 5中的Reactive Web框架是一个包含了一套完整的服务端技术框架,这里面包括Web容器以及Web应用框架,是一套非常值得学习的技术栈。
Reactive Streams 是 JVM 中面向流的库标准和规范,它包含以下特性:
在Spring官网中,提供了下图的框架图:
从这张结构图我们可以清晰地区分出基于Servlet api和Reactive各自的技术栈结构,不过在最上层,Spring都给我们封装成了我们在SpringMVC中使用的基于注解的Web层api。如果以前在使用SpringMVC的时候,Controller里面没有耦合太多Servlet api的话,那么迁移过去就是一件很轻松的事情,在新的框架上开发也和以往没有太大的区别,除非使用到了Reactive的新特性。这一点Spring做的非常棒,充分体现出了Spring框架设计的优雅。
异步框架在处理IO密集型业务上有比较突出的性能优势,因此有了这一套框架后,以后在相关的技术选型上又多了一套方案。Spring Cloud Gateway也要求必须运行在Reactive Web中。
针对Reactive框架,Spring对Server端重新进行了抽象,其中一个最底层的接口就是HttpHandler,它的定义如下:
public interface HttpHandler {
/**
* Handle the given request and write to the response.
* @param request current request
* @param response current response
* @return indicates completion of request handling
*/
Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response);
}
但是真正在Web层中,它提供的抽象是WebHandler接口,它的定义如下:
public interface WebHandler {
/**
* Handle the web server exchange.
* @param exchange the current server exchange
* @return {@code Mono} to indicate when request handling is complete
*/
Mono<Void> handle(ServerWebExchange exchange);
}
两者通过HttpWebHandlerAdapter进行适配。结构如下:
比起以往我们熟悉的HttpServletRequest和HttpServletResponse这两个Servlet api,在Reactive框架中的api就再也看不到Servlet身影了。正因为如此,底层的Web容器就不一定要是Servlet容器了,所以Spring为此增加了一个新的基于Netty的内嵌Web容器。这个Web容器只是做了很简单的封装,使用了Netty原生HttpServerCodec对http请求和响应进行decode和encode。接下来的内容需要一些Netty的知识才能理解Spring的这套设计。
Spring封装了HttpServer、NettyWebServer以及NettyReactiveWebServerFactory来注入和启动整个Netty框架。整体流程大致如下:
过程稍微有些复杂,其中关键的地方就是TcpBridgeServer(TcpServer的子类)中的newHandler方法了:
@Override
public final Mono<? extends NettyContext> newHandler(BiFunction<? super NettyInbound, ? super NettyOutbound, ? extends Publisher<Void>> handler) {
Objects.requireNonNull(handler, "handler");
return Mono.create(sink -> {
ServerBootstrap b = options.get();
SocketAddress local = options.getAddress();
b.localAddress(local);
ContextHandler<Channel> contextHandler = doHandler(handler, sink);
b.childHandler(contextHandler);
if(log.isDebugEnabled()){
b.handler(loggingHandler());
}
contextHandler.setFuture(b.bind());
});
}
这里就是很熟悉的Netty框架的启动代码了,我们可以看到doHandler方法返回的ContextHandler被添加进Netty的pipeline中,而这里的doHandler已经在TcpBridgeServer中被重写,节选部分代码:
return ContextHandler.newServerContext(sink,
options,
loggingHandler,
(ch, c, msg) -> {
HttpServerOperations ops = HttpServerOperations.bindHttp(ch,
handler,
c,
compressPredicate,
msg);
if (alwaysCompress) {
ops.compression(true);
}
return ops;
})
.onPipeline(this)
.autoCreateOperations(false);
这里的onPipeline(this)方法就是设置一个回调,因为ContextHandler是Netty的ChannelInitializer,所以在它initChannel方法中会回调onPipeline设置进去的回调方法用来设置Netty的pipeline,具体被回调的就是TcpBridgeServer中的accept方法:
@Override
public void accept(ChannelPipeline p, ContextHandler<Channel> c) {
p.addLast(NettyPipeline.HttpCodec, new HttpServerCodec(
options.httpCodecMaxInitialLineLength(),
options.httpCodecMaxHeaderSize(),
options.httpCodecMaxChunkSize(),
options.httpCodecValidateHeaders(),
options.httpCodecInitialBufferSize()));
if (ACCESS_LOG_ENABLED) {
p.addLast(NettyPipeline.AccessLogHandler, new AccessLogHandler());
}
p.addLast(NettyPipeline.HttpServerHandler, new HttpServerHandler(c));
}
于是,经过配置后每个请求的Netty pipeline都会有如下结构的Handler:
由此可以看到,核心是HttpServerHandler,在这里面会将经过Netty的原生HTTP解码器处理过后的请求封装为ChannelOperations,并在ChannelOperations里面调用HttpHandler(在构造NettyWebServer的时候从Spring容器获取)的apply方法,交由web框架来处理请求。相关代码片段如下:
//136行 HttpServerHandler的channelRead方法中
parentContext.createOperations(ctx.channel(), msg);
//249行 ContextHandler的createOperations方法中
channel.eventLoop().execute(op::onHandlerStart);
//380行ChannelOperations的applyHandler方法中
Mono.fromDirect(handler.apply((INBOUND) this, (OUTBOUND) this))
.subscribe(this);
以上介绍的都是Spring根据Netty实现的Reactive Web容器,同时,Spring Boot也对常见的Servlet容器进行了适配,使得Reactive框架也能运行在Servlet 3.1容器里。至于适配则是通过ServletHttpHandlerAdapter,将Servlet的api转换成对应的Reactive api。
从SpringBoot原生的HttpHandlerAutoConfiguration中可以发现,前面介绍的HttpHandler其实是一个Web容器的底层抽象,Spring愿意暴露给应用层的还是6.1节中提到的WebHandler,这两者之间的适配是在HttpHandlerAutoConfiguration里面进行配置的,这里面使用到了WebHttpHandlerBuilder来进行构造WebHandler bean:
public HttpHandler build() {
WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
decorated = new ExceptionHandlingWebHandler(decorated, this.exceptionHandlers);
HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
if (this.sessionManager != null) {
adapted.setSessionManager(this.sessionManager);
}
if (this.codecConfigurer != null) {
adapted.setCodecConfigurer(this.codecConfigurer);
}
if (this.localeContextResolver != null) {
adapted.setLocaleContextResolver(this.localeContextResolver);
}
if (this.applicationContext != null) {
adapted.setApplicationContext(this.applicationContext);
}
return adapted;
}
即将Spring容器中注入的WebHandler和WebFilter经过FilteringWebHandler和ExceptionHandlingWebHandler两层委派,然后由HttpWebHandlerAdapter进行适配。FilteringWebHandler是一个能够使用WebFilter过滤器的WebHandler,让整个Web容器具备了过滤器功能,可以类比于Servlet中的Filter。从这里就可以得到一些总结和经验,不管是tomcat、原生netty,还是SpringBoot里面封装的EmbeddedNettyServer,或者是SpringMVC等和web服务相关的框架,都应用了责任链的设计模式,可见它在web服务器设计上的重要性。
在应用层中有一个WebHandler的实现是DispatcherHandler,它则是WebFlux的核心分发处理器。其实从名字上可以看出来它充当的角色和SpringMVC里面的DispatcherServlet是一样的,两者在核心处理的设计上也基本上是一样的。在DispatcherHandler中,同样包含了HandlerMapping、HandlerAdapter等抽象概念,处理逻辑也是按照下图所示:
其实,这样的设计和SpringMVC的DispatcherServlet有着异曲同工之处。因为Spring在web层没有直接强耦合SpringMVC的api,而是提供了Spring Web层,这也充分体现了Spring在设计框架时的严谨和优雅。如果我们在自己web层的代码遵循它的设计思想的话,没有耦合像ModelAndView、HttpServletRequest、HttpServletResponse(其实也没有必要耦合)等api的话,从理论上讲是很容易迁移到WebFlux上的。因为我们项目中要使用到Spring Cloud Gateway这种IO密集型业务,所以在技术选型的时候,想尝试一下异步框架可能带来的性能或者吞吐量的提升,所以在一开始便考虑使用WebFlux。其实对于以往使用SpringMVC的业务,也并非就需要迁移,也都是根据实际情况而定的。在Spring官网有更多关于WebFlux的背景和特性介绍。可以关注一下它关于Applicability和Performance的描述。
从WebFlux的设计上来看,它是不依赖Servlet容器的,虽然Spring也针对Tomcat,Jetty等Web容器进行了适配,但是我觉得Spring内置的Netty Server里应该才是它最合适的选择。所以这里就要求classpath下面不能有Servlet相关框架的依赖,也不能使用官方提供的方式来打war包在外置的Servlet容器中启动一个Spring Boot工程。
至此,Spring Boot中内嵌的Netty容器已经分析完了,可以看出来,这个Web容器很轻薄,只是基于Netty原生提供的HTTP编解码handler上面封装了一层HttpHandler。不过在如何将这个HttpHandler作用进Netty的pipeline中,Spring做了一些设计,有一些复杂。不过理解了配置后的pipeline最终结构,就可以清楚它的工作原理了,这样的设计也对我们使用Netty提供了一个很好的参考示例,所以个人觉得很有学习的价值。