代码之家  ›  专栏  ›  技术社区  ›  Deepak Kumar

Spring 5 WebFlux中@Controller和RouterFunction之间的差异

  •  37
  • Deepak Kumar  · 技术社区  · 7 年前

    现在有两种方法可以在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. 路由输入 @Configuration 使用RouterFunction初始化:
    @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 回复  |  直到 5 年前
        1
  •  49
  •   Serhii Povísenko Alferd Nobel    3 年前

    编程范式:命令与功能

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

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

    MVC遗留:servlet堆栈与Netty堆栈

    当我们谈论 @控制器 我想说的是,我们通常会从同步Java世界的角度来思考: Servlets , ServletContext , ServletContainerInitializer , DispatcherServlet 等等,即使我们会回来 Mono 从控制器到应用程序的反应性,我们仍将在以下方面发挥作用: Servlet 3.0 支持的规范 java.nio.* 并在相同的servlet容器上运行,例如 Jetty Tomcat . 随后,我们将使用相应的设计模式和方法来构建web应用程序。

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

    随后,出现了一组用于反应式环境的新类及其API: ServerRequest , ServerResponse , WebFilter Reactive Manifesto .

    用例

    最近,我的团队面临着无法整合的问题 Swagger 具有 RouterFucntion 端点。它可以投票支持 @Controlers ,但Spring团队介绍了他们的解决方案- Spring REST Docs 可以很容易地连接到无功 WebTestClient . 我在这里使用“connected”这个词是因为它遵循了真正的反应式含义:与其过载的配置和副作用注释不同,您可以轻松地在测试中构建API文档,而无需接触您的工作代码。

    2020年更新 :尽管从现在起,Spring Webflux已经可以使用OpenAPI规范与Swagger集成,但它仍然缺乏配置的简单性和透明度,在我看来,这是作为过时MVC方法的一部分的结果。

    结束(意见)

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

        2
  •  10
  •   Suren Aznauryan    4 年前

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

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

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

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

    在微型微服务、Amazon Lambdas和类似云服务的时代,重要的是提供功能,允许开发人员创建具有几乎相同框架功能库的轻量级应用程序。这就是为什么Spring框架团队决定将此功能合并到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() 
                               ) 
                    ); 
        }
    }