Spring WebMVCをやってみる (13) - @ValidとBindingResult -
簡単に入力検証なのを実現したいのであれば@ValidとBindingResultを利用する事で出来るらしい。色々突っかかる所があるのであくまで暫定的ではあるけどメモっておく
必要なライブラリ
Spring Frameworkには@Validではなく、@Validatedっていうアノテーションがある。今回やるのは前者な方で後者に関しては今後やる可能性もあるのでとりあえずは「アノテーション「@Validated」と「@Valid」」を参考されたしという事で(ry
で本題の必要なライブラリに関して、javax.validationとその実装であるライブラリが必要。んまぁ後者のその実装に関してはHibernate Validatorがサポートされているのでそれを使う。という事でそのライブラリ参照を追加する
repositories {
mavenCentral()
}
dependencies {
compile "javax.servlet:servlet-api:2.5"
compile "org.springframework:spring-webmvc:3.2.5.RELEASE"
// 追加
compile "org.hibernate:hibernate-validator:5.0.2.Final"
testRuntime "javax.servlet:jstl:1.2"
testCompile "junit:junit:4.11"
testCompile "org.hamcrest:hamcrest-all:1.3"
testCompile "org.springframework:spring-test:3.2.5.RELEASE"
}
SampleForm.java
package sample;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotEmpty;
public class SampleForm implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
@NotNull
@NotEmpty(message = "{error.empty.sample.name}")
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@NotEmptyで引数を指定しているが、引数を指定しない場合にはorg.hibernate.validator.constraints.NotEmpty.messageというメッセージリソースなプロパティが参照される。これに関しては後術するので(ry
ValidationMessages_ja.properties
#org.hibernate.validator.constraints.NotEmpty.message={sample_form.name}が未入力です
error.empty.sample_form.name="{sample_form.name}"が入力されてません
sample_form.name=名前
SampleController.java
package sample;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.AbstractView;
@Controller
@RequestMapping("/sample")
public class SampleController {
@ModelAttribute("sample_form")
public SampleForm setupForm() {
SampleForm sample = new SampleForm();
return sample;
}
@RequestMapping
public String index() {
return "index";
}
@RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView save(
@Valid @ModelAttribute("sample_form") final SampleForm sample,
BindingResult bindingResult) {
if (bindingResult.hasErrors())
return new ModelAndView("index");
return new ModelAndView(new AbstractView() {
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.getWriter().print(sample.getName());
}
});
}
}
というようにフォーム検証を行うアノテーションを付与して@ModelAttributeで送信したデータとのオブジェクトのマッピングを行い、そのバインディング上でエラーがあるかをチェックすれば良い模様。んで正常な場合だと普通に画面に入力したデータが表示されるだけだけど、エラーな場合にはindexなビューなレスポンスをレンダリングする的な感じかと
でJSPでは
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<spring:hasBindErrors name="sample_form">
<c:forEach items="${errors.fieldErrors}" var="error">
<spring:message message="${error}" />
</c:forEach>
</spring:hasBindErrors>
<form:form action="/swmvc/sample/save.action" method="post" modelAttribute="sample_form">
<form:input path="name" />
<form:errors path="name" />
<input type="submit" value="save" />
</form:form>
というようにフォームエラーがある場合には表示させるのは
- <spring:hasBindErrors>でチェックして<spring:message>で表示させる
- <form:errors path=""/>で表示させる
というような方法がある模様。まぁテストも書きましょうって事で
SampleControllerTest.java
package swmvc;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
public class SampleControllerTest extends AbstractTestCase {
@Test
public void test_save() throws Exception {
mock.perform(post("/sample/save").param("name", "hoge"))
.andExpect(status().isOk())
.andExpect(model().hasNoErrors())
.andExpect(content().string(is("hoge")));
}
@Test
public void test_save_fail() throws Exception {
mock.perform(post("/sample/save"))
.andExpect(status().isOk())
.andExpect(view().name(is("index")))
.andExpect(model().hasErrors())
.andExpect(model().attributeHasFieldErrors(
"sample_form", // model attribute name?
"name" // property
));
}
}
終わり。ただ書いたやり方だと、フォーム検証でエラーが発生したらindexビューをレンダリングするようになっているのでリロードするとPOSTなデータを再送信するような方式になってしまうのでそこら辺の対処法は後日書く
余談: javax.validationが読み込むメッセージリソースとSpringが利用するメッセージリソースを共通化する
上記でも書いたように検証エラーメッセージ等はValidationMessages_[locale].properties等から読まれる訳ですけど、Springが使うMessageSourceと共通化する形で
<bean
id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
p:basename="classpath:applicationMessages" />
で指定しても、検証メッセージで使用するプロパティはValidationMessagesから参照して無かったらデフォルトを出すっていう形になる模様。なので上記だけ設定してもapplicationMessages.propertiesからは検証メッセージなプロパティが作用しない状態になる模様。その場合は
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<context:component-scan base-package="sample" />
<mvc:annotation-driven validator="validator" />
<bean
id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="validationMessageSource" ref="messageSource" />
</bean>
<bean
id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource"
p:basename="classpath:applicationMessages" />
</beans>
っていうようにLocalValidatorFactoryBeanなvalidationMessageSourceな設定が別途で必要な模様。但し、これSpring3.2.x系で動かすとSupport Bean Validation 1.1 (JSR-349)っていうエラーが起きた。
3.2.x系で動かす場合には、 https://jira.springsource.org/browse/SPR-10466 を参考にする事で同様な事を利用する事が出来るようになる模様
一応メモ