Spring MVC 4.1 No. 011 受け取る型のチェック
今までパラメータはすべてStringで受けていたので、型変換のエラー等は発生していませんでした。ここでは、String型以外の型でパラメータを受け取りどのような動作になるかを確認していきます。
最初にパラメータを受けつ取るクラスを作成します。price変数をIntegerにしておきどのように動作するのかを確認します。
package com.example.spring.controller.c011; public class C011Model { private String name; private Integer price; // setter、getterは省略 }
Controllerクラスです。ModelAttriubteでC011Modelのパラメータを受け取ります。また、C011Modelへのパラメータの割り当ての際のエラーを取得するため、C011の次の引数にBindingResultクラスを指定します。BIndingResultクラスは、バインドされるクラスの次の引数にないといけません。
package com.example.spring.controller.c011; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/c011") public class C011Controller { @RequestMapping("/checkType") public String checkType(@ModelAttribute C011Model c011Model, BindingResult errors) { return "c011/checkType"; } }
最後に表示用のJSPです。
<%@page contentType="text/html; charset=utf-8" %><%-- --%><!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>サンプル</title> </head> <body> バインディングエラー <form:errors path="c011Model.*" /><br> c011Model.nameの値は <c:out value="${c011Model.name}" /><br> c011Model.priceの値は <c:out value="${c011Model.price}" /><br> </body> </html>
このプログラムで「/c011/checkType?name=書籍&price=文字列」というリクエストを投げると次の様な出力になります。
バインディングエラー Failed to convert property value of type java.lang.String to required type java.lang.Integer for property price; nested exception is java.lang.NumberFormatException: For input string: "文字列" c011Model.nameの値は c011Model.priceの値は
c011Model.priceには文字列のためバインディングができずnullが入っています。また、バインディング失敗のデフォルトのエラーメッセージが表示されます。
このままだと、非常にわかりにくいのでデフォルトのエラーメッセージを変更します。
まず、spring-context.xmlにメッセージソースの設定を追加します。
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:/messages" /> </bean>
そして、指定した名前+「.properties」ファイルを作成します。今回は型変換のエラーのメッセージだけを記載します。
typeMismatch.java.lang.Integer=整数で入力してください。
このメッセージは、DefaultMessageCodesResolverのJavaDocに記載のある通り、
1.: code + "." + object name + "." + field
2.: code + "." + field
3.: code + "." + field type
4.: code
の順でメッセージが解決されます。
この状態で先ほどと同じデータを送信すると上記のメッセージになります。
ここで、メッセージを以下のように変更します。
typeMismatch.java.lang.Integer={0}は整数で入力してください。
ここで、また先程のリクエストを送信すると今回は{0}の部分がフィールド名になり、
priceは整数で入力してください。
というメッセージにあります。このpriceの部分はフィールド名に対応していて、この名前も置換が可能です。
メッセージソースに、フィールド名の値をキーとして登録します。
price=価格
そうすることで、最終的には以下のメッセージになります。
価格は整数で入力してください。
確認用のテストケースは次のとおりです。
package com.example.spring.controller.c011; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; import java.util.List; import java.util.Map; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.ModelAndView; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/spring-context.xml" }) public class C011ControllerTest { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setup() { mockMvc = webAppContextSetup(wac).build(); } @Test public void checkTypeへのGET_priceが1000() throws Exception { MvcResult mvcResult = mockMvc .perform(get("/c011/checkType").param("price", "1000")) .andExpect(status().isOk()) .andExpect(view().name("c011/checkType")) .andExpect(model().hasNoErrors()) .andExpect(model().attributeExists("c011Model")).andReturn(); Map<String, Object> model = mvcResult.getModelAndView().getModel(); Object c011ModelObject = model.get("c011Model"); assertThat(c011ModelObject, is(notNullValue())); assertThat(c011ModelObject, is(instanceOf(C011Model.class))); C011Model c011Model = (C011Model) c011ModelObject; assertThat(c011Model.getPrice(), is(1000)); } @Test public void checkTypeへのGET_priceがabc() throws Exception { MvcResult mvcResult = mockMvc .perform(get("/c011/checkType").param("price", "abc")) .andExpect(status().isOk()) .andExpect(view().name("c011/checkType")) .andExpect(model().hasErrors()) .andExpect(model().errorCount(1)) .andExpect( model().attributeHasFieldErrors("c011Model", "price")) .andExpect(model().attributeExists("c011Model")).andReturn(); // パラメータのチェック ModelAndView mav = mvcResult.getModelAndView(); Map<String, Object> model = mav.getModel(); Object c011ModelObject = model.get("c011Model"); assertThat(c011ModelObject, is(notNullValue())); assertThat(c011ModelObject, is(instanceOf(C011Model.class))); C011Model c011Model = (C011Model) c011ModelObject; assertThat(c011Model.getPrice(), is(nullValue())); // エラーメッセージのチェック Object object = mav.getModel().get( "org.springframework.validation.BindingResult.c011Model"); assertThat(object, is(not(nullValue()))); assertThat(object, is(instanceOf(BindingResult.class))); BindingResult bindingResult = (BindingResult) object; // エラーのあるフィールドの取得 List<FieldError> list = bindingResult.getFieldErrors("price"); assertThat(list, is(not(nullValue()))); assertThat(list.size(), is(1)); // 詳細なエラーチェック FieldError fieldError = list.get(0); assertThat(fieldError.getCode(), is("typeMismatch")); // エラーメッセージのパラメータ Object[] args = fieldError.getArguments(); assertThat(args.length, is(1)); assertThat(args[0], is(instanceOf(DefaultMessageSourceResolvable.class))); DefaultMessageSourceResolvable dmr = (DefaultMessageSourceResolvable) args[0]; assertThat(dmr.getCode(), is("price")); } }
ソースは https://github.com/kuwalab/spring-mvc41 にあります。タグ011が今回のサンプルです。
まとめ http://kuwalab.hatenablog.jp/entry/spring
最初 http://kuwalab.hatenablog.jp/entry/spring_mvc41/001
前回 http://kuwalab.hatenablog.jp/entry/spring_mvc41/010
次回 http://kuwalab.hatenablog.jp/entry/spring_mvc41/012