• Spring 5 新功能:函数式 Web 框架


    英文:ARJEN POUTSMA 
    
    译文:debugging, 达尔文, 混元归一, leoxu, xufuji456
    
    链接:oschina.net/translate/new-in-spring-5-functional-web-framework
    

    就像在昨天Juergen发布的博客的一样,Spring 5.0框架第二个里程碑版本中介绍了一个新的函数式web框架。在这篇文章中,我将更详细的介绍这个框架。

    紧记该函数式web框架是在Spring5.0第一个里程碑版本基础上构建的。并且我们依旧提供基于注解的请求处理(例如@Controller,@RequestMapping),关于基于注解的请求处理部分的相关信息请查阅关于Spring5.0第一个里程碑版本的博客。

    示例 –

    我们选用示例程序作为开始。下面是一个响应资源库用于暴露Person对象。这个响应资源库与传统的无响应资源库类似,除了Flux对应传统的 List,Mono对应传统的 Person对象。Mono作为完成标识:用于指示保存工作完成.更多Reactor 类型信息请查阅 Dave发布的博客

        public interface PersonRepository {
    
          Mono<Person> getPerson(int id);
    
          Flux<Person> allPeople();
    
          Mono<Void> savePerson(Mono<Person> person);}

    这里我们介绍如何使用新的函数式web框架暴露资源库:

        RouterFunction<?> route = route(GET("/person/{id}"),
    
          request -> {
    
            Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id"))
    
              .map(Integer::valueOf)
    
              .then(repository::getPerson);
    
            return Response.ok().body(fromPublisher(person, Person.class));
    
          })
    
          .and(route(GET("/person"),
    
            request -> {
    
              Flux<Person> people = repository.allPeople();
    
              return Response.ok().body(fromPublisher(people, Person.class));
    
            }))
    
          .and(route(POST("/person"),
    
            request -> {
    
              Mono<Person> person = request.body(toMono(Person.class));
    
              return Response.ok().build(repository.savePerson(person));
    
            }));

    这里我们介绍如何运行它,下面是Reactor Netty的示例:

        HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
    
        ReactorHttpHandlerAdapter adapter =
    
          new ReactorHttpHandlerAdapter(httpHandler);
    
        HttpServer server = HttpServer.create("localhost", 8080);
    
        server.startAndAwait(adapter);

    最后要做的是,进行一次尝试请求:

        $ curl ' 
    
        {"name":"John Doe","age":42}

    上面的介绍覆盖了很多内容,下面让我们深入挖掘下!

    核心组件 –

    我将通过依次介绍HandlerFunction,RouterFunction以及FilterFunction 等核心组件来介绍整个框架。这三个接口以及本文中其他类型都可以在org.springframework.web.reactive.function包中找到。

    处理功能 –

    新框架的起点是HandlerFunction,其实质是Function

        HandlerFunction<String> helloWorld =
    
          request -> Response.ok().body(fromObject("Hello World"));

    如上,构建于Reactor之上的处理方法是完全的响应式的(reactive),它们可以接受Flux、Mono或者其他相应流(Reactive Streams)的发布者作为返回类型的参数。

    需要注意的是处理方法本身是没有副作用的,因为它将response作为返回值,而不是作为参数(对比Servlet.service(ServletRequest,ServletResponse),其实质是BiConsumer

    路由功能 –

    入站请求是由RouterFunction,(即Function

        RouterFunction<String> helloWorldRoute = 
    
          request -> {
    
            if (request.path().equals("/hello-world")) {
    
              return Optional.of(r -> Response.ok().body(fromObject("Hello World")));
    
            } else {
    
              return Optional.empty();
    
            }
    
          };

    一般不用写完整的路由方法,而是静态引入RouterFunctions.route(),这样就可以用请求判断式(RequestPredicate) (即 Predicate)和处理方法(HandlerFunction)创建路由方法了。如果判断式判断成功则返回处理方法,否则返回空结果。如下是用route方法方式重写上面的例子:

        RouterFunction<String> helloWorldRoute =
    
          RouterFunctions.route(request -> request.path().equals("/hello-world"),
    
            request -> Response.ok().body(fromObject("Hello World")));

    静态引入RequestPredicates.*后就可以使用那些常用的判断式了,如匹配路径、HTTP方法、content-type等。这样上面的例子将会变得更精简:

        RouterFunction<String> helloWorldRoute =
    
          RouterFunctions.route(RequestPredicates.path("/hello-world"),
    
            request -> Response.ok().body(fromObject("Hello World")));
    组合功能 –

    两个路由方法可以被组合成一个新的路由方法,可以路由任意处理方法:如果第一个路由不匹配则执行第二个。可以通过调用RouterFunction.and()方法实现,如下:

        RouterFunction<?> route =
    
          route(path("/hello-world"),
    
            request -> Response.ok().body(fromObject("Hello World")))
    
          .and(route(path("/the-answer"),
    
            request -> Response.ok().body(fromObject("42"))));

    上面的例子如果路径匹配/hello-world会返回“Hello World”,如果匹配/the-answer则返回“42”。如果都不匹配则返回一个空的Optional对象。注意,组合的路由是按顺序执行的,所以应该将更通用的方法放到更明确的方法的前面。

    请求判断式也是可以组合的,通过调研and或者or方法。正如预期的一样:and表示给定的两个判断式同时满足则组合判断式满足,or则表示任意判断式满足。如下:

        RouterFunction<?> route =
    
          route(method(HttpMethod.GET).and(path("/hello-world")), 
    
            request -> Response.ok().body(fromObject("Hello World")))
    
          .and(route(method(HttpMethod.GET).and(path("/the-answer")), 
    
            request -> Response.ok().body(fromObject("42"))));

    实际上,RequestPredicates中的大部分判断式都是组合的!比如RequestPredicates.GET(String)是RequestPredicates.method(HttpMethod)和RequestPredicates.path(String)的组合。所以上面的例子可以重写为:

        RouterFunction<?> route =
    
          route(GET("/hello-world"),
    
            request -> Response.ok().body(fromObject("Hello World")))
    
          .and(route(GET("/the-answer"),
    
            request -> Response.ok().body(fromObject(42))));
    方法引用 –

    此外,目前为止我们的处理方法都是行内的lambda表达式。尽管这样很适合于实例和简短的例子,但是当结合请求路由和请求处理两个关注点时,可能就有变“混乱”的趋势了。所以我们将尝试将他们简化。首先,创建一个包含处理逻辑的类:

        class DemoHandler {
    
          public Response<String> helloWorld(Request request) {
    
            return Response.ok().body(fromObject("Hello World"));
    
          }
    
          public Response<String> theAnswer(Request request) {
    
            return Response.ok().body(fromObject("42"));
    
          }}

    注意,这两个方法的签名都是和处理方法兼容的。这样就可以方法引用了:

        DemoHandler handler = new DemoHandler(); // or obtain via DI
    
        RouterFunction<?> route =
    
          route(GET("/hello-world"), handler::helloWorld)
    
          .and(route(GET("/the-answer"), handler::theAnswer));
    过滤功能 –

    由路由器函数进行映射的路由可以通过调用 RouterFunction.filter(FilterFunction

        RouterFunction<?> route =
    
          route(GET("/hello-world"), handler::helloWorld)
    
          .and(route(GET("/the-answer"), handler::theAnswer))
    
          .filter((request, next) -> {
    
            System.out.println("Before handler invocation: " + request.path());
    
            Response<?> response = next.handle(request);
    
            Object body = response.body();
    
            System.out.println("After handler invocation: " + body);
    
            return response;
    
          });

    注意这里对下一个处理器的调用时可选的。这个在安全或者缓存的场景中是很有用的 (例如只在用户拥有足够的权限时才调用 next)。

    因为 route 是一个没有被绑定的路由器函数,我们就得知道接下来的处理会返回什么类型的响应消息。这就是为什么我们在过滤器中要以一个 Response

        RouterFunction<String> route =
    
          route(GET("/hello-world"), handler::helloWorld)
    
          .andSame(route(GET("/the-answer"), handler::theAnswer))
    
          .filter((request, next) -> {
    
            Response<String> response = next.handle(request);
    
            String newBody = response.body().toUpperCase();
    
            return Response.from(response).body(fromObject(newBody));
    
          });

    使用注解的话,类似的功能可以使用 @ControllerAdvice 或者是一个 ServletFilter 来实现。

    运行一个服务端

    所有这些都很不错,不过仍然有一块欠缺:我们如何实际地将这些函数在一个 HTTP 服务器中跑起来呢? 答案毋庸置疑,那就是通过调用另外的一个函数。 你可以通过使用 RouterFunctions.toHttpHandler() 来将一个路由器函数转换成 HttpHandler。HttpHandler 是 Spring 5.0 M1 中引入的一个响应式抽象: 它能让你运行许多的响应式运行时: Reactor Netty, RxNetty, Servlet 3.1+, 以及 Undertow。在本示例中,我们已经展示了在 Reactor Netty 中运行一个路由会是什么样子的。对于 Tomcat 来说则是像下面这个样子:

        HttpHandler httpHandler = RouterFunctions.toHttpHandler(route);
    
        HttpServlet servlet = new ServletHttpHandlerAdapter(httpHandler);
    
        Tomcat server = new Tomcat();
    
        Context rootContext = server.addContext("",
    
          System.getProperty("java.io.tmpdir"));
    
        Tomcat.addServlet(rootContext, "servlet", servlet);  
    
        rootContext.addServletMapping("/", "servlet");
    
        tomcatServer.start();

    需要注意的意见事情就是上面的东西并不依赖于一个 Spring 应用程序上下文。就跟 JdbcTemplate 以及其它 Spring 的工具类那样, 要不要使用应用程序上下文是可以选的: 你可以将你的处理器和路由器函数在一个上下文中进行绑定,但并不是必须的。

    还要注意的就是你也可以将一个路由器函数转换到一个 HandlerMapping中去,那样就它可以在一个 DispatcherHandler (可能是跟响应式的 @Controllers 并行)中运行了。

    结论

    至此便结束了对 Spring 新函数式 web 框架的介绍。让我简单小结一下:

    • 处理功能通过作出回应来处理请求,

    • 路由器功能可连接到处理功能,并可与其他路由器功能共用,

    • 路由器功能可由过滤器的功能进行过滤,

    • 路由器功能可在响应式网络运行机制中运行。


    转载 http://geek.csdn.net/news/detail/107401

  • 相关阅读:
    HTTP-接触
    什么是虚拟机-粗略学习
    jQuery中的动画理论干货
    jQuery-中的事件
    熟悉又陌生的快捷方式
    jQuery中的DOM操作
    jQuery与javascript库
    jQuery-选择器(2)
    jest操作 Elasticsearch
    配置 Kibana
  • 原文地址:https://www.cnblogs.com/zhangxinhua/p/8319233.html
Copyright © 2020-2023  润新知