Spring Framework Advent Calendar 2011 part.8 - Spring MVC で String 配列にカンマ入り文字列を渡すと分割されてしまう問題

Spring framework Advent Calender 8 日目です。本日は Spring MVC に戻って、配列型のチェックボックスにまつわる話題です。

こんな画面を想像してください。

よくある Web の入力フォームです。チェックボックスの部分に注目してください。この部分は、対応する Form に配列が使われています。JSP のソースと、対応する Form のソースを見てみましょう。

  ...
  <div class="field">
    <div class="label">アイテム</div>
    <form:checkboxes items="${names}" path="items" delimiter=" "/>
  </div>
 
  <div class="button">
    <input type="submit" name="execute" value="登録" />
  </div>
  ...
import java.io.Serializable;

@SuppressWarnings("serial")
public class RegisterForm implements Serializable {
    ...
    
    private String[] items;
    
    ...
    
    public String[] getItems() {
		return items;
    }
    
    public void setItems(String[] items) {
		this.items = items;
    }
}


このとき、生成されるHTMLのソースは以下のようになっています(長過ぎる行には部分的に改行を入れています)。

<div class="label">アイテム</div>
  <span><input id="items1" name="items" value="orange" type="checkbox"><label for="items1">orange</label></span>
<span> <input id="items2" name="items" value="apple" type="checkbox"><label for="items2">apple</label></span>
<span> <input id="items3" name="items" value="melon" type="checkbox"><label for="items3">melon</label></span>
<span> <input id="items4" name="items" value="gra,pe" checked="checked" type="checkbox"><label for="items4">gra,pe</label></span>
<input name="_items" value="on" type="hidden"></div>
<div class="button">
  <input name="execute" value="登録" type="submit">
</div>
</form></div>


これらのチェックボックスをチェックしたとき、配列に渡る値はどのようになるでしょうか? 今回のエントリのタイトルから、読者の方は既にお分かりだとは思いますが...。



特に問題なさそうです。では、チェックを1つにしてみます。



おや? "gra,pe" が "gra" と "pe" に分割されてしまっていますね。
これは、Spring が Bean にリクエストの値をバインドするときに動作する ConversionService に原因があります。Spring MVC のリクエスマッピングは、HTTP でとんでくるリクエスト文字列をさまざまな型に変換して Bean にセットする仕組みになっています。その型変換をおこなうのが ConversionService なのですが、そのうちのひとつに StringToArrayConverter があります。これはカンマ区切り文字列を String 配列に置き換える Converter ですが、これが今回、こちらの意図しない動作をした原因です。

原因がわかれば対処法はあります。今回は、カスタムの PropertyEditor を登録する方法で回避してみます。

/**
 * カンマを分割しない {@link StringArrayPropertyEditor}.
 * <p>
 * 配列型のフォームに対して カンマを含む String のデータを渡すと、
 * StringToArrayConverter が動いて値が分割されてしまう問題の回避策。
 *
 * @see StringArrayPropertyEditor
 */
public class CommaIgnoredStringArrayEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        String[] array = new String[] { text };
        setValue(array);
    }
}


そして、この仕掛けが必要となる Controller の initBinder メソッドでカスタムエディタを追加します。

    @InitBinder
    public void initBinder(WebDataBinder binder) {
            binder.registerCustomEditor(String[].class, new CommaIgnoredStringArrayEditor());
        }
    }



こんどは大丈夫なようですね。
なお、今回のような問題は、

  • 配列型のプロパティで、
  • 画面側での選択が1つしかない
  • かつ、その選択した値にカンマが含まれている

という特殊な状況でしか起こりませんので、このような対処が必要になること自体あまりないかもしれません。ただ、こうして Spring の拡張ポイントを覚えていくのもまた一興かな、とも思います。