【问题标题】:Difference between @Controller and RouterFunction in Spring 5 WebFluxSpring 5 WebFlux 中 @Controller 和 RouterFunction 的区别
【发布时间】:2018-04-15 23:09:16
【问题描述】:

在 Spring 5 现在有两种方法可以暴露 HTTP 端点。

  1. @Controller@RestController 通过创建控制器的类,例如
@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()));
}

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

【问题讨论】:

  • 这是一个偏好问题,而不是性能问题。
  • 我仍然不明白为什么有人会更喜欢路由器。与控制器相比,它非常不可读。也许我不明白这一点......编辑:见sparkbit.pl/spring-web-reactive-rest-controllers“当你想要创建的只是一个非常小的服务时,这种方法[功能性Web框架]的优点是简单和减少样板代码。”跨度>
  • 目前(spring boot 2.1)我建议使用Controller,不是出于性能原因,只是因为路由器功能没有验证,招摇集成等功能。与您关于性能的问题有关,改进将带有 bean 反应式。

标签: spring spring-boot spring-webflux reactive


【解决方案1】:

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

@Controller@RestController 注释的情况下,我们同意基于注释的模型,在该模型中,我们将注释用于映射(不仅如此),因此会产生副作用(在函数式中是不允许的) world) 以使我们的 API 正常工作。此类副作用可能是 @Valid 注释,它为请求的主体提供内置 bean 验证,或者 @RequestMapping 带有整个控制器的根路径。

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

MVC 传统:Servlet 堆栈与 Netty 堆栈

当我们谈论@Controller时,我会说我们通常会考虑同步Java世界:ServletsServletContextServletContainerInitializerDispatcherServlet 等。即使我们会返回@987654339 @ 来自控制器以使我们的应用程序具有响应性,我们仍将按照支持 java.nio.*Servlet 3.0 规范运行,并在相同的 servlet 容器上运行,例如 JettyTomcat。随后,我们将在此处使用相应的设计模式和方法来构建 Web 应用程序。

另一方面,RouterFunction 的灵感来自源自异步 Java 世界的真正反应式方法 - Netty 及其 Channel Model

随后出现了一组用于响应式环境的新类及其 API:ServerRequestServerResponseWebFilter 等。至于我,它们是由 Spring 团队根据前几年维护框架和理解新的 Web 系统需求而设计的。这些要求的名称是Reactive Manifesto

用例

最近我的团队遇到了无法将SwaggerRouterFucntion 端点集成的问题。它可以支持@Controlers,但 Spring 团队介绍了他们的解决方案 - Spring REST Docs,它可以很容易地连接到响应式WebTestClient。我在这里使用“连接”这个词,因为它遵循真正的反应式含义:您可以轻松地在测试中构建您的 API 文档,而无需触及您的工作代码。

2020 年更新:尽管现在 Spring Webflux 已经可以随后使用 OpenAPI 规范与 Swagger 集成,但它仍然缺乏配置简单性和透明度,以我的拙见,是成为古老 MVC 方法一部分的结果。

关闭(意见)

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

【讨论】:

    【解决方案2】:

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

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

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

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

    在微型微服务、Amazon Lambda 和类似云服务的时代,提供允许开发人员创建具有几乎相同的框架特性库的轻量级应用程序的功能非常重要。这就是 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() 
                               ) 
                    ); 
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-12-29
      • 2018-06-13
      • 2014-10-04
      • 1970-01-01
      • 2020-06-12
      • 2018-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多