Springで入力パラメータが数値の場合に入力文字種を半角文字に限定する

# はじめに
Springにて入力パラメータとしてFormに数値型（IntegerやLong）で定義した場合、全角数字で入力した場合も正常にバインドされます。
しかし、これだと、半角数字だけを許容したいという場面に対応出来ません。
入力チェックのためだけにフィールドをString型で定義して半角数字のみを許容するようにPatternアノテーションを付与して入力規則を定義するのも何だかな、と思っていました。
そこで、なんかいい方法がないか探していたところ、WebDataBinderを利用しCustomNumberEditorを拡張したクラスを登録すればよいことがわかりました。

# やり方

まず、CustomNumberEditorを拡張したクラスを作成します。

```java:CustomNumberEditorを拡張したクラス
public class SampleCustomNumberEditor extends CustomNumberEditor {

	private final Class<? extends Number> numberClass;
 
    public SampleCustomNumberEditor (Class<? extends Number> numberClass, boolean allowEmpty) {
        super(numberClass, allowEmpty);
        this.numberClass = numberClass;
    }
    
    @Override
    public void setAsText(String s) throws IllegalArgumentException {
        // 値が入力されている場合のみ処理する
        if (StringUtils.hasText(s)) {
            // 半角の整数の場合
            if (s.matches("^(\\+|\\-)?[0-9]*$")) {
                // スーパークラスのメソッドに処理を移譲
                super.setAsText(s);
                return;
            }
            // 小数点付きのクラスの場合
            if (numberClass == Float.class || numberClass == Double.class
                || numberClass == BigDecimal.class) {
                // 半角の小数点つき数値文字列以外の場合(この例では".9"のような表現も許容)
                if (!s.matches("^((\\+|\\-)?[0-9]+)?\\.[0-9]+$")) {
                    throw new IllegalArgumentException("半角数値以外が入力されています");
                }
            } else {
                    throw new IllegalArgumentException("半角の整数以外が入力されています");
            }
        }
        // 半角数値以外を弾いた後はスーパークラスのメソッドを呼び出し処理を移譲
        super.setAsText(s);
    }
}
```

Controllerクラス、もしくは、全処理で適応したい場合はControllerAdviceアノテーションを付与したクラスに、@InitBinderアノテーションを付与した以下のメソッドを作成し、WebDataBinderのregisterCustomEditorメソッドで、前述で作成したクラスを登録します。

```java:Controllerクラス、もしくは、ControllerAdviceクラス
@ControllerAdvice
public class SampleControllerAdvice {
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // Integer型を登録する
        binder.registerCustomEditor(Integer.class, new SampleCustomNumberEditor(Integer.class, true));
        // Long型を登録する
        binder.registerCustomEditor(Long.class, new SampleCustomNumberEditor(Long.class, true));

        // ...
        
        // Double型を登録する
        binder.registerCustomEditor(Double.class, new SampleCustomNumberEditor(Double.class, true));
        // BigDecimal型を登録する
        binder.registerCustomEditor(BigDecimal.class, new SampleCustomNumberEditor(BigDecimal.class, true));
    }
}
```

上記で登録したすべての数値型に関しては、Formに半角数値以外が入力された場合は、入力エラーとして処理されます。必要に応じて使う型だけ登録すればよいかと思います。
なお、デフォルトでは、CustomNumberEditorが有効になります。

# その他
とりあえずInteger型だけ対応したい、というケースにおいては、わざわざEditorクラスを作成しなくても、以下のように匿名クラスで対応できます。

```java:Controllerクラス、もしくは、ControllerAdviceクラス
@ControllerAdvice
public class SampleControllerAdvice {
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // Integer型を登録する
        binder.registerCustomEditor(Integer.class, new CustomNumberEditor(Integer.class, true) {
            @Override
            public void setAsText(String s) throws IllegalArgumentException {
                // 値が入力されている場合のみ処理する
                if (StringUtils.hasText(s)) {
                    // 半角の整数以外の場合
                    if (!s.matches("^(\\+|\\-)?[0-9]*$")) {
                        throw new IllegalArgumentException("半角数値以外が入力されています");
                    }
                }
                super.setAsText(s);
            }
        });
    }
}
```