Comme mentionné hier dans le blog de Juergen, la deuxième étape de Spring 5.0 est l'introduction d'un nouveau framework Web fonctionnel. Dans cet article, nous donnerons plus d’informations sur ce framework.
Commençons par quelques extraits d'un exemple d'application. Vous trouverez ci-dessous le référentiel de réponses exposant l'Person
objet . Très similaire à la base d'informations traditionnelle et non réactive, sauf qu'elle renvoie Flux8abf60ac54173a2785e603c7a1f95b4e tandis que la base traditionnelle renvoie Liste8abf60ac54173a2785e603c7a1f95b4e, et où Mono8abf60ac54173a2785e603c7a1f95b4e
est personne retournée. Mono903bf37051cf83cbd9686768ac0189ae est utilisé comme indicateur de fin : indiquant quand la sauvegarde est terminée. Pour plus d'informations sur les types de réacteurs, consultez cet article de blog.
public interface PersonRepository { Mono<Person> getPerson(int id); Flux<Person> allPeople(); Mono<Void> savePerson(Mono<Person> person); }
Voici comment nous exposons le référentiel avec le nouveau framework Web fonctionnel :
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)); }));
Voici comment nous l'exécutons, par exemple dans Reactor Netty :
HttpHandler httpHandler = RouterFunctions.toHttpHandler(route); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); HttpServer server = HttpServer.create("localhost", 8080); server.startAndAwait(adapter);
La dernière chose à faire est d'essayer :
$ curl 'http://localhost:8080/person/1' {"name":"John Doe","age":42}
Il y en a plus ci-dessous, alors creusons plus profondément !
Je présenterai le cadre en expliquant en détail les composants de base : HandlerFunction
,RouterFunction
et FilterFunction
. Ces trois interfaces, ainsi que tous les autres types décrits dans l'article, peuvent être trouvés dans le package org.springframework.web.reactive.function.
HandlerFunction
Le point de départ de ce nouveau framework est HandlerFunction8742468051c85b06f0a0af9e3e506b5c
, qui est essentiellement FunctionRequest
et Réponse Il s'agit d'une interface conviviale nouvellement définie qui fournit le JDK-8 DSL aux messages HTTP sous-jacents. Pour créer des entités Response
, il existe un outil de construction pratique, très similaire à ce que vous voyez dans ResponseEntity. Correspondant à l'annotation HandlerFunction
est une méthode avec @RequestMapping.
Ce qui suit est un exemple d'une fonction de traitement simple "Hello World", qui renvoie un message de réponse avec un statut 200 et un corps de String :
HandlerFunction<String> helloWorld = request -> Response.ok().body(fromObject("Hello World"));
Comme nous l'avons vu dans l'exemple ci-dessus, les fonctions de gestionnaire sont entièrement réactives en s'appuyant sur Reactor : elles acceptent Flux, Mono ou tout autre flux correspondant Publisher
comme type de réponse.
Une chose à noter est que HandlerFunction
lui-même n'a aucun effet secondaire car il renvoie la réponse au lieu de la traiter comme un paramètre (voir Servlet.service(ServletRequest,ServletResponse), qui est essentiellement BiConsumercda33a95b3b41a85d85f5c063c76b18c). Aucun effet secondaire présente de nombreux avantages : facile à tester, à écrire et à optimiser.
RouterFunction
Les requêtes entrantes sont routées vers la fonction de gestionnaire avec RouterFunction8742468051c85b06f0a0af9e3e506b5c
(c'est-à-dire Functionef8351df79cafe29834dcd2972fa7086) Créez une méthode de routage avec la méthode de traitement (HandlerFunction). Si le jugement réussit, la méthode de traitement est renvoyée, sinon un résultat vide est renvoyé. Voici l'exemple ci-dessus réécrit à l'aide de la méthode route :
RouterFunction<String> helloWorldRoute = RouterFunctions.route(request -> request.path().equals("/hello-world"), request -> Response.ok().body(fromObject("Hello World")));
Vous pouvez (statiquement) importer RequestPredicates.* pour accéder aux prédicats couramment utilisés, à la correspondance basée sur le chemin, la méthode HTTP, le type de contenu, etc. Avec lui, nous pouvons rendre helloWorldRoute plus simple :
RouterFunction<String> helloWorldRoute = RouterFunctions.route(RequestPredicates.path("/hello-world"), request -> Response.ok().body(fromObject("Hello World")));
Fonction de combinaison
Deux fonctions de routage peuvent former une nouvelle fonction de routage, routage vers l'une ou l'autre Fonction de gestion : Si la la première fonction ne correspond pas, puis la seconde est exécutée. Vous pouvez combiner deux fonctions de route comme celle-ci en appelant 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"))));
Si le chemin correspond à /hello-world, ce qui précède répondra par "Hello World", s'il correspond à /the- réponse, alors "42" sera renvoyé en même temps. Si aucune des deux ne correspond, un Optional
vide est renvoyé. Notez que les fonctions de routage combinées sont exécutées séquentiellement, il est donc logique de placer la fonction générique avant la fonction concrète.
你也可以组合要求谓词,通过调用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));
FilterFunction
由路由函数映射的路径可以通过调用RouterFunction.filter(FilterFunction469df1b70b2914a3841e0404ec3e3d5d)进行过滤,其中FilterFunction43ca9160a1fbc6e1e17f36fac17e2094本质上是BiFunction
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
是一个无限路由函数,因此我们知道接下来的处理程序会返回什么类型的响应信息。这就是为什么我们最终在我们的过滤器中用Response6b3d0130bba23ae47fe2b8e8cddf0195结束以及用Object
响应body的原因。在处理程序类中,两种方法都返回Responsef7e83be87db5cd2d9a8a0b8117b38cd4,所以应该有可能有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中运行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();
有一点要注意的是,上面的代码不依赖于Spring应用程序上下文。就像JdbcTemplate
和其他Spring实用工具类,使用应用程序上下文是可选的:你可以在上下文中接通处理程序和路由函数,但它不是必需的。
还要注意的是,你也可以转换路由函数为HandlerMapping,以便它可以在DispatcherHandler
中运行(可能需要有响应的@Controllers)。
让我通过简短的总结来得出结论:
处理函数通过返回响应处理请求。
路由函数路由到处理函数,并且可以与其他路由函数组合。
路由函数可以通过过滤器进行过滤。
路由函数可以在响应的web运行时中运行。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!