Home  >  Article  >  Web Front-end  >  New in Spring 5: Functional Web Framework

New in Spring 5: Functional Web Framework

高洛峰
高洛峰Original
2016-10-15 13:50:361097browse

Example

We choose a sample program to start. Below is a reactive resource library for exposing Person objects. This responsive resource library is similar to the traditional non-responsive resource library, except that Flux corresponds to the traditional List, and Mono corresponds to the traditional Person object. Mono as completion flag: used to indicate the completion of the save work. For more Reactor type information, please check Dave's blog

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

Here we introduce how to use the new functional web framework to expose the resource library:

RouterFunction route = route(GET("/person/{id}"),
  request -> {
    Mono 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 people = repository.allPeople();
      return Response.ok().body(fromPublisher(people, Person.class));
    }))
  .and(route(POST("/person"),
    request -> {
      Mono person = request.body(toMono(Person.class));
      return Response.ok().build(repository.savePerson(person));
    }));

Here we introduce how to run Here is an example of Reactor Netty:

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

The last thing to do is to make a try request:

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

The above introduction covers a lot of content, let’s dig deeper!

Core Components

I will introduce the entire framework by introducing core components such as HandlerFunction, RouterFunction and FilterFunction in sequence. These three interfaces, as well as other types in this article, can be found in the org.springframework.web.reactive.function package.

Processing function

The starting point of the new framework is HandlerFunction, its essence is Function>, where Request and Response are newly defined immutable interfaces, providing a basic HTTP message description optimized for JDK8 DSL. There is a convenient constructor for constructing Response instances, very similar to the one in ResponseEntity. The annotation method corresponding to HandlerFunction is the method annotated by @RequestMapping.

The following is the processing method of "Hello World", which returns a message with status 200 and body as string.

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

As mentioned above, the processing methods built on Reactor are completely reactive, and they can accept publishers of Flux, Mono or other corresponding streams (Reactive Streams) as return type parameters.

It should be noted that the processing method itself has no side effects, because it uses response as a return value, not as a parameter (compare Servlet.service (ServletRequest, ServletResponse), which is essentially BiConsumer). A side-effect-free approach has many benefits: it’s easier to test, build, and optimize.

Routing function

Inbound requests are routed to HandlerFunction by RouterFunction, (i.e. Function>). When the condition is matched, the routing method will execute the processing method, otherwise it will return an empty result. The routing method has a similar function to the @RequestMapping annotation. However, there is another significant difference: when using annotations, routing will be limited to the range that the annotated value can express, and it is difficult to deal with the coverage of these methods; when using routing methods, the code is there and can be easily Overwrite or replace.

The following is an example of a routing method, including an inline processing method. This seems a bit redundant, don't worry because we will streamline it later.

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

Generally, you do not need to write a complete routing method, but statically introduce RouterFunctions.route(), so that you can use the request predicate (ie Predicate) and the processing method (HandlerFunction) to create a routing method. If the judgment is successful, the processing method is returned, otherwise an empty result is returned. The following is the above example rewritten using the route method:

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

After statically introducing RequestPredicates.*, you can use commonly used judgment expressions, such as matching paths, HTTP methods, content-type, etc. This way the above example will become more streamlined:

RouterFunction 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 helloWorld(Request request) {
    return Response.ok().body(fromObject("Hello World"));
  }
  public Response 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) 来进行过滤, 这里的 FilterFunction 其实就是一个 BiFunction, Response>。函数的处理器(handler)参数代表的就是整个链条中的下一项: 这是一个典型的 HandlerFunction, 但如果附加了多个过滤器的话,它也能够是另外的一个 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> 结束, 那样它就会可能有一个 String 类型的响应消息体。我们可以通过使用 RouterFunction.andSame() 而不是 and() 来完成这件事情。这个组合方法要求路由器函数参数是同一个类型。例如,我们可以让所有的响应消息变成小写的文本形式:

RouterFunction route =
  route(GET("/hello-world"), handler::helloWorld)
  .andSame(route(GET("/the-answer"), handler::theAnswer))
  .filter((request, next) -> {
    Response 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 并行)中运行了。

结论

This concludes the introduction to Spring’s new functional web framework. Let me briefly summarize:

A processing function handles a request by making a response,

A router function can be connected to a processing function and can be shared with other router functions,

A router function can be filtered by a filter function,

Router Features can run on a reactive web operating mechanism.


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn