11.2定义函数式请求处理程序

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示例:

package demo;

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
import static reactor.core.publisher.Mono.just;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
@Configuration
public class RouterFunctionConfig {
    @Bean
    public RouterFunction<?> helloRouterFunction() {
        return route(GET("/hello"),
                     request -> ok().body(just("Hello World!"), String.class));
    }
}

首先要注意的是,已经选择静态地导入几个helper类,可以使用这些类来创建前面提到的函数类型。还静态导入了Mono,以使其余代码更易于阅读和理解。

在这个@Configuration类中,有一个类型为RouterFunction<?>。 如前所述,RouterFunction声明一个或多个RequestPredicate对象与将处理匹配请求的函数之间的映射。

RouterFunctions中的route()方法接受两个参数:RequestPredicate和处理请求匹配的函数。在本例中,RequestPredicatesGET()方法声明了一个RequestPredicate,它与 /hello 路径的HTTP GET请求相匹配。

至于handler函数,它是作为lambda编写的,尽管它也可以是方法引用。虽然没有显式声明,但是处理程序lambda接受一个ServerRequest作为参数。它使用来自ServerResponseok()和来自BodyBuilderbody()返回一个ServerResponse,后者是从ok()返回的。这样做是为了创建一个带有HTTP 200(OK)状态代码和一个表示Hello Worldbody负载的响应!

如前所述,helloRouterFunction()方法声明了一个仅处理单一类型请求的RouterFunction。但是如果需要处理不同类型的请求,不必编写另一个@Bean方法。只需要调用andRoute()来声明另一个RequestPredicate到函数的映射。例如,下面介绍如何为/byeGET请求添加另一个处理程序:

@Bean
public RouterFunction<?> helloRouterFunction() {
    return route(GET("/hello"), request -> ok().body(just("Hello World!"), String.class))
        .andRoute(GET("/bye"), request -> ok().body(just("See ya!"), String.class));
}

Hello World的例子可以让你接触到新的东西。但是让我们把它放大一点,看看如何使用Spring的函数式web编程模型来处理类似于真实场景的请求。

为了演示函数式编程模型如何在实际应用程序中使用,让我们将DesignTacoController的功能重新设计为函数式样式。以下配置类是DesignTacoController的功能模拟:

@Configuration
public class RouterFunctionConfig {
    @Autowired
    private TacoRepository tacoRepo;

    @Bean
    public RouterFunction<?> routerFunction() {
        return route(GET("/design/taco"), this::recents)
            .andRoute(POST("/design"), this::postTaco);
    }

    public Mono<ServerResponse> recents(ServerRequest request) {
        return ServerResponse.ok()
            .body(tacoRepo.findAll().take(12), Taco.class);
    }

    public Mono<ServerResponse> postTaco(ServerRequest request) {
        Mono<Taco> taco = request.bodyToMono(Taco.class);
        Mono<Taco> savedTaco = tacoRepo.save(taco);
        return ServerResponse
            .created(URI.create(
                "http://localhost:8080/design/taco/" +
                savedTaco.getId()))
            .body(savedTaco, Taco.class);
    }
}

如你所见,routerFunction()方法声明了一个routerFunctionbean,就像Hello World的例子。但在处理哪些类型的请求以及如何处理这些请求方面有所不同。在本例中,创建RouterFunction来处理 /design/tacoGET请求和 /designPOST请求。

更突出的是路由是由方法引用处理的。当RouterFunction后面的行为相对简单和简短时,lambda非常好。但是,在许多情况下,最好将该功能提取到单独的方法中(甚至在单独的类中提取到单独的方法中,以保持代码的可读性。

根据你的需要,/design/tacoGET请求将由recents()方法处理。它使用注入的TacoRepository来获取一个Mono,从中提取12个项目。postTaco()方法处理 /designPOST请求,该方法从传入的ServerRequest中提取MonopostTaco()方法然后使用TacoRepository保存它,然后使用返回Monosave()方法。

上一页
下一页