かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot でログイン画面 + 一覧画面 + 登録画面の Webアプリケーションを作る ( その12 )( 登録画面作成2 )

概要

Spring Boot でログイン画面 + 一覧画面 + 登録画面の Webアプリケーションを作る ( その11 )( 登録画面作成 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • 登録画面 ( 入力→確認→完了 ) の作成、確認。
    • Code2 の入力フィールドが小さすぎるのでもう少し大きくします。
    • エラー時、入力フィールドの背景色を赤にしたいのでやり方を調べます。
    • SurfaceArea の入力チェックを @NotBlank, @Digits の2つではなく @Digits だけにしても空の時にエラーになる ( @Digits だけでも必須チェックが入る? ) ようなので、@Digits だけなら空の時にはエラーにならないようにする方法を調べてみます。
    • Validator インターフェースを実装したクラスを用意して、複合項目の入力チェックを実装してみます。以下の仕様で実装してみます。
      • Code と Code2 に同じ文字列を入力している場合はエラー。
      • Name = "Japan" あるいは "日本" の場合、Continent に Asia 以外が選択されている場合はエラー。
      • Continent = "Asia" の場合、Region は "Eastern Asia", "Middle East", "Southeast Asia", "Southern and Central Asia" のいずれかでない場合はエラー。
  • 下記の件は必要がなくなったため調査していません。
    • SurfaceArea と Population は @NotBlank, @Digits の2つのアノテーションを付加していますが、入力チェックは両方実行され、どちらのエラーメッセージも表示されていました。チェックの順序を指定して、1つのチェックがエラーになったら次のチェックは行わないようにする方法があるのか調べてみます。

ソフトウェア一覧

参考にしたサイト

  1. 原色大辞典
    http://www.colordic.org/

  2. Hibernate Validator - Chapter 2. Declaring and validating bean constraints
    http://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-bean-constraints.html

  3. How to customize default message for BigDecimal
    http://stackoverflow.com/questions/15104012/how-to-customize-default-message-for-bigdecimal

  4. Kielczewski.eu - Spring Boot Security Application
    http://kielczewski.eu/2014/12/spring-boot-security-application/

    • Validator インターフェースを実装したクラスを作成する時に参考にしました。

手順

Tomcat を起動する

毎回 Tomcat を起動/終了していて Spring Loaded を生かしていなかったので、今回は最初に起動して必要な時だけ再起動します。

  1. Gradle tasks View から bootRun タスクを実行します。

Code2 の入力フィールドを少し大きくする

  1. src/main/resources/templates/country の下の input.html を リンク先のその1の内容 に変更します。

  2. 確認します。ブラウザで http://localhost:8080/country/input にアクセスして登録画面を表示すると、SurfaceArea と同じ大きさになっています。

    f:id:ksby:20150214202135p:plain

エラー時、入力フィールドの背景色を赤にする

  1. src/main/resources/templates/country の下の input.html を リンク先のその2の内容 に変更します。

  2. 確認します。ブラウザで http://localhost:8080/country/input にアクセスして登録画面を表示し、何も入力せずに「確認」ボタンをクリックすると入力フィールドの背景色が薄い赤になります。

    f:id:ksby:20150214210308p:plain

SurfaceArea の入力チェックを @NotBlank を削除して @Digits だけにした場合に、空の時にはエラーにならないようにする方法を調査する

  1. 原因は @Digits アノテーションを付加するフィールド ( 数値のみ入力を許可する ) を BigDecimail 型ではなく String 型で定義していたためでした。BigDecimail 型にすれば @Digits だけにした場合に空の時にはエラーにならないようになります。

  2. 今回は空はエラーにしたいので、src/main/java/ksbysample/webapp/basic/web の下の CountryForm.javaリンク先の内容 に変更します。

  3. @NotNull のエラーメッセージを日本語化するために src/main/resources の下の ValidationMessages_ja_JP.properties を リンク先の内容 に変更します。

  4. 確認します。まずは Ctrl+F5 を押して Tomcat を再起動します ( 再起動しないと properties ファイルの変更と、@NotBlank → @NotNull への変更が反映されませんでした )。

  5. ブラウザで http://localhost:8080/country/input にアクセスして登録画面を表示し、何も入力せずに「確認」ボタンをクリックすると SafaceArea と Population の入力項目には @NotNull の入力チェックエラーのメッセージだけ表示されました。

    f:id:ksby:20150215090020p:plain

  6. 次に SafaceArea に 1.001、Population に 1.1 を入力すると @Digits の入力チェックエラーのメッセージだけ表示されました。

    f:id:ksby:20150215090846p:plain

  7. 最後に SafaceArea を 1.01、Population を 1 に変更するとエラーメッセージは何も表示されませんでした。

    f:id:ksby:20150215092302p:plain

  8. BigDecimail 型ではなく String 型にしていたのは、数値以外の値を入力すると Failed to convert property value of type java.lang.String to required type java.math.BigDecimal for property surfaceArea; nested exception is java.lang.NumberFormatException というエラーが出るためだったのですが、英語の長いエラーメッセージではなく分かりやすい日本語のエラーメッセージを表示する方法がないか調べてみます。

    f:id:ksby:20150215092649p:plain

  9. いろいろ調べた結果、エラーメッセージを日本語化する方法がありました。src/main/resources の下の messages_ja_JP.properties を リンク先のその1の内容 に変更します。Failed to convert ... のメッセージは Bean Validation のエラーメッセージではないので、ValidationMessages_ja_JP.properties に定義しても反映されません。

  10. 確認します。まずは Ctrl+F5 を押して Tomcat を再起動します ( properties ファイルを変更しているため )。

  11. ブラウザで http://localhost:8080/country/input にアクセスして登録画面を表示し、SurfaceArea に "a" と入力して「確認」ボタンをクリックすると「数値を入力して下さい。」のエラーメッセージが表示されました。

    f:id:ksby:20150215100317p:plain

Bean Validation のアノテーションを2つ以上付加している場合に、チェックの順序を指定して1つのチェックがエラーになったら次のチェックは行わないようにする

※上の「SurfaceArea の入力チェックを @NotBlank を削除して @Digits だけにした場合に、空の時にはエラーにならないようにする方法を調査する」に書いた内容で実現したい動作になったため、この件の調査は取り止めます。

Validator インターフェースを実装したクラスを用意して、複合項目の入力チェックを実装する

  1. src/main/java/ksbysample/webapp/basic/web の下に CountryFormValidator.java を作成します。作成後、リンク先の内容 に変更します。

  2. src/main/java/ksbysample/webapp/basic/web の下の CountryController.javaリンク先の内容 に変更します。

  3. src/main/resources の下の messages_ja_JP.properties を リンク先のその2の内容 に変更します。

  4. 確認します。まずは Ctrl+F5 を押して Tomcat を再起動します。

  5. ブラウザで http://localhost:8080/country/input にアクセスして登録画面を表示し、Code と Code2 に "JP" と入力して「確認」ボタンをクリックすると「Code2 には Code と異なる文字列を入力して下さい。」のエラーメッセージが表示されました。

    f:id:ksby:20150215204658p:plain

  6. Code を "JPN" に変更した後、Continent で Europe を選択して「確認」ボタンをクリックします。今度は「Name に "Japan" あるいは "日本" を入力している場合、Continent は "Asia" を選択して下さい。」のエラーメッセージが表示されました。

    f:id:ksby:20150215204910p:plain

  7. Continent で Asia を選択した後、Region に "東南アジア" を入力して「確認」ボタンをクリックします。今度は「Continent に "Asia" を選択している場合、Region には "Eastern Asia", "Middle East", "Southeast Asia", "Southern and Central Asia" のいずれかの文字列を入力して下さい。」のエラーメッセージが表示されました。

    f:id:ksby:20150215205658p:plain

  8. Region に "Eastern Asia" を入力して「確認」ボタンをクリックします。入力チェックエラーはないので、今度は確認画面へ遷移しました。

    f:id:ksby:20150215205908p:plain

Tomcat を終了する

  1. Ctrl+F2 を押して Tomcat を停止します。

※今回 Spring Loaded を利用して Tomcat の再起動を最小限にするつもりでしたが、1)エラーが発生すると実装ミスなのか Tomcat を再起動しないと反映されない問題なのかまだ見極めが出来ておらず結構頻繁に再起動していました、2)入力チェックエラーを実装していると properties ファイルを変更するため再起動せざろうえない ( 再起動しないと properties ファイルの変更内容が反映されない ) 、という感じであまり Spring Loaded の恩恵を受けられませんでした。2) の properties ファイルのキャッシュ?は開発中は無効化できないか調べてみます。

次回は。。。

  • 登録処理と完了画面を実装します。
  • properties ファイルのキャッシュ?を無効化できないか調べてみます。

ソースコード

input.html

■その1

                    <div class="form-group" th:classappend="${#fields.hasErrors('*{code2}')} ? 'has-error' : ''">
                        <label for="code2" class="control-label col-sm-2">Code2</label>
                        <div class="col-sm-10">
                            <div class="row"><div class="col-sm-2"><input type="text" name="code2" id="code2" class="form-control input-sm" value="" placeholder="JP" th:field="*{code2}"/></div></div>
                            <div class="row" th:if="${#fields.hasErrors('*{code2}')}"><div class="col-sm-10"><p class="form-control-static text-danger"><small th:errors="*{code2}"></small></p></div></div>
                        </div>
                    </div>
  • Code2 の入力フィールドを <div class="col-sm-1"><div class="col-sm-2"> へ変更します。

■その2

    <style>
    <!--
    body {
        padding-top: 50px;
    }
    .navbar-brand {
        font-size: 24px;
    }
    .form-group {
        margin-bottom: 5px;
    }
    h1 {
        margin-bottom: 20px;
    }
    .cst-form-inputform {
        margin-bottom: 20px;
    }
    .has-error .form-control {
        background-color: #fff5ee;
    }
    -->
    </style>
  • .has-error .form-control { ... } を追加して、エラー時の background-color を定義します。

CountryForm.java

    @NotNull
    @Digits(integer=8, fraction=2, message = "{error.digits.integerandfraction}")
    private BigDecimal surfaceArea;

    @NotNull
    @Digits(integer=11, fraction=0, message = "{error.digits.integeronly}")
    private BigDecimal population;
  • surfaceArea と population を @NotBlank@NotNull、String型 → BigDecimail型へ変更します。@NotBlank@NotNull へ変更しているのは @NotBlank が CharSequence にしか適用できないためです。これで入力値が空の時は @NotNull のエラーメッセージだけ表示され、数値が入力されて @Digits の入力チェックエラー発生時は @Digits のエラーメッセージだけ表示されるようになります。

ValidationMessages_ja_JP.properties

javax.validation.constraints.NotNull.message=必須の入力項目です。

org.hibernate.validator.constraints.NotBlank.message=必須の入力項目です。
  • javax.validation.constraints.NotNull.message を追加します。

messages_ja_JP.properties

■その1

AbstractUserDetailsAuthenticationProvider.locked=入力された ID はロックされています
AbstractUserDetailsAuthenticationProvider.disabled=入力された ID は使用できません
AbstractUserDetailsAuthenticationProvider.expired=入力された ID の有効期限が切れています
AbstractUserDetailsAuthenticationProvider.credentialsExpired=入力された ID のパスワードの有効期限が切れています
AbstractUserDetailsAuthenticationProvider.badCredentials=入力された ID あるいはパスワードが正しくありません

typeMismatch.java.math.BigDecimal=数値を入力して下さい。
  • typeMismatch.java.math.BigDecimal を追加します。

■その2

typeMismatch.java.math.BigDecimal=数値を入力して下さい。

countryForm.code2.equalCode = Code2 には Code と異なる文字列を入力して下さい。
countryForm.continent.notAsia = Name に "Japan" あるいは "日本" を入力している場合、Continent は "Asia" を選択して下さい。
countryForm.region.notAsiaPattern = Continent に "Asia" を選択している場合、Region には "Eastern Asia", "Middle East", "Southeast Asia", "Southern and Central Asia" のいずれかの文字列を入力して下さい。
  • CountryFormValidator クラスで使用している countryForm.code2.equalCode, countryForm.continent.notAsia, countryForm.region.notAsiaPattern を定義します。

CountryFormValidator.java

package ksbysample.webapp.basic.web;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import java.util.regex.Pattern;

@Component
public class CountryFormValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return clazz.equals(CountryForm.class);
    }

    @Override
    public void validate(Object target, Errors errors) {
        CountryForm countryForm = (CountryForm)target;

        // Code と Code2 に同じ文字列を入力している場合はエラー
        if (StringUtils.equals(countryForm.getCode(), countryForm.getCode2())) {
            errors.rejectValue("code2", "countryForm.code2.equalCode");
        }

        // Name = "Japan" あるいは "日本" の場合、Continent に Asia 以外が選択されている場合はエラー
        if ((StringUtils.equals(countryForm.getName(), "Japan") || StringUtils.equals(countryForm.getName(), "日本"))
                && (!StringUtils.equals(countryForm.getContinent(), "Asia"))) {
            errors.rejectValue("continent", "countryForm.continent.notAsia");
        }

        // Continent = "Asia" の場合、Region は "Eastern Asia", "Middle East", "Southeast Asia", "Southern and Central Asia"
        // のいずれかでない場合はエラー
        if ((StringUtils.equals(countryForm.getContinent(), "Asia"))
                && (!Pattern.matches("^(Eastern Asia|Middle East|Southeast Asia|Southern and Central Asia)$", countryForm.getRegion()))) {
            errors.rejectValue("region", "countryForm.region.notAsiaPattern");
        }
    }

}
  • Validator インターフェースを実装し、supports メソッド、validate メソッドをオーバーライドします。
  • supports メソッドの実装内容は上記の通りです ( 実はよく分かっていません )。
  • validate メソッドに複合項目チェックを実装します。引数の target に Formクラスのインスタンスが渡されてくるので、最初に実際の Form クラスにキャストします。またチェックエラーの時は errors.rejectValue か errors.reject を呼び出してエラーメッセージをセットします。今回は入力フィールドにエラーメッセージを表示させるので errors.rejectValue を使用します。errors.rejectValue は第1引数が項目名、第2引数が messages.properties に定義したエラーメッセージのメッセージIDです。

CountryController.java

@Controller
@RequestMapping("/country")
public class CountryController {

    @Autowired
    private Constant constant;

    @Autowired
    private CountryFormValidator countryFormValidator;

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.addValidators(countryFormValidator);
    }
  • private CountryFormValidator countryFormValidator; を追加します。
  • initBinder メソッドを追加します。

履歴

2015/02/15
初版発行。