web-dev-qa-db-ja.com

Spring MVC-RESTサービスのBeanリストの@Valid

Spring MVC RESTサービス(json)には、次のようなコントローラーメソッドがあります。

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody List<MyBean> request, BindingResult bindingResult) {

MyBeanクラスにBean検証アノテーションがある場合。

この場合、検証は行われないようですが、他のコントローラーではうまく機能します。

Json入力を変更するdtoでリストをカプセル化したくありません。

Beanのリストに検証がないのはなぜですか?代替手段は何ですか?


33

@ValidはJSR-303アノテーションであり、JSR-303はJavaBeansの検証に適用されます。 Java.util.ListはJavaBeanではありません(JavaBeanの 公式の説明 による)。したがって、JSR-303準拠のバリデータを使用して直接検証することはできません。これは、2つの観測によってサポートされています。

JSR-303仕様 のセクション3.1.3は次のように述べています:

インスタンス検証のサポートに加えて、オブジェクトのグラフの検証もサポートされています。グラフ検証の結果は、制約違反の統一されたセットとして返されます。 bean XにタイプYのフィールドが含まれる状況を考えます。 フィールドYに@Validアノテーションで注釈を付けることにより、Xが有効な場合にValidatorがY(およびそのプロパティ)を検証します検証済み。タイプY(サブクラス、実装)で宣言されたフィールドに含まれる値の正確なタイプZは、実行時に決定されます。 Zの制約定義が使用されます。これにより、@ Validとマークされた関連付けの適切な多態的な動作が保証されます。

コレクション値、配列値、および一般的に繰り返し可能なフィールドとプロパティは@Validアノテーションで装飾することもできます。これにより、反復子の内容が検証されます。 Java.lang.Iterableを実装するオブジェクトはすべてサポートされます。

重要な情報を太字でマークしました。このセクションは、コレクション型を検証するために、Bean内にカプセル化する必要があることを意味します(Consider the situation where bean X contains a field of type Yによって暗黙指定されます)。さらに、コレクションを直接検証することはできません(Collection-valued, array-valued and generally Iterable fields and properties may also be decoratedによって暗示され、フィールドとプロパティに重点を置いています)。

実際のJSR-303実装

サンプルアプリケーション は、Hibernate ValidatorとApache Beans Validatorの両方でコレクションの検証をテストします。このサンプルでmvn clean test -Phibernate(Hibernate Validatorを使用)およびmvn clean test -Papache(Beans Validatorを使用)としてテストを実行すると、両方ともコレクションの直接検証を拒否します。これは仕様に沿っているようです。 Hibernate ValidatorはJSR-303のリファレンス実装であるため、このサンプルは、コレクションを検証するためにBeanにカプセル化する必要があることをさらに証明しています。


それをクリアすると、質問に示されている方法でコレクションをコントローラーメソッドに直接渡そうとする際に、設計上の問題もあると思います。検証がコレクションに対して直接機能する場合でも、コントローラーメソッドは、コレクションに直接マッピングされないカスタムXML、SOAP、ATOM、EDI、Googleプロトコルバッファーなどの代替データ表現を使用できません。これらの表現をサポートするために、コントローラーはオブジェクトインスタンスを受け入れて返す必要があります。それには、コレクションをオブジェクトインスタンス内にカプセル化する必要があります。したがって、他の答えが示唆するように、Listを別のオブジェクト内にラップすることを強くお勧めします。

42
manish

私がこれを見つけることができる唯一の方法は、リストをラップすることですこれは、JSON入力を変更する必要があることも意味します.

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody List<MyBean> request, BindingResult bindingResult) {

になる:

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody MyBeanList request, BindingResult bindingResult) {

また、次のものも必要です。

import javax.validation.Valid;
import Java.util.List;

public class MyBeanList {

    @Valid
    List<MyBean> list;

    //getters and setters....
}

これは、リストのカスタム検証でも可能になるように見えますが、私はまだそこまで持っていません。

@Validアノテーションは、標準のJSR-303 Bean Validation APIの一部であり、Spring固有の構造ではありません。適切なValidatorが設定されている限り、Spring MVCはバインド後に@Validオブジェクトを検証します。

リファレンス: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html

13
J0B

直接検証してみてください。このようなもの:

@Autowired
Validator validator;

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public Object myMethod(@RequestBody List<Object> request, BindingResult bindingResult) {
    for (int i = 0; i < request.size(); i++) {
        Object o = request.get(i);
        BeanPropertyBindingResult errors = new BeanPropertyBindingResult(o, String.format("o[%d]", i));
        validator.validate(o, errors);
        if (errors.hasErrors())
            bindingResult.addAllErrors(errors);
    }
    if (bindingResult.hasErrors())
        ...
5
therg

Com.google.common.collect.ForwardingListを使用する

public class ValidList<T> extends ForwardingList<T> {

  private List<@Valid T> list;

  public ValidList() {
    this(new ArrayList<>());
  }

  public ValidList(List<@Valid T> list) {
    this.list = list;
  }

  @Override
  protected List<T> delegate() {
    return list;
  }

  /** Exposed for the {@link javax.validation.Validator} to access the list path */
  public List<T> getList() {
    return list;
  }
}

ラッパーは必要ありません

あなたは使うかもしれません

@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody ValidList<MyBean> request, BindingResult bindingResult) {

ラッパーを使用して、JSONを次のように変更する必要があります

{
  "list": []
}

この実装では、元のJSONを使用できます

[]
2
Jebil

ListJavaBeanの両方として機能するカスタムJava.util.Listでリクエストをラッピングするエレガントな方法があります。 こちらを参照

2
smartwjw

Org.springframework.validation.beanvalidation.LocalValidatorFactoryBeanをメンバーとして独自のバリデーターを実装し、各アイテムに対してそのバリデーターを呼び出します。

public class CheckOutValidator implements Validator {


    private Validator validator;

   @Override
    public void validate(Object target, Errors errors) { 
    List request = (List) target;
    Iterator it = request.iterator()   
    while(it.hasNext()) {
    MyBean b = it.next();
    validator.validate(b, errors);

     }

     }

//setters and getters

}
2

持っているリストごとにラッパーを書きたくない場合は、汎用ラッパーを使用できます。

public class ListWrapper<E> {

    private List<E> list;

    public ListWrapper() {
        list = new ArrayList<>();
    }

    public ListWrapper(List<E> list) {
        this.list = list;
    }

    @Valid
    public List<E> getList() {
        return list;
    }

    public void setList(List<E> list) {
        this.list = list;
    }

    public boolean add(E e) {
        return list.add(e);
    }

    public void clear() {
        list.clear();
    }

}
1
deve

あなたの最良の選択肢はリストをラップすることだと思います- それがSpring MVCのBeanではない場合、リクエストパラメータを検証する方法?

@ Validがコレクションの要素に適用されると言う方法はありません。

0
Hardy