今回の課題は「Validator と MessageSource」で作成したパーツを再利用した登録フォームにします。
データオブジェクトの再利用
とりあえず登録フォームの入力項目は以下のように考えました。
- メールアドレス: email
- パスワード: password
- 名字: firstName
- 名前: lastName
UserProfile.java
このクラスは IdCard クラスを extends しています。名字(firstName)と名前(lastName)は、プロパティとそれらに対応する setter/getter を定義していますが、メールアドレス(email)とパスワード(password)については、スーパークラスの setter/getter を呼び出しています。
package wrider.model; public class UserProfile extends IdCard { private String lastName; public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; } private String firstName; public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getEmail() { return super.getEmail(); } public void setEmail(String email) { super.setEmail(email); } public String getPassword() { return super.getPassword(); } public void setPassword(String password) { super.setPassword(password); } }
Validator の再利用
Validator は、IdCard 用の IdCardValidator を DI(Dependency injection)します。
UserProfileValidator.java
リファレンスの“6.2 Validation using Spring's Validator interface”に習い、コンストラクター引数として IdCardValidator を DI しています。この方法を Constructor-based DI というらしいです。
上記 UserProfile クラスと同じように登録フォーム独自の項目である「名字」と「名前」の未入力チャックとエラー情報の reject を ValidationUtils の rejectIfEmptyOrWhitespace(..) メソッドで行い、「メールアドレス」と「パスワード」に関しては、DI した IdCardValidator をinvokeValidator(..) メソッドで呼び出して検証させます。
package wrider.validator; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; import org.springframework.validation.Errors; import wrider.model.UserProfile; public class UserProfileValidator implements Validator { private final Validator idCardValidator; private static final String ERROR_FIELD_EMPTY = "error.field.empty"; public UserProfileValidator(Validator idCardValidator) { this.idCardValidator = idCardValidator; } public boolean supports(Class<?> clazz) { return UserProfile.class.isAssignableFrom(clazz); } public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", ERROR_FIELD_EMPTY); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", ERROR_FIELD_EMPTY); UserProfile userProfile = (UserProfile)target; ValidationUtils.invokeValidator(idCardValidator, userProfile, errors); } }
Validator の登録
新しく作成した UserProfileValidator を applicationContext.xml に登録します。
applicationContext.xml(抜粋)
リファレンスの“4.4.1.1 Constructor-based dependency injection”に習い <constructor-arg> 要素で userProfileValidator ビーンのコンストラクター引数として idCardValidator を DI するよう指示しています。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" : http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- Validator --> <bean id="idCardValidator" class="wrider.validator.IdCardValidator"/> <bean id="userProfileValidator" class="wrider.validator.UserProfileValidator"> <constructor-arg ref="idCardValidator"/> </bean> : </beans>
エラーコードの修正
今回は IdCard と UserProfile のバリデーションエラーに共通のエラーコードで対応したいので、以下のように errors.xml を修正しました。
修正前 [エラーコード].[オブジェクト名].[フィールド名]
↓
修正後 [エラーコード].[フィールド名]
errors.xml
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>field and form level error messages</comment> <entry key="error.field.empty.email">必須です</entry> <entry key="error.field.illigal.email">あなたのメルアドです</entry> <entry key="error.field.empty.password">必須です</entry> <entry key="error.field.illigal.password">内容をお確かめ下さい</entry> <entry key="error.field.empty.firstName">必須です</entry> <entry key="error.field.empty.lastName">必須です</entry> <entry key="error.form.invalid.idCard">入力ミスがあります</entry> <entry key="error.form.invalid.userProfile">入力ミスがあります</entry> </properties>
registrationForm.jsp(抜粋)
登録フォームの jsp です。とりあえず先の login.jsp に名字と名前を追加して、<form:form> タグの modelAttribute と、POST リクエストを投げる先を変えただけです。
<body> <h1>WriDer's Demo Site</h1> <form:form action="register.html" method="POST" modelAttribute="userProfile"> <label for="email">メールアドレス</label> <input type="text" id="email" name="email" value="${userProfile.email}"/> <span class="red"><form:errors path="email"/></span> <br /> <label for="password">パスワード</label> <input type="password" id="password" name="password" value="${userProfile.password}"/> <span class="red"><form:errors path="password"/></span> <br /> <label for="firstName">名字</label> <input type="text" id="firstName" name="firstName" value="${userProfile.firstName}"/> <span class="red"><form:errors path="firstName"/></span> <br /> <label for="lastName">名前</label> <input type="text" id="lastName" name="lastName" value="${userProfile.lastName}"/> <span class="red"><form:errors path="lastName"/></span> <br /> <input type="submit" value="決定"/> </form:form> </body>
コントローラーの変更
今回の登録フォームも AccountController クラスで対応します。@RequestMapping アノテーションで /account/register.html に対する GET/POST リクエストを振り分けています。POST で実行される register メソッドで userProfileValidator を呼び出し、バリデーションを行っています。
また、@SessionAttributes, @Autowired, @ModelAttribute アノテーション、および import に UserProfile に関する記述を追加しています。
AccountController.java(抜粋)
package wrider.controller; : import wrider.model.UserProfile; @Controller @SessionAttributes({"idCard", "userProfile"}) public class AccountController { @Autowired private Validator idCardValidator; @Autowired private Validator userProfileValidator; : @ModelAttribute("userProfile") public UserProfile makeUserProfile() { return new UserProfile(); } : @RequestMapping(value="/account/register.html", method=RequestMethod.GET) public String register() { return "account/registrationForm"; } @RequestMapping(value="/account/register.html", method=RequestMethod.POST) public ModelAndView register(UserProfile userProfile, BindingResult br) { this.userProfileValidator.validate(userProfile, br); ModelAndView mav = new ModelAndView(); mav.getModel().putAll(br.getModel()); mav.setViewName("account/registrationForm"); return mav; } : }
実行
図はブラウザーで /account/register.html にアクセスし、「名前」を未入力にして submit した後の画面です。
POST リクエストを受けた Spring Framework は IdCard インスタンスに「メールアドレス」と「パスワード」、UserProfile インスタンスに「名字」「名前」をセットし、AccountController の register(..)メソッドを呼び出します。同メソッドは UserProfileValidator のバリデーション結果を BindingResult から取り出して ModelAndView にセットします。
Java は元々、コンポーネントを再利用する仕掛けを持っていますが、Spring Framework が提供する DI(または IoC)の仕掛けを使えば、依存関係を記した XML(やアノテーション)に従って、インスタンス化や注入を行ってくれます。使いたいコンポーネントを直に import して new する頻度を減らせるので、うまく使いこなせば大規模アプリケーションの改修なども楽になるかも、と感じました。
0 件のコメント:
コメントを投稿