- [Spring Webflux](#spring-webflux) - [Concept](#concept) - [核心机制](#核心机制) - [reactive](#reactive) - [back pressure](#back-pressure) - [reactive stream](#reactive-stream) - [编程模型](#编程模型) - [并发模型](#并发模型) - [spring mvc](#spring-mvc) - [webflux](#webflux) - [调用阻塞api](#调用阻塞api) - [Reactive Core](#reactive-core) - [HttpHandler](#httphandler) - [server api adapters](#server-api-adapters) - [Reactor Netty](#reactor-netty) - [Undertow](#undertow) - [Tomcat](#tomcat) - [Jetty](#jetty) - [WebHandler API](#webhandler-api) - [bean types for WebHttpHandlerBuilder auto-detect](#bean-types-for-webhttphandlerbuilder-auto-detect) - [Form Data](#form-data) - [Multipart Data](#multipart-data) - [Filter](#filter) - [UrlHandler](#urlhandler) - [Exceptions](#exceptions) - [Logging](#logging) - [Log Id](#log-id) - [Appenders](#appenders) - [DispatcherHandler](#dispatcherhandler) - [DispatcherHandler委托](#dispatcherhandler委托) - [Processing](#processing) - [Result Handling](#result-handling) - [Annotated Controllers](#annotated-controllers) - [@Controller](#controller) - [AOP](#aop) # Spring Webflux ## Concept ### 核心机制 #### reactive reactive代表基于“事件响应”的编程模型, #### back pressure 在spring webflux中,`back pressure`为反应式编程的核心机制,用于协调生产者和消费者之间的速率差异,令系统在高负载或资源受限的情况下仍能稳定运行。 - 同步场景:在同步场景下,阻塞式调用是一种天然的back pressure形式,调用方会阻塞并等待,直到被调用方执行完成 - 非阻塞场景:在非阻塞的代码中,需要关注事件速率,生产者产生事件的速率不能压过消费者消费的速率 ##### reactive stream reactive stream为一个小型规范,定义了异步组件和back pressure交互的规范。reactive stream的主要用途是让Subscriber控制publisher产生数据的速率。 ### 编程模型 `spring-web` module包含Spring webflux的响应式基础,包括若夏内容: - http抽象 - 对支持server的reactive stream adapters - codecs - 核心WebHandler API,其与servlet api兼容 Spring webflux提供了如下两种编程模型: - `Annotated Controllers`: 和spring mvc一致,基于相同的注解。`spring mvc`和`webflux controllers`都支持`reactive return type`,因此,很难区分`spring mvc`和`webflux controllers` - `Functional Endpoint`:基于lambda的、轻量的函数编程模型。其可以被看做是一个`支持对请求进行route和handle`的工具集合。 ### 并发模型 #### spring mvc 在`spring mvc`(通常为servlet应用)中,其假设当前线程可能会被阻塞(例如,远程调用等会阻塞当前线程)。为了减少处理请求时阻塞所带来的影响,servlet容器会使用包含大量线程的线程池。 #### webflux 对于`webflux`(通常为non-blocking server),其会假设应用并不会阻塞,故而,非阻塞的server可以使用一个线程数较少且固定的线程池(event loop workers)来处理请求。 #### 调用阻塞api 当想要在webflux中使用阻塞api时,可以按如下方式进行使用。`RxJava`和`Reactive`都支持`publushOn`操作,其支持在另一个线程中`继续执行`。 ## Reactive Core `spring-web`对于reactive web应用具有如下支持: - 对于server request的处理,拥有如下两种级别的支持: - `HttpHandler`: 基于`non-blocking I/O`和`Reactive Stream back pressure`,适配`Reactor Netty, Undertow, Tomcat, Jetty以及任一Servlet Container - `WebHandler`:稍高级别的、通用的web api,用于请求处理,在此基础上构建基于`annotated controllers`和`functional endpoints`的编程模型 - 对于client端,`ClientHttpConnector`用于发送http请求,同时兼容`非阻塞io和Reactive Stream back pressure` - codecs用于客户端和服务端的序列化和反序列化 ### HttpHandler HttpHandler中只通过一个单独的方法来处理请求和相应,其对不同的http server api进行了最小化的抽象。 下表描述了支持的api:
Server name Server API used Reactive Streams support

Netty

Netty API

Reactor Netty

Undertow

Undertow API

spring-web: Undertow to Reactive Streams bridge

Tomcat

Servlet non-blocking I/O; Tomcat API to read and write ByteBuffers vs byte[]

spring-web: Servlet non-blocking I/O to Reactive Streams bridge

Jetty

Servlet non-blocking I/O; Jetty API to write ByteBuffers vs byte[]

spring-web: Servlet non-blocking I/O to Reactive Streams bridge

Servlet container

Servlet non-blocking I/O

spring-web: Servlet non-blocking I/O to Reactive Streams bridge

下表中描述了server依赖:
Server name Group id Artifact name

Reactor Netty

io.projectreactor.netty

reactor-netty

Undertow

io.undertow

undertow-core

Tomcat

org.apache.tomcat.embed

tomcat-embed-core

Jetty

org.eclipse.jetty

jetty-server, jetty-servlet

#### server api adapters 如下示例展示了如何使用`HttpHandler` Adapters来适配不同的Server Api: ##### Reactor Netty ```java HttpHandler handler = ... ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler); HttpServer.create().host(host).port(port).handle(adapter).bindNow(); ``` ##### Undertow ```java HttpHandler handler = ... UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler); Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build(); server.start(); ``` ##### Tomcat ```java HttpHandler handler = ... Servlet servlet = new TomcatHttpHandlerAdapter(handler); Tomcat server = new Tomcat(); File base = new File(System.getProperty("java.io.tmpdir")); Context rootContext = server.addContext("", base.getAbsolutePath()); Tomcat.addServlet(rootContext, "main", servlet); rootContext.addServletMappingDecoded("/", "main"); server.setHost(host); server.setPort(port); server.start(); ``` ##### Jetty ```java HttpHandler handler = ... Servlet servlet = new JettyHttpHandlerAdapter(handler); Server server = new Server(); ServletContextHandler contextHandler = new ServletContextHandler(server, ""); contextHandler.addServlet(new ServletHolder(servlet), "/"); contextHandler.start(); ServerConnector connector = new ServerConnector(server); connector.setHost(host); connector.setPort(port); server.addConnector(connector); server.start(); ``` ### WebHandler API `org.springframework.web.server` package基于`HttpHandler`构建,提供了通用的Web API。Web Api由多个`WebException`, 多个`WebFilter`, 一个`WebHandler`组件构成,组成了一个chain。 相比于`HttpHandler`仅仅是对不同http server的抽象,`WebHandler`提供了一个更加通用、更加广泛的功能集合: - user sessions attributes - request attributes - resolved `Locale` or `Principal` for request - abstractions for multipart data #### bean types for WebHttpHandlerBuilder auto-detect 在spring上下文中,`WebHttpHandlerBuilder`可以自动探测到如下类型的components:
Bean name Bean type Count Description

<any>

WebExceptionHandler

0..N

Provide handling for exceptions from the chain of WebFilter instances and the target WebHandler. For more details, see Exceptions.

<any>

WebFilter

0..N

Apply interception style logic to before and after the rest of the filter chain and the target WebHandler. For more details, see Filters.

webHandler

WebHandler

1

The handler for the request.

webSessionManager

WebSessionManager

0..1

The manager for WebSession instances exposed through a method on ServerWebExchange. DefaultWebSessionManager by default.

serverCodecConfigurer

ServerCodecConfigurer

0..1

For access to HttpMessageReader instances for parsing form data and multipart data that is then exposed through methods on ServerWebExchange. ServerCodecConfigurer.create() by default.

localeContextResolver

LocaleContextResolver

0..1

The resolver for LocaleContext exposed through a method on ServerWebExchange. AcceptHeaderLocaleContextResolver by default.

forwardedHeaderTransformer

ForwardedHeaderTransformer

0..1

For processing forwarded type headers, either by extracting and removing them or by removing them only. Not used by default.

#### Form Data `ServerWebExchange`将会向外暴露如下方法,用于访问form data: ```java Mono> getFormData(); ``` `DefaultServerWebExchange`使用`HttpMessageReader`来对form data(application/x-www-form-urlencoded)进行parse操作,将formdata转化为`MultiValueMap`。 #### Multipart Data `ServerWebExchange`向外暴露如下方法,用于访问multipart data。 ```java Mono> getMultipartData(); ``` `DefaultServerWebExchange`将会使用`HttpMessageReader>`来对`multipart/form-data`,`multipart/mixed`,`multipart/related`数据进行转换,数据将会被转化为`MultiValueMap`类型。 #### Filter 在`WebHandler API`中,可以使用`WebFilter`来实现拦截式的逻辑,当使用`Webflux Config`时,`WebFilter`的注可以通过将其注册为bean来实现。 对于`WebFilter`的优先级,欸可以通过使用`@Order`注解或实现`Ordered`接口来实现。 #### UrlHandler 在编写web程序时,可能希望controller endpoint既能够匹配末尾带`/`的url版本,又能匹配末尾不带`/`的url版本。 > 例如,想要让`@GetMapping("/home")`既能够匹配`GET /home`,又能够匹配`GET /home/`。 对此,可以使用`UrlHandlerFilter`进行处理,其支持如下两种配置: - 当接收到带有末尾带有`/`的url时,向浏览器返回一个重定向状态,让浏览器重新请求末尾不带`/`的url - 将请求当作不带末尾`/`,并对请求继续处理 实例化UrlHandlerFilter的示例如下: ```java UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter // will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post" .trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT) // will mutate the request to "/admin/user/account/" and make it as "/admin/user/account" .trailingSlashHandler("/admin/**").mutateRequest() .build(); ``` #### Exceptions 在`WebHandler Api`中,可以使用`WebExceptionHandler`对异常进行处理。当使用`Webflux Config`时,注册`WebExceptionHandler`仅需将其声明为bean即可,可以通过`@Order`注解或`Ordered`接口来指明顺序。 #### Logging ##### Log Id 在webflux中,同一个请求可能在多个线程中被执行过,故而,在定位请求日志时,无法通过线程id来关联请求。为了解决该问题,spring webflux在打印消息时前缀了一个`log id`,其是针对单个特定请求的。 在server端,log id存储在`ServerWebExchange`中的`LOG_ID_ATTRIBUTE`属性中。在client端,log id存储在`Client Request`中的`LOG_ID_ATTRIBUTE`属性中。 ##### Appenders `slf4j`和`log4j`等日志库都提供异步logger,用于避免阻塞。使用异步logger会存在部分缺点,例如`丢弃无法排队的异步消息`。但是,其仍然是目前非阻塞框架的最佳选择。 ## DispatcherHandler 和spring MVC类似,spring webflux同样按照`font controller pattern`进行设计,包含一个`central WebHandler`,即`DispatcherHandler`。 Dispathcer提供了一个共享的请求处理算法,将实际的处理逻辑委托给其他可配置的组件。 `DispatcherHandler`通过spring配置来发现委托的组件。`DispatcherHandler`其本身也是一个spring bean,并且实现了`ApplicationContextAware`接口,可以访问spring上下文。 如果`Dispatcher`的bean name为`webHandler`,那么其会被`WebHttpHandlerBuilder`发现,并且将其放入`request-processing chain`中。 webflux应用中包含的spring configuration通常包括: - bean name为`webHandler`的`DispatcherHandler` - `WebFilter`和`WebExceptionHandler` beans - 被委托给`webHandler`的beans - 其他 上述配置将被被`WebHttpHandlerBuilder`使用,用于构建process chain,示例如下所示: ```java ApplicationContext context = ... HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build(); ``` 上述示例中返回的handler可以和`server adapter`结合使用。 ### DispatcherHandler委托 `DispatcherHandler`会将请求的委托给特定的bean对象,bean对象会处理请求并且将结果渲染到response中。 DispatcherHandler会对如下类型的bean进行auto-detect。
Bean type Explanation

HandlerMapping

Map a request to a handler. The mapping is based on some criteria, the details of which vary by HandlerMapping implementation — annotated controllers, simple URL pattern mappings, and others.

The main HandlerMapping implementations are RequestMappingHandlerMapping for @RequestMapping annotated methods, RouterFunctionMapping for functional endpoint routes, and SimpleUrlHandlerMapping for explicit registrations of URI path patterns and WebHandler instances.

HandlerAdapter

Help the DispatcherHandler to invoke a handler mapped to a request regardless of how the handler is actually invoked. For example, invoking an annotated controller requires resolving annotations. The main purpose of a HandlerAdapter is to shield the DispatcherHandler from such details.

HandlerResultHandler

Process the result from the handler invocation and finalize the response. See Result Handling.

### Processing `DispatcherHandler`按照如下方式处理请求: - 首先,会挨个查询`HandlerMapping`用于查找匹配的handler,会匹配第一个匹配的handler - 如果匹配到handler,会通过对应的HandlerAdapter执行该handler,并且会将handler返回的值作为`HandlerResult`暴露 - `HandlerResult`将会被分配给指定的`HandlerResultHandler`进行处理,可能会直接向response中写入数据或渲染view ### Result Handling 通过`HandlerAdapter`对handler进行调用,返回的结果会被包裹在`HandlerResult`中,随之还包含额外的上下文。 `HandlerResult`将会被传递给第一个支持其的上下文,下表中展示了`HandlerResultHandler`的实现:
Result Handler Type Return Values Default Order

ResponseEntityResultHandler

ResponseEntity, typically from @Controller instances.

0

ServerResponseResultHandler

ServerResponse, typically from functional endpoints.

0

ResponseBodyResultHandler

Handle return values from @ResponseBody methods or @RestController classes.

100

ViewResolutionResultHandler

CharSequence, View, Model, Map, Rendering, or any other Object is treated as a model attribute.

See also View Resolution.

Integer.MAX_VALUE

## Annotated Controllers spring webflux提供了基于注解的编程模型,通过使用注解来向外暴露request mappings。通过注解标注的controllers可包含灵活的方法签名,并且无需继承任何基类或实现指定接口。 使用示例如下所示: ```java @RestController public class HelloController { @GetMapping("/hello") public String handle() { return "Hello WebFlux"; } } ``` ### @Controller 在基于注解的webflux变成模型中,可以使用`@Controller`或`@RestController`来声明controller bean。 #### AOP 对于部分场景,需要在运行时使用aop proxy来对controller进行decorate。 对于针对controller的aop,推荐使用`class-based proxy`,可以使用`@EnableTransactionManagement(proxyTargetClass=true)`来实现。