Spring 5 WebFlux 中@Controller 和 RouterFunction 的区别

Difference between @Controller and RouterFunction in Spring 5 WebFlux

spring5 现在有两种公开 HTTP 端点的方法。

  1. @Controller@RestController 通过使控制器的 class,例如
@RestController
@RequestMapping("persons")
public class PersonController { 

    @Autowired
    private PersonRepo repo;

    @GetMapping("/{id}")
    public Mono<Person> personById(@PathVariable String id){
        retrun repo.findById(id);
    }
}
  1. 使用 RouterFunctions 在 @Configuration class 中路由:
@Bean
public RouterFunction<ServerResponse> personRoute(PersonRepo repo) {
    return route(GET("/persons/{id}"), req -> Mono.justOrEmpty(req.pathVariable("id"))                                             
                                                 .flatMap(repo::getById)
                                                 .flatMap(p -> ok().syncBody(p))
                                                 .switchIfEmpty(notFound().build()));
}

使用 anyone 方法是否有任何性能差异?从头开始编写应用程序时应该使用哪个。

编程范式:命令式与函数式

在使用 @Controller@RestController 注释的情况下,我们同意基于注释的模型,在该模型中我们使用注释进行映射(而且不仅如此),因此会产生副作用(即在功能世界中是不允许的)使我们的 API 工作。这种副作用可能是 @Valid 为请求主体提供内置 bean 验证的注解,或者 @RequestMapping 整个控制器的根路径。

另一方面,对于路由器功能,我们摆脱了在 API 实现方面包含任何副作用的注释,并将其直接委托给功能链:router -> handler。这两个非常适合构建基本的反应块:一系列事件和两个主角,这些事件的发布者和订阅者。

MVC 遗产:Servlets 堆栈与 Netty 堆栈

当我们谈论 @Controller 时,我会说我们通常会从同步 Java 世界的角度来思考:ServletsServletContextServletContainerInitializer, DispatcherServlet 等。即使我们将从控制器 return Mono 使我们的应用程序具有反应性,我们仍然会根据支持 [=22= 的 Servlet 3.0 规范进行游戏] 和 运行 在相同的 servlet 容器上,例如 JettyTomcat。随后,这里我们将使用相应的设计模式和方法来构建 Web 应用程序。

另一方面,

RouterFunction 受到源自异步 Java 世界的真正反应式方法的启发 - Netty 及其 Channel Model.

随后出现了一组新的 类 及其用于响应式环境的 API:ServerRequest, ServerResponse, WebFilter and others. As for me, they were designed by the Spring team in accordance with the previous years of maintaining the framework and understanding new web systems requirements. The name for those requirements is Reactive Manifesto

用例

最近我的团队遇到了无法集成的问题Swagger with RouterFucntion endpoints. It could upvote for @Controlers, but the Spring team introduced their solution - Spring REST Docs that could be easily connected to reactive WebTestClient。我在这里使用单词 'connected' 因为它遵循真正的反应性含义:而不是 Swagger 的重载配置和副作用注释,您可以轻松地在测试中构建您的 API 文档,而无需触及您的工作代码全部.

2020 年更新:尽管从现在开始 Spring Webflux 已经可以随后使用 OpenAPI 规范,它仍然缺乏配置的简单性和透明度,在我看来,这是作为陈旧的 MVC 方法的一部分的结果。

关闭(意见)

由于没有性能影响,它可能会听到类似于“使用什么完全取决于个人偏好”的内容。我同意这确实是个人偏好在两种选择中的选择:当你让自己在同一个领域停留十年时向前或向后移动。我认为 @Controller 的被动支持是由 Spring 团队完成的,以使旧项目能够以某种方式与时间要求保持一致并且至少有迁移的机会。 如果您打算从头开始创建 Web 应用程序,请不要犹豫并使用引入的反应式堆栈。

虽然有点晚了,但这可能对未来的读者有用。

通过切换到功能路由声明:

  1. 您在一处维护所有路由配置
  2. 在访问传入的请求参数、路径变量和请求的其他重要组成部分方面,您可以获得与通常的基于注释的方法几乎相同的灵活性
  3. 您可以避免整个 Spring 框架基础结构 运行,这可能会减少应用程序的引导时间

关于第 3 点,在某些情况下 Spring 生态系统的整个功能(IoC、注释处理、自动配置)可能是冗余的,因此减少了应用程序的整体启动时间。

在微型微服务、Amazon Lambdas 和类似云服务的时代,重要的是提供允许开发人员创建具有几乎相同的框架功能库的轻量级应用程序的功能。这就是 Spring Framework 团队决定将此功能合并到 WebFlux 模块中的原因。

新的功能性 Web 框架允许您在不启动整个 Spring 基础结构的情况下构建 Web 应用程序。这种情况下的 main 方法应该有点像下面(注意,没有 @SpringBootApplication 注释)

class StandaloneApplication { 
    public static void main(String[] args) { 
        HttpHandler httpHandler = RouterFunctions.toHttpHandler(
           routes(new BCryptPasswordEncoder(18))
        ); 

        ReactorHttpHandlerAdapter reactorHttpHandler = new ReactorHttpHandlerAdapter(httpHandler); 

        HttpServer.create() 
            .port(8080) 
            .handle(reactorHttpHandler) 
            .bind() 
            .flatMap(DisposableChannel::onDispose) 
            .block(); 
    }

    static RouterFunction<ServerResponse> routes(PasswordEncoder passwordEncoder ) { 
        return
            route(
                POST("/check"), 
                request -> request 
                          .bodyToMono(PasswordDTO.class)
                          .map(p -> passwordEncoder 
                              .matches(p.getRaw(), p.getSecured())) 
                          .flatMap(isMatched -> isMatched 
                              ? ServerResponse 
                                  .ok() 
                                  .build() 
                              : ServerResponse 
                                  .status(HttpStatus.EXPECTATION_FAILED) 
                                  .build() 
                           ) 
                ); 
    }
}