LoginSignup
8
7

More than 5 years have passed since last update.

Spring MVCでクエリパラメータ名をhoge[]みたいにすると果てる

Posted at

メモです。

背景

@RestController で REST な API をつくって、以下みたいなリクエストを受け取るとする。

GET /foo?hoge[]=1&hoge[]=2

jQuery の $.param とかで、$.param({hoge: [1, 2]}) すると上記のパラメータ文字列になるので良くある形式なのだと思う。

ところが、SpringMVC で上記を @ModelAttribute でうけ取ろうとすると NumberFormatException になって InternalServerError になる。
@RequestParam なんじゃないか?という話もあるけど、@Validated によるバリデーションをかけつつ、DTO にマップしたいので @ModelAttribute を使う。

DTO への値設定時に、パラメータ名をプロパティ名とみなして設定するのだけど、[] は配列アクセスとみなして、[] の間にインデクスが入っている前提で処理される。実際にはなにも入っていないので、空文字を整数に変換しようとして NumberFormatException になる。
あと、対象プロパティが Set<...> の場合も、InvalidPropertyException が発生したりする。

@ExceptionHandlerNumberFormatException を拾えるが、発生状況がクエリパラメータの処理時に限定できないので、本当に ISE の場合も気づけなくなってしまう。

思うに、@ModelAttribute による値のバインドは、REST API で使われることは想定していない。普通のフォームからの POST とかみたいに、呼び出し元もおなじシステム(≒開発者が入力値をコントロール可能)であることを前提としているのかと思う。

ただ、REST API のときに、適当なクエリパラメータで ISE おこせるとかイケていないので、とりあえず何とかする。

方法

SpringMVC ではコントローラメソッドの引数を解決するための部品として HandlerMethodArgumentResolver というのがある。@ModelAttribute だったり BindingResult だったり、どれもこの仕組でコントローラメソッドの引数に提供されている。

この HandlerArgumentResolver に自作のものを追加し、パラメータ処理時の NumberFormatException に限り、違う例外でラップして再送出する。

@Configuration
public class XxxxConfiguration {
    @Bean
    public WebMvcConfigurer getConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolver) {
                argumentResolver.add(new HandlerMethodArgumentResolver() {
                    private final HandlerMethodArgumentResolver underlying = new ServletModelAttributeMethodProcessor(false);

                    @Override
                    public boolean supportsParameter(MethodParameter parameter) {
                        // Default の ArgumentResolver の方が優先順位が高いので、ModelAttribute.class を目印にすると、デフォルトの方で処理されてしまう
                        return parameter.hasParameterAnnotation(なんか自作のアノテーション.class);
                    }

                    @Override
                    public Object resolveArgument(MethodParameter parameter, .....) throws Exception {
                        try {
                            return underlying.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
                        } catch (NumberFormatException ex) {
                            throw new なんか自作の例外(ex);
                        }
                    }
                });
            }
        };
    }
}

たぶんこんなんじで、なんとかなる。

8
7
1

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
8
7