Heim > Artikel > Web-Frontend > Neu in Spring 5: Functional Web Framework
Beispiel
Wir wählen ein Beispielprogramm zum Starten aus. Nachfolgend finden Sie eine reaktive Ressourcenbibliothek zum Offenlegen von Person-Objekten. Diese reaktionsfähige Ressourcenbibliothek ähnelt der herkömmlichen nicht reaktionsfähigen Ressourcenbibliothek, mit der Ausnahme, dass Flux
public interface PersonRepository { Mono<Person> getPerson(int id); Flux<Person> allPeople(); Mono<Void> savePerson(Mono<Person> person);}
Hier stellen wir die Verwendung vor das neue funktionale Web-Framework zum Offenlegen der Ressourcenbibliothek:
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)); }));
Hier stellen wir vor, wie man es ausführt, hier ist ein Beispiel von Reactor Netty:
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); HttpServer server = HttpServer.create("localhost", 8080); server.startAndAwait(adapter);
Das Letzte, was zu tun ist ist eine Versuchsanfrage zu stellen:
$ curl ' {"name":"John Doe","age":42}
Die obige Einführung deckt viele Inhalte ab, lasst uns tiefer gehen!
Kernkomponenten
Ich werde das gesamte Framework vorstellen, indem ich nacheinander Kernkomponenten wie HandlerFunction, RouterFunction und FilterFunction einführe. Diese drei Schnittstellen sowie andere Typen in diesem Artikel sind im Paket org.springframework.web.reactive.function zu finden.
Handhabungsfunktion
Der Ausgangspunkt des neuen Frameworks ist HandlerFunction
Das Folgende ist die Verarbeitungsmethode von „Hello World“, die eine Nachricht mit dem Status 200 und dem Text als Zeichenfolge zurückgibt.
HandlerFunction<String> helloWorld = request -> Response.ok().body(fromObject("Hello World"));
Wie oben sind die auf Reactor aufgebauten Verarbeitungsmethoden vollständig reaktiv. Sie können Herausgeber von Flux, Mono oder anderen entsprechenden Streams (Reactive Streams) als Rückgabeparameter akzeptieren.
Es ist zu beachten, dass die Verarbeitungsmethode selbst keine Nebenwirkungen hat, da sie die Antwort als Rückgabewert und nicht als Parameter verwendet (vergleiche Servlet.service (ServletRequest, ServletResponse), das im Wesentlichen BiConsumer ist
Routing-Funktion
Eingehende Anfragen werden von RouterFunction
Das Folgende ist ein Beispiel für eine Routing-Methode, einschließlich einer Inline-Verarbeitungsmethode. Das sieht etwas überflüssig aus, keine Sorge, wir werden es später optimieren.
RouterFunction<String> helloWorldRoute = request -> { if (request.path().equals("/hello-world")) { return Optional.of(r -> Response.ok().body(fromObject("Hello World"))); } else { return Optional.empty(); } };
Im Allgemeinen müssen Sie keine vollständige Routing-Methode schreiben, sondern RouterFunctions.route() statisch einführen, sodass Sie das Anforderungsprädikat (RequestPredicate) (d. h. Predicate
RouterFunction<String> helloWorldRoute = RouterFunctions.route(request -> request.path().equals("/hello-world"), request -> Response.ok().body(fromObject("Hello World")));
Nach der statischen Einführung von RequestPredicates.* können Sie häufig verwendete Beurteilungsausdrücke verwenden, z. B. passende Pfade, HTTP-Methoden, Inhaltstypen, usw. Auf diese Weise wird das obige Beispiel schlanker:
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> 结束, 那样它就会可能有一个 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)); });
使用注解的话,类似的功能可以使用 @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 并行)中运行了。
结论
Damit ist die Einführung in das neue funktionale Web-Framework von Spring abgeschlossen. Lassen Sie mich kurz zusammenfassen:
Die Verarbeitungsfunktion verarbeitet die Anfrage, indem sie eine Antwort gibt,
Die Router-Funktion kann mit der Verarbeitungsfunktion verbunden und mit anderen Router-Funktionen geteilt werden,
Routerfunktionen können durch Filterfunktionen gefiltert werden, und
Routerfunktionen können in einem reaktiven Netzwerkbetriebsmechanismus ausgeführt werden.