快速上手 Spring WebFlux 框架

一、前言

本文主要介绍基于 SpringBoot 如何快速上手使用 SpringFlux 框架开发 WEB 网站,同时稍微深入地了解以下 SpringBoot 启动 SpringFlux 框架的过程。

Spring 5.0 在原有的 Spring MVC Stack(又称 Servlet Stack)以外,又引入了新的 WEB 开发技术栈——Spring Flux Stack(又称 Reactive Stack),以满足不同的应用程序及开发团队的需求。

开发者一直在寻找最适合他们的应用程序的运行时、编程框架及架构。比如,有些用例最适合采用基于同步阻塞 IO 架构的技术栈,而 另一些用例 可能更适合于基于 Reactive Streams 响应式编程原则构建的异步的、非阻塞的技术栈。

后续将有系列文章深入介绍 SpringFlux 所采用的响应式编程原则及其代表实现 ProjectReactor,希望通过系列文章的介绍,让广大读者能够在逐步使用 SpringFlux 的过程中,理解响应式编程原理及实现,进而能够对项目应该选择 SpringMVC 还是 SpringWebFlux 形成自己的判断标准。

文章系列

二、快速上手

1、创建项目

打开 http://start.spring.io,来初始化一个 Spring WebFlux 项目吧。左侧一列是 Project Metadata,填上你的 group 名(我们使用 net.yesdata 吧),还有 Artifact 名(默认是 demo);然后右侧一列是 Dependencies(依赖),我们输入”reactive web”,在得到的下拉框中选择”Reacive Web”,然后下方”Selected Dependencies”处会显示我们选中的”Reactive Web”。然后点击”Generate Project”,浏览器会下载创建好的 Spring Boot 项目。加压后用你喜欢的 IDE(Eclipse 或者 IntelliJ IDEA 等)。

通过 start.spring.io 创建 reactive 栈的项目

2、增加一个 Controller

如果你熟悉 Spring MVC,你一定对 @Controller 注解不陌生。即使不熟悉也没关系,我会简单介绍一下。
Spring WebFlux 带有两种特征,一种是函数式的(Functional),另一种是基于注解的(annotation-based)。函数式编程不太适合快速上手,我们先选择基于注解。类似于 Spring MVC 模型,Spring WebFlux 模型也使用 @Controller 注解,以及 @RestController 注解。

用 IDE 打开项目后,追加一个类:SampleController
然后增加如下代码:

1
2
3
4
5
6
7
8
9
@RestController
@RequestMapping("/")
public class SampleController {

@GetMapping("/hello")
public String hello(){
return "hello";
}
}

运行后,用浏览器访问:http://localhost:8080/hello,将会在浏览器上看到”hello”字样。

增加 SampleController

SampleController 执行结果

怎么样,是不是很简单。

3、增加一个 WebFilter

增加一个 Filter 试试看。在 Spring WebFlux 框架下,增加 Filter 是通过实现 WebFilter 接口实现的。

1
2
3
4
5
6
7
8
@Component
public class FirstWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
serverWebExchange.getAttributes().put("User", "jerry");
return webFilterChain.filter(serverWebExchange);
}
}

三、深入了解一下 SpringBoot 启动 Spring WebFlux 的过程

对于已经笔记熟悉 Spring MVC 框架的开发人员来说,快速上手无法满足欲望。必须了解其背后的机制原理等,方能有知己知彼的感觉。接下来稍微探讨以下 SpringBoot 启动 Spring WebFlux 的过程。尚不太熟悉 Spring MVC 框架的开发人员也可以阅读一下本章内容,或许对更好地使用 Spring WebFlux 框架有帮助。

1、准备工作

你需要从 Gitubhttps://github.com/spring-projects/spring-framework下载 Spring WebFlux 的源码,以便进行后续分析。

2、@EnableWebFlux

回顾一下通过 http://start.spring.io 创建项目中,DemoApplication 启动类的注解中,有一个注解是:@EnableWebFlux。SpringBoot 就是通过这个注解,来启动 Spring WebFlux 的。

1
2
3
4
5
6
7
8
@EnableWebFlux
@SpringBootApplication
public class DemoApplication {

truepublic static void main(String[] args) {
truetrueSpringApplication.run(DemoApplication.class, args);
true}
}

再看一下 EnableWebFlux 的定义(可以到 SpringWebFlux 源码中找到),如下,我们注意到它引入了 DelegatingWebFluxConfiguration 类。看来秘密会藏在 DelegatingWebFluxConfiguration 类里呢。

1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebFluxConfiguration.class)
public @interface EnableWebFlux {
}

打开 DelegatingWebFluxConfiguration.java 文件(可以到 SpringWebFlux 源码中找到),然后你我们发现它的父类是 WebFluxConfigurationSupport 类。我们先来看看这个父类。

1
2
3
4
@Configuration
public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport {
......
}

下面是父类 WebFluxConfigurationSupport 实现的代码片段。我们注意到,它向 Spring Bean 容器中,注入了两个 Bean:webHandler 和 requestMappingHandlerMapping(实际上还注入了其它若干个 Bean,不过我们暂时只关注这两个吧)。

名为”webHandler”的 Bean 的类型是 DispatcherHandler,顾名思义是分发器,分发请求给各个处理单元。
名为”requestMappingHandlerMapping”的 Bean 的类型是 RequestMappingHandlerMapping,它的根本是实现了 HandlerMapping 接口。HandlerMapping 的作用是将请求映射到 Handler 上,比如把某个请求路径映射到某个 Controller 上。

关于 DispatcherHandler 和 HandlerMapping,后面章节继续深入讲解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class WebFluxConfigurationSupport implements ApplicationContextAware {
@Bean
public DispatcherHandler webHandler() {
return new DispatcherHandler();
}

@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setContentTypeResolver(webFluxContentTypeResolver());
mapping.setCorsConfigurations(getCorsConfigurations());

PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
if (useCaseSensitiveMatch != null) {
mapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
}
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}

return mapping;
}
}

2、DispatcherHandler

DispatcherHandler 将自身作为 Bean 注册到 Spring 的 Bean 容器中,它与 WebFilter、WebExceptionHandler 等一起,组成处理链。

DispatcherHandler 会从 Spring Configuration 中探寻到它需要的组件,主要是以下三类:

1
2
3
4
5
6
7
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
...
private List<HandlerMapping> handlerMappings;
private List<HandlerAdapter> handlerAdapters;
private List<HandlerResultHandler> resultHandlers;
...
}
  • HandlerMapping
    • 映射 Request 到 Handler
    • HandlerMapping 的默认实现是 RequestMappingHandlerMapping,用于 @RequestMapping 所标记的方法;RouterFunctionMapping 用于函数式端点的路由;SimpleUrlHandlerMapping 用于显式注册的 URL 模式与 WebHandler 配对
  • HandlerAdaptor
    • 帮助 DispatcherHandler 调用 Request 所映射的 Handler
    • HandlerAdaptor 的主要作用是将 DispatcherHandler 从调用具体 Handler 的细节中解放出来
  • HandlerResultHandler
    • 处理 Handler 的结果并终结 Response
    • 调用具体的 Handler 的返回结果是包装在 HandlerResult 中的,HandlerResultHandler 负责处理 HandlerResult。比如 ResponseBodyResultHandler 负责处理 @ResponseBody 标记的方法的返回值

示意图如下:

1
2
3
4
5
DispatcherHandler
|
|-->HandlerMapping
|-->HandlerAdaptor
|-->HandlerResultHandler

DispatcherHandler 的核心方法是:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
if (this.handlerMappings == null) {
return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
}
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}

该方法道出了 DispatcherHandler 处理请求的套路

  • 先通过 HandlerMapping 找到 Request 的专用 Handler,具体的代码片段是

    1
    concatMap(mapping -> mapping.getHandler(exchange))
  • 再通过 HandlerAdaptor 执行这个具体的 Handler,具体的代码片段是

    1
    flatMap(handler -> invokeHandler(exchange, handler))
  • 最后通过 HandlerResultHandler 处理 Handler 返回的结果,具体的代码片段是

    1
    flatMap(result -> handleResult(exchange, result))

可能你有疑问,这个方法里 Request 在哪呢?藏在”ServerWebExchange exchange”里了,关于 ServerWebExchange 又其它文章进一步介绍,这儿就不作详细说明了。

3、HandlerMapping

DispatcherHandler 套路中,处理 Request 的第一环节就是使用 HandlerMapping。常见的 HandlerMapping 有三种:RequestMappingHandlerMapping、RouterFunctionMapping、SimpleUrlHandlerMapping

RequestMappingHandlerMapping 是默认的,是否还记得 DispatcherHandler 中有发布一个 Bean 名为”requestMappingHandlerMapping”?它承担了映射 Request 与 annotation-based Handler 之间的关系。

由于我们习惯于使用 @Controller、@RestController、@RequestMapping 之类的注解来开发 WEB 项目,所以非常依赖 RequestMappingHandlerMapping。我们接下来就谈谈RequestMappingHandlerMapping

3-1、RequestMappingHandlerMapping 的类层级

1 层:AbstractHandlerMapping implements HandlerMapping, Ordered, BeanNameAware
^
|
2 层:AbstractHandlerMethodMapping implements InitializingBean
^
|
3 层:RequestMappingInfoHandlerMapping
^
|
4 层:RequestMappingHandlerMapping implements EmbeddedValueResolverAware

3-2、扫描可用 handler

大家注意到,第 2 层实现了 InitializingBean 接口,实现了该接口的类有机会在 BeanFactory 设置好它的所有属性后通过调用

1
void afterPropertiesSet()

方法通知它。我们来看看第 2 层的对该方法的实现吧。

1
2
3
4
5
6
7
8
@Override
public void afterPropertiesSet() {

initHandlerMethods();

// Total includes detected mappings + explicit registrations via registerMapping..
...
}

篇幅原因,这儿不细究方法中所调用的

1
initHandlerMethods();

的细节了 —— 实际上这里面是扫描所有 @Controller 注解的类中的 @RequestMapping 及其变体所修饰的方法,即最终会处理 Request 的 Handler,并缓存起来,以便后续进一步执行。

3-3、与 DispatcherHandler 相互配合

DispatcherHandler 会调用 MappingHandler 的

1
Mono<Object> getHandler(ServerWebExchange exchange)

方法,选择处理这个请求交的具体的 Handler。

四、总结

Spring WebFlux 模型的使用非常简单,尤其是对于熟悉 Spring MVC 模型的开发人员来说,无缝切换。使用 Spring Boot 框架开发时,使用 @Controller、@RestController、@RequestMapping 等注解,实现处理 Request 的 Handler 尤其方便。