0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring MVC のハンドラメソッドの引数について

Last updated at Posted at 2025-02-27

Spring MVC のハンドラメソッド(コントローラークラスのメソッド)の引数について調べたメモ

バージョンは 6.2.3 時点の内容です。

結論からいうと HandlerMethodArgumentResolver というインターフェースがポイントになっており、自作のリゾルバを登録したい場合は WebMvcConfigurer#addArgumentResolvers を使えばよいです。

ハンドラメソッドの引数で利用できるもの

ハンドラメソッドは以下のように定義し、引数で様々な情報を受け取ることが可能です。

下記の例では @RequestBody アノテーションを使うことで、リクエストボディをメソッドの引数として受け取っています。

@RestController
@RequestMapping("/qiita")
public class QiitaController {

	@PostMapping("/hello")
	public QiitaResponse hello(@RequestBody QiitaRequest request) {
		var response = new QiitaResponse();
		response.setMessage("Hello %s!!".formatted(request.getName()));
		return response;
	}
}

これらの他にも

  • @RequestParam
  • @RequestHeader
  • HttpServletRequest
  • HttpSession

など様々なものを指定する事ができます。詳細はHandler Methodsを参照してください。

これらはどのような仕組みで解決されるのか

リクエストが DispatcherServlet に到達すると、リクエストのパスに基づいて実行対象のメソッドの特定が行われます。(省略)

その後 ServletInvocableHandlerMethod#invokeAndHandle を経由し、
InvocableHandlerMethod#invokeForRequestでハンドラメソッドの実行が行われます。

その中の InvocableHandlerMethod#getMethodArgumentValuesでハンドラメソッドのそれぞれの引数を解決しており、以下の部分で行われています。

InvocableHandlerMethod.java
if (!this.resolvers.supportsParameter(parameter)) {
  throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
  args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}

resolvers.supportsParameter, resolvers.resolveArgumentでは HandlerMethodArgumentResolverComposite#getArgumentResolver が呼び出されており、ここで 引数の定義に応じたHandlerMethodArgumentResolver が選択され、実際の値の作成が行われます。

HandlerMethodArgumentResolver

HandlerMethodArgumentResolverは以下の2つのメソッドが定義されています。

  • supportsParameter
  • resolveArgument

supportsParameter では、引数の情報が自身がサポートしているかどうかを返却し、
resolveArgument で実際の値の作成が行われます。

様々な実装クラスが存在します。ここで少し紹介します。

RequestResponseBodyMethodProcessor

@RequestBody アノテーションが付与された引数を解決する際に利用されます。

RequestParamMethodArgumentResolver

@RequestParam アノテーションが付与された引数を解決する際に利用されます。

AuthenticationPrincipalArgumentResolver

Spring Security の機能で @AuthenticationPrincipal アノテーションが付与された引数を解決する際に利用されます。認証したユーザーの情報を取得することができます。

HandlerMethodArgumentResolver の候補の一覧はどこで登録されるのか

HandlerMethodArgumentResolverComposite の中で候補の中から対象のリソルバの特定を行いますが、この候補はどこで登録されるのかを調べてみます。

これらは、アプリケーション起動時にRequestMappingHandlerAdapter#getDefaultArgumentResolvers で登録が行われています。また自分たちで独自で定義したリゾルバは後半部分の

RequestMappingHandlerAdapter.java
// Custom arguments
if (getCustomArgumentResolvers() != null) {
  resolvers.addAll(getCustomArgumentResolvers());
}

の部分で登録が行われます。
この getCustomArgumentResolvers で返ってきているものは
WebMvcConfigurer#addArgumentResolvers で登録したものが返されます。

例えば以下のように WebMvcConfigurer をBean定義しておけば 自作の MyArgResolver をリゾルバの一覧に追加することができます。

@Bean
WebMvcConfigurer myWebMvcConfigurer() {
  return new WebMvcConfigurer() {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
      resolvers.add(new MyArgResolver());
    }

  };
}

HandlerMethodArgumentResolver の実装例

上記の例で登場した MyArgResolver は以下のような実装です。

ハンドラメソッドの引数に @MyArg アノテーションが付与されていると固定の文字列を渡す実装にしています。

MyArgResolver.java
public class MyArgResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
        // 引数に MyArgアノテーションが付与されているか?
		return parameter.hasParameterAnnotation(MyArg.class);
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        // 常に同じ文言を返すサンプル
		return "My arg !!!!!";
	}
}
MyArg.java
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyArg {

}

利用箇所のハンドラメソッドでは引数に @MyArg アノテーションを付与します。

QiitaController.java
@RestController
@RequestMapping("/qiita")
public class QiitaController {

	@PostMapping("/hello")
	public QiitaResponse hello(@RequestBody QiitaRequest request, @MyArg String value) {
		System.out.println(value); // My arg !!!!!
		
		var response = new QiitaResponse();
		response.setMessage("Hello %s!!".formatted(request.getName()));
		return response;
	}
}

独自のリゾルバが必要になりそうな場面ですが、個人的にはBean定義されているものはControllerクラスにDIすれば取得できるのでわざわざリゾルバを作る必要は無いと思います。
これらが必要になる例としてはリクエストに紐づいた情報を取得するのに利用するのがいいかなと思っています。

実はハンドラメソッドの戻り値も似たような方法で解決されている。

今回はハンドラメソッドの引数について説明しましたが、実はハンドラメソッドの戻り値についてもこれと似た方法で HandlerMethodReturnValueHandler というインターフェースを使って解決が行われています。

まとめ

ハンドラメソッドの引数がどのような仕組みで解決され、また自作で定義するにはどのような方法で行うかについての説明をしました。

引数の解決の方法はライブラリを自作する際にも活用できそうなので、覚えておくとSpring以外の場面でも役に立つかもしれません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?