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を使ってプロジェクトを作成します。
設定は以下のように

f:id:yuya_hirooka:20210926121552p:plain

コントローラーを作って登録する

リクエストで名前を受け付けて、挨拶を返すコントローラーを作成します。
まずは、リクエストとレスポンスを受ける用の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を使ってRequestMappingHandlerMappingGreetingControllerを受け取ってます。
また、RequestMappingInfoを使ってリクエストのマッピング設定を記述し、その定義とコントローラーのリクエストをハンドルするMethodオブジェクト、そしてコントロラー本体をRequestMappingHandlerMappingに登録しています。
この際に色々処理を入れてロジックでハンドラーの定義をごにょごにょできるみたいですね。

さっくり触っただけですが、ほぼ同じような記述でMVC側も動くみたいです。
あと、Recordクラスはほんとに今まで通りな感じで使えましたね。
関数エンドポイントの方でも今までと全くおんなじように利用できました。