11.2 定义函数式请求处理程序
Spring MVC 基于注解的编程模型从 Spring 2.5 开始就出现了,并且广受欢迎。不过,它也有一些缺点。
首先,任何基于注解的编程都涉及到注解应该对做什么以及如何做定义上区分。注解本身定义了什么;如何在框架代码的其他地方定义。当涉及到任何类型的定制或扩展时,这会使编程模型复杂化,因为这样的更改需要在注解外部的代码中工作。此外,调试这样的代码是很棘手的,因为不能在注解上设置断点。
另外,随着 Spring 的不断流行,来自其他语言和框架的新开发人员可能会发现基于注解的 Spring MVC(和 WebFlux)与他们已经知道的非常不同了。作为 WebFlux 的替代,Spring 5 引入了一个新的函数式编程模型来定义响应式 API。
这个新的编程模型更像是一个库,而不是一个框架,允许你将请求映射到不带注解的处理代码。使用 Spring 的函数式编程模型编写 API 涉及四种主要类型:
  • RequestPredicate —— 声明将会被处理的请求类型
  • RouteFunction —— 声明一个匹配的请求应该如何被路由到处理代码中
  • ServerRequest —— 表示 HTTP 请求,包括对头和正文信息的访问
  • ServerResponse —— 表示 HTTP 响应,包括头和正文信息
作为将所有这些类型组合在一起的简单示例,请考虑以下Hello World示例:
1
package demo;
2
3
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
4
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
5
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
6
import static reactor.core.publisher.Mono.just;
7
8
import org.springframework.context.annotation.Bean;
9
import org.springframework.context.annotation.Configuration;
10
import org.springframework.web.reactive.function.server.RouterFunction;
11
@Configuration
12
public class RouterFunctionConfig {
13
@Bean
14
public RouterFunction<?> helloRouterFunction() {
15
return route(GET("/hello"),
16
request -> ok().body(just("Hello World!"), String.class));
17
}
18
}
Copied!
首先要注意的是,已经选择静态地导入几个 helper 类,可以使用这些类来创建前面提到的函数类型。还静态导入了 Mono,以使其余代码更易于阅读和理解。
在这个 @Configuration 类中,有一个类型为 RouterFunction<?>。 如前所述,RouterFunction 声明一个或多个 RequestPredicate 对象与将处理匹配请求的函数之间的映射。
RouterFunctions 中的 route() 方法接受两个参数:RequestPredicate 和处理请求匹配的函数。在本例中,RequestPredicates 的 GET() 方法声明了一个 RequestPredicate,它与 /hello 路径的 HTTP GET 请求相匹配。
至于 handler 函数,它是作为 lambda 编写的,尽管它也可以是方法引用。虽然没有显式声明,但是处理程序 lambda 接受一个 ServerRequest 作为参数。它使用来自 ServerResponse 的 ok() 和来自 BodyBuilder 的 body() 返回一个 ServerResponse,后者是从 ok() 返回的。这样做是为了创建一个带有 HTTP 200(OK)状态代码和一个表示 Hello World 的 body 负载的响应!
如前所述,helloRouterFunction() 方法声明了一个仅处理单一类型请求的 RouterFunction。但是如果需要处理不同类型的请求,不必编写另一个 @Bean 方法。只需要调用 andRoute() 来声明另一个 RequestPredicate 到函数的映射。例如,下面介绍如何为/bye 的 GET 请求添加另一个处理程序:
1
@Bean
2
public RouterFunction<?> helloRouterFunction() {
3
return route(GET("/hello"), request -> ok().body(just("Hello World!"), String.class))
4
.andRoute(GET("/bye"), request -> ok().body(just("See ya!"), String.class));
5
}
Copied!
Hello World 的例子可以让你接触到新的东西。但是让我们把它放大一点,看看如何使用 Spring 的函数式 web 编程模型来处理类似于真实场景的请求。
为了演示函数式编程模型如何在实际应用程序中使用,让我们将 DesignTacoController 的功能重新设计为函数式样式。以下配置类是 DesignTacoController 的功能模拟:
1
@Configuration
2
public class RouterFunctionConfig {
3
@Autowired
4
private TacoRepository tacoRepo;
5
6
@Bean
7
public RouterFunction<?> routerFunction() {
8
return route(GET("/design/taco"), this::recents)
9
.andRoute(POST("/design"), this::postTaco);
10
}
11
12
public Mono<ServerResponse> recents(ServerRequest request) {
13
return ServerResponse.ok()
14
.body(tacoRepo.findAll().take(12), Taco.class);
15
}
16
17
public Mono<ServerResponse> postTaco(ServerRequest request) {
18
Mono<Taco> taco = request.bodyToMono(Taco.class);
19
Mono<Taco> savedTaco = tacoRepo.save(taco);
20
return ServerResponse
21
.created(URI.create(
22
"http://localhost:8080/design/taco/" +
23
savedTaco.getId()))
24
.body(savedTaco, Taco.class);
25
}
26
}
Copied!
如你所见,routerFunction() 方法声明了一个 routerFunction<?> 的 bean,就像 Hello World 的例子。但在处理哪些类型的请求以及如何处理这些请求方面有所不同。在本例中,创建 RouterFunction 来处理 /design/taco 的 GET 请求和 /design 的 POST 请求。
更突出的是路由是由方法引用处理的。当 RouterFunction 后面的行为相对简单和简短时,lambda 非常好。但是,在许多情况下,最好将该功能提取到单独的方法中(甚至在单独的类中提取到单独的方法中),以保持代码的可读性。
根据你的需要,/design/taco 的 GET 请求将由 recents() 方法处理。它使用注入的 TacoRepository 来获取一个 Mono<Taco>,从中提取 12 个项目。postTaco() 方法处理 /design 的 POST 请求,该方法从传入的ServerRequest 中提取 Mono<Taco>。postTaco() 方法然后使用 TacoRepository 保存它,然后使用返回 Mono<Taco> 的 save() 方法。
复制链接