Springのアノテーション付きコントローラーのメソッドを明示的にハンドラーとして登録する
はじめに
Spring のWebFluxのドキュメント眺めていたら明示的なハンドラーの登録という項目が合ってなんとなく気になったので動かしてみたいと思います。
あとは、Java 16から入ってるRecordsクラスを試してみたい気持ちもあり。
ちなみに明示的なハンドラーの登録はMVCの方でも使えるみたいです。
Webfluxでは大きく以下の2つのハンドラー定義の方法をサポートしています(MVCでも同様)。
上記のやり方でニーズを満たせない場合、より高度なハンドラーの登録ロジックが必要な場合(動的なハンドラー登録、異なるURLで同一ハンドラーを登録など)明示的なハンドラーの登録を行なうことができます。
また、この方法はアノテーション付きコントローラーを使っている際に利用可能なようです。
やってみる
環境
$ java --version openjdk 17 2021-09-14 OpenJDK Runtime Environment (build 17+35-2724) OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing) $ mvn -v Apache Maven 3.6.3 Maven home: /usr/share/maven Java version: 17, vendor: Oracle Corporation, runtime: /home/******/.sdkman/candidates/java/17-open Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-86-generic", arch: "amd64", family: "unix"
プロジェクトの作成
Spring Initializrを使ってプロジェクトを作成します。
設定は以下のように
コントローラーを作って登録する
リクエストで名前を受け付けて、挨拶を返すコントローラーを作成します。
まずは、リクエストとレスポンスを受ける用のRecordクラスを作成します。
NameRequest.java
public record NameRequest(String value) {}
GreetingResponse
public record GreetingResponse(String value) { public GreetingResponse(String value){ this.value = String.format("Hello %s!", value); } }
次にコントローラーを実装します。
GreetingController.java
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @RestController public class GreetingController { public Mono<GreetingResponse> hello(@RequestBody Mono<NameRequest> name) { return name.map(it -> new GreetingResponse(it.value())); } }
@〇〇Mappting
みたいなのを付けてない以外は普通のコントローラーですね。
また、Recordクラスは今まで定義してたDTOみたいな感じで扱えるみたいですね。
このコントローラーのメソッドをハンドラーマッピングとして登録します。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.reactive.result.method.RequestMappingInfo; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; import reactor.core.publisher.Mono; import java.lang.reflect.Method; @Configuration public class Config { @Autowired public void handlerSetting(RequestMappingHandlerMapping mapping, GreetingController handler) throws NoSuchMethodException { RequestMappingInfo info = RequestMappingInfo .paths("/hello").methods(RequestMethod.POST).build(); Method method = GreetingController.class.getMethod("hello", Mono.class); mapping.registerMapping(info, handler, method); } }
@Autowired
を使ってRequestMappingHandlerMapping
とGreetingController
を受け取ってます。
また、RequestMappingInfoを使ってリクエストのマッピング設定を記述し、その定義とコントローラーのリクエストをハンドルするMethod
オブジェクト、そしてコントロラー本体をRequestMappingHandlerMapping
に登録しています。
この際に色々処理を入れてロジックでハンドラーの定義をごにょごにょできるみたいですね。
さっくり触っただけですが、ほぼ同じような記述でMVC側も動くみたいです。
あと、Recordクラスはほんとに今まで通りな感じで使えましたね。
関数エンドポイントの方でも今までと全くおんなじように利用できました。