Spring 5 WebFlux中@Controller和RouterFunction的区别

45

现在在Spring 5中有两种暴露HTTP端点的方式。

  1. 通过使控制器类成为@Controller@RestController
@Controller
@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类中配置路由:
@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()));
}

在使用任何一种方法时,是否存在性能差异?在从头开始编写应用程序时,我应该使用哪种方法?


2
这是一种偏好问题,而不是性能问题。 - JB Nizet
5
我仍然不明白为什么有人喜欢路由器。与控制器相比,它非常难以阅读。也许我没有理解到点子上......编辑:请参见http://www.sparkbit.pl/spring-web-reactive-rest-controllers/“这种方法的优点[功能性Web框架]是当您想要创建一个非常小的服务时,简单和减少样板代码。” - jaw
2
目前(Spring Boot 2.1),我建议使用Controller,不是因为性能原因,而是因为路由器功能没有验证、Swagger集成等功能。关于性能的问题,与bean reactive相关的改进将会到来。 - nekperu15739
2个回答

65

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

在使用@Controller@RestController注解时,我们采用基于注解的模型,使用注解进行映射(不仅仅是这个作用),以及副作用(函数式编程中不允许)来使得API正常工作。这些副作用可以是@Valid注解,为请求体提供内置的bean验证,或者是@RequestMapping与整个控制器的根路径。

另一方面,对于路由器函数,我们摆脱了任何副作用的注解,将其直接委托给函数式链:router -> handler。这两者非常适合构建基本的反应块:事件序列和两个主角,即发布者和订阅者。

MVC遗留问题:Servlets堆栈 vs Netty堆栈

当我们谈论@Controller时,我认为我们通常会考虑同步Java世界的术语:ServletsServletContextServletContainerInitializerDispatcherServlet等。即使我们从控制器返回Mono以使我们的应用程序具有响应性,我们仍然会遵循支持java.nio.*的Servlet 3.0规范,并在相同的servlet容器(如Jetty或Tomcat)上运行。因此,在这里我们将使用相应的设计模式和方法来构建Web应用程序。

RouterFunction受异步Java世界的真正反应式方法启发,Netty及其Channel Model

随后,在反应式环境下出现了一组新的类和它们的API:ServerRequestServerResponseWebFilter等。对我来说,它们是由Spring团队设计的,根据维护框架和了解新的网络系统需求的前几年而得出的。这些需求的名称为Reactive Manifesto

用例

最近,我的团队遇到了一个问题,即无法将SwaggerRouterFunction端点集成。它可以为@Controlers进行升级,但Spring团队推出了他们的解决方案 - Spring REST Docs,可以轻松连接到反应式WebTestClient。我在这里使用“连接”一词是因为它遵循真正的反应式含义:您可以在测试中构建API文档,而无需触及您的工作代码,而不是使用具有过载配置和副作用注释的Swagger。 2020年更新:尽管Spring Webflux现在已经可以使用OpenAPI规范随后与Swagger集成,但仍然缺乏配置简单性和透明度,我认为这是作为古老MVC方法的一部分的结果。

结论(个人意见)

由于没有性能影响,可能会听到类似于“完全基于个人喜好来选择使用什么”的话。我同意这确实是两个选项之间的个人喜好:向前还是向后移动,当你让自己在同一个领域停留十年时。我认为,Spring团队为@Controller提供反应式支持,是为了使旧项目能够在时间要求方面保持一致,并至少具有迁移的机会。如果您要从头开始创建Web应用程序,请毫不犹豫地使用介绍的反应式堆栈。

12
虽然有点晚了,但这对未来的读者可能有用。
通过切换到函数式路由声明: 1. 您可以在一个地方维护所有路由配置。 2. 在访问传入请求参数、路径变量和其他重要请求组件方面,您几乎可以获得与通常基于注释的方法相同的灵活性。 3. 您可以避免运行整个Spring框架基础设施,从而可能减少应用程序启动时间。
关于第3点,有些情况下,Spring生态系统的整个功能(IoC、注释处理、自动配置)可能是多余的,因此会降低应用程序的总体启动时间。
在微小的微服务、Amazon Lambda和类似的云服务时代,提供允许开发人员创建轻量级应用程序的功能,几乎具有相同的框架特性非常重要。这就是为什么Spring Framework团队决定将此功能纳入WebFlux模块的原因。
新的函数式Web框架允许您构建Web应用程序,而无需启动整个Spring基础架构。在这种情况下,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() 
                           ) 
                ); 
    }
}

3
我是functional-web的新手。在使用之前,我需要澄清以下疑问。 1)我们不能在单个控制器中维护所有端点吗? 2)如果没有@SpringBootApplication,其他Spring功能将如何启用? - Prasath

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接