例
サンプルプログラムを選択して開始します。以下は、人物オブジェクトを公開するためのリアクティブ リソース ライブラリです。この応答性の高いリソース ライブラリは、Flux
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);
最後に行うことは、try リクエストを行うことです:
$ curl ' {"name":"John Doe","age":42}
上記の紹介では多くの内容をカバーしています。さらに詳しく見てみましょう!
コアコンポーネント
HandlerFunction、RouterFunction、FilterFunction などのコアコンポーネントを順番に紹介することで、フレームワーク全体を紹介します。これら 3 つのインターフェイスとこの記事の他のタイプは、 org.springframework.web.reactive.function パッケージにあります。
処理関数
新しいフレームワークの開始点は HandlerFunction
以下はステータス200、本文を文字列としたメッセージを返す「Hello World」の処理メソッドです。
HandlerFunction<String> helloWorld = request -> Response.ok().body(fromObject("Hello World"));
上で述べたように、Reactor 上に構築された処理メソッドは完全にリアクティブであり、Flux、Mono、またはその他の対応するストリーム (Reactive Streams) のパブリッシャーを戻り値の型パラメーターとして受け入れることができます。
応答をパラメーターとしてではなく戻り値として使用するため、処理メソッド自体には副作用がないことに注意してください (本質的に BiConsumer
ルーティング関数
インバウンドリクエストは、RouterFunction
以下はインライン処理方式を含むルーティング方式の例です。これは少し冗長に見えますが、後で合理化するので心配しないでください。
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() を静的に導入すると、リクエスト述語 (つまり Predicate
RouterFunction<String> helloWorldRoute = RouterFunctions.route(request -> request.path().equals("/hello-world"), request -> Response.ok().body(fromObject("Hello World")));
RequestPredicate.*を静的に導入した後は、一致するパス、HTTPメソッド、コンテンツタイプなど、一般的に使用される判定式を使用できます。このようにして、上記の例はより合理化されます:
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 并行)中运行了。
结论
これで Spring の新しい機能 Web フレームワークの紹介は終わりです。簡単に要約します:
処理関数は応答を作成することでリクエストを処理します、
ルーター関数は処理関数に接続でき、他のルーター関数と共有できます、
ルーター関数はフィルター関数でフィルタリングできます、
ルーター機能は次のことができます。リアクティブ Web 操作メカニズム上で実行されます。