Home > Java > javaTutorial > Spring5 new features-detailed code examples of functional web framework

Spring5 new features-detailed code examples of functional web framework

黄舟
Release: 2017-03-20 10:44:43
Original
1925 people have browsed it

As mentioned in Juergen's blog yesterday, the second milestone of Spring 5.0 is the introduction of a new functional web framework. In this article, we will give more information about this framework.

Examples

Let’s start with some excerpts from a sample application. Below is the response information base exposing Personobject. Very similar to a traditional, non-responsive information base, except that it returns Flux whereas the traditional one returns List, and Mono<Person> ; returns Person. Mono is used as a completion flag: indicating when the save is completed. For more information on Reactor types, see this blog post.

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

Here's how we expose the resource library with the new functional web framework:

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));
}));
Copy after login

Here we'll show you how to run it, such as in Reactor Netty:

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

The last thing to do is give it a try:

$ curl &#39;http://localhost:8080/person/1&#39;
{"name":"John Doe","age":42}
Copy after login

There’s more below, so let’s dig deeper!

Core Components

I will introduce the framework by thoroughly explaining the core components: HandlerFunction, RouterFunction, and FilterFunction. These three interfaces, as well as all other types described in the article, can be found in the org.springframework.web.reactive.function package.

HandlerFunction

The starting point of this new framework is HandlerFunction<T>, which is basically Function> , where Request and Response are newly defined, immutable interface-friendly to provide JDK-8 DSL to the underlying HTTP messages. For building Response entities is a convenience build tool, very similar to what you see in ResponseEntity. The annotation corresponding to HandlerFunction is a method with @RequestMapping.

The following is an example of a simple "Hello World" processing function, which returns a response message with a 200 status and a body of String:

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

As we saw in the example above, handler functions are fully responsive by building on top of Reactor: they accept Flux, Mono, or any other corresponding stream Publisher as the response type.

One thing to note is that HandlerFunction itself has no side effects, because it returns the response instead of treating it as a parameter (see Servlet.service(ServletRequest,ServletResponse), this essence Above is BiConsumer). No side effects have many benefits: easy to test, write and optimize.

RouterFunction

Incoming requests are routed to the handler function with RouterFunction<T> (i.e. Function< Request, Optional) is routed to the handler function if it matches; otherwise an empty result is returned. 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 function with built-in processing functions. It may seem a bit lengthy, but don't worry: we'll find a way to make it shorter.

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

Generally, you do not need to write a complete routing method, but staticIntroduce RouterFunctions.route(), so that you can use the request judgment (RequestPredicate) (i.e. Predicate) and processing Method (HandlerFunction) creates a routing method. If the judgment is successful, the processing method is returned, otherwise an empty result is returned. The following is a rewrite of the above example using the route method:

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

You can (statically) import RequestPredicates.* to access commonly used predicates, matching based on path, HTTP method, content type, etc. With it, we can make helloWorldRoute simpler:

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

Combined function

Two routing functions can form a new routing function and route to any processing function : If the first function does not match, then execute the second one. You can combine two routing functions like this by calling 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"))));
Copy after login

If the path matches /hello-world, the above will respond with "Hello World", if it matches /the-answer, both Returns "42". If neither matches, an empty Optional is returned. Note that the combined routing functions are executed sequentially, so it makes sense to put the generic function before the concrete function.

你也可以组合要求谓词,通过调用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"))));
Copy after login

事实上,在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))));
Copy after login

方法引用

顺便说一句:到目前为止,我们已经编写了所有的处理函数作为内联的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"));
    }
}
Copy after login

注意,两个方法都有一个兼容了处理函数的标志。这允许我们使用方法引用:

DemoHandler handler = new DemoHandler(); // or obtain via DI
RouterFunction<?> route =
    route(GET("/hello-world"), handler::helloWorld)
    .and(route(GET("/the-answer"), handler::theAnswer));
Copy after login

FilterFunction

由路由函数映射的路径可以通过调用RouterFunction.filter(FilterFunction)进行过滤,其中FilterFunction本质上是BiFunction>。函数的处理器(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;
});
Copy after login

需要注意的是,要不要调用下一个处理程序是可选的。这在安全缓存方案中非常有用(如只在用户有足够权限的时候调用next)。

由于route是一个无限路由函数,因此我们知道接下来的处理程序会返回什么类型的响应信息。这就是为什么我们最终在我们的过滤器中用Response结束以及用Object响应body的原因。在处理程序类中,两种方法都返回Response,所以应该有可能有String响应主体。我们可以通过使用RouterFunction.andSame()来代替and()做到这一点。这种组合方法需要参数路由函数是相同的类型。例如,我们可以让所有的响应变成大写:

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));
  });
Copy after login

使用注解,相似的功能可以用@ControllerAdvice和/或ServletFilter来实现。

运行服务端

所有这一切都很好,但有一件事忘了:我们如何才能在实际的HTTP服务器中运行这些函数呢?答案勿庸置疑是通过调用另一个函数。你可以通过使用RouterFunctions.toHttpHandler()将路由函数转换成HttpHandler。HttpHandler是引进到Spring 5.0 M1的一个响应抽象:它允许你运行在各种响应运行时上:Reactor Netty、RxNetty、Servlet 3.1+,和Undertow。在这个例子中,我们已经表明了在Reactor Netty中运行route是怎么样的。对于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();
Copy after login

有一点要注意的是,上面的代码不依赖于Spring应用程序上下文。就像JdbcTemplate和其他Spring实用工具类,使用应用程序上下文是可选的:你可以在上下文中接通处理程序和路由函数,但它不是必需的。

还要注意的是,你也可以转换路由函数为HandlerMapping,以便它可以在DispatcherHandler中运行(可能需要有响应的@Controllers)。

结论

让我通过简短的总结来得出结论:

  • 处理函数通过返回响应处理请求。

  • 路由函数路由到处理函数,并且可以与其他路由函数组合。

  • 路由函数可以通过过滤器进行过滤。

  • 路由函数可以在响应的web运行时中运行。

The above is the detailed content of Spring5 new features-detailed code examples of functional web framework. For more information, please follow other related articles on the PHP Chinese website!

source:php.cn
Statement of this Website
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
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template