Spring MVC覚書
Spring MVC 4.3.11.RELEASE
Spring MVCのControllerで、ハンドラメソッド引数に独自オブジェクトをバインドしたい場合は、以下のいずれかの方法を採ると思いますが、
- フォームオブジェクトを利用する
-
HandlerMethodArgumentResolver
実装クラスを利用する
HandlerMethodArgumentResolver
実装クラスを利用する場合、バインド先の独自オブジェクトはフォームオブジェクトのように自動的にSpring MVCのModel
に登録されず、入力チェックもされません。
Model
に登録されないのでは、何度もバインドするには向かないし、何より使用感が変わってきます。
入力チェックされないのでは、システム情報のような安全なデータしかバインドできません。
ということで、実現したい要件によってはこれらを実装しなければならなくなり、それなりにSpring MVCを理解していないと正しく実装できず、後々トラブルになったりしそうです。
ModelAttributeMethodProcessor
クラスを利用する
ModelAttributeMethodProcessor
クラスは、HandlerMethodArgumentResolver
実装クラスで@ModelAttribute
を付与した引数へのバインドを処理します。
バインドしたい独自オブジェクトが上記のような標準的な使い方を想定しているなら、このクラスをオーバーライドすると、簡単に実現することができます。
実装例を見ていきますが、ここではリクエストヘッダをまとめてバインドする独自オブジェクトを実装し、入力チェックを適用します。
独自オブジェクト
@Getter
@Setter
public class RequestHeader {
@SupportedBrowser("FireFox") // 入力チェックアノテーション
private String userAgent;
private String accept;
private String acceptLanguage;
private String acceptEncoding;
}
独自オブジェクトには、リクエストヘッダをバインドするプロパティを定義し、ユーザエージェントにはFireFoxのみ許可するようにしてみます。(入力チェックはテキトーですw)
ModelAttributeMethodProcessor
拡張クラス
public class RequestHeaderModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
// コンストラクタ
// ここでは、`@ModelAttribute`アノテーションが不要(true)なことを親コンストラクタに渡しています
public RequestHeaderModelAttributeMethodProcessor() {
super(true);
}
// サポートする型
// オーバーライドして、さきほど作成した独自オブジェクトを対象にしています
@Override
public boolean supportsParameter(MethodParameter parameter) {
return RequestHeader.class.isAssignableFrom(parameter.getParameterType());
}
// リクエストパラメータをバインドする
// オーバーライドして、任意のバインドロジックを定義します
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValue("userAgent", request.getHeader("user-agent"));
pvs.addPropertyValue("accept", request.getHeader("accept"));
pvs.addPropertyValue("acceptLanguage", request.getHeader("accept-language"));
pvs.addPropertyValue("acceptEncoding", request.getHeader("accept-encoding"));
binder.bind(pvs);
}
}
bindRequestParameters
メソッドでは、Controllerの@InitBinder
メソッドと同じ方法でバインドする値とバインド先プロパティを指定します。
バインド先のオブジェクト(RequestHeader
オブジェクト)は親クラスで生成してくれるので、ここでは各プロパティへのバインド方法を指定するだけでOKです。
なお、ここでは扱いませんが、ArgumentResolverはBeanとしてSpring MVCに登録するので、任意の@Service
などをインジェクションできます。複雑なロジックやDB接続はそちらに実装すると良いですね。
以下のように、作成したArgumentResolverを適用します。
- Java Config
@Configuration
public class MyWebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new RequestHeaderModelAttributeMethodProcessor());
}
}
- XML
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="x.x.RequestHeaderModelAttributeMethodProcessor"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
Controller
@Controller
public class SampleController {
// ハンドラメソッド
// フォームオブジェクトと同じ方法でバインドできます
@GetMapping("sample")
public String handle(@Validated RequestHeader header, BindingResult result, Model model) {
// 入力チェック
// フォームオブジェクトと同じ方法でチェック結果を取得できます
if (result.hasErrors()) {
return "sample/failure";
}
// Modelからも取得できます
RequestHeader headerFormModel = model.asMap().get("requestHeader");
return "sample/success";
}
}
Controllerでは、フォームオブジェクトと同じように引数として定義するだけです。
Model
に登録されるので、当然@SessionAttribute
などにも対応できるはず。
まとめ
ModelAttributeMethodProcessor
クラスを利用することで、入力チェックなどを自分で実装することなく、バインドだけに集中して簡単に実装することができました。
せっかくSpring MVCが提供してくれる仕組みがあるので、なるべく標準的に作りたいなーと思ったのですが、意外と情報がなくてSpringのソースコードを見ながら作りましたorz...
誰かの参考になると嬉しいです!