Dans un service Spring MVC REST service (json), j'ai une méthode de contrôleur comme celle-ci:
@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody List<MyBean> request, BindingResult bindingResult) {
Où la classe MyBean a des annotations de validation de bean.
Les validations ne semblent pas avoir lieu dans ce cas, bien que cela fonctionne bien pour les autres contrôleurs.
Je ne veux pas encapsuler la liste dans un dto cela qui changerait l'entrée json.
Pourquoi n'y a-t-il pas de validations pour une liste de beans? Quelles sont les alternatives?
@Valid
est une annotation JSR-303 et JSR-303 s'applique à la validation sur JavaBeans. UNE Java.util.List
n'est pas un JavaBean (selon la description officielle d'un JavaBean), il ne peut donc pas être validé directement à l'aide d'un validateur compatible JSR-303. Ceci est étayé par deux observations.
La section 3.1.3 de la spécification JSR-3 dit que:
En plus de prendre en charge la validation d'instance, la validation des graphiques de l'objet est également prise en charge. Le résultat d'une validation de graphique est renvoyé sous la forme d'un ensemble unifié de violations de contraintes. Considérez la situation où le bean X contient un champ de type Y . En annotant le champ Y avec l'annotation @Valid , le validateur validera Y (et ses propriétés) lorsque X est validé . Le type Z exact de la valeur contenue dans le champ déclaré de type Y (sous-classe, implémentation) est déterminé lors de l'exécution. Les définitions de contraintes de Z sont utilisées. Cela garantit un comportement polymorphe correct pour les associations marquées @Valid.
Valeur de collection, valeur de tableau et généralement Iterable les champs et les propriétés peuvent également être décorés avec l'annotation @Valid . Cela provoque la validation du contenu de l'itérateur. Tout objet implémentant Java.lang.Iterable est pris en charge.
J'ai marqué les informations importantes en gras. Cette section implique que pour qu'un type de collection soit validé, il doit être encapsulé dans un bean (sous-entendu par Consider the situation where bean X contains a field of type Y
); et en outre que les collections ne peuvent pas être validées directement (sous-entendu par Collection-valued, array-valued and generally Iterable fields and properties may also be decorated
, en mettant l'accent sur les champs et les propriétés ).
Implémentations réelles de JSR-303
J'ai n exemple d'application qui teste la validation de la collection avec Hibernate Validator et Apache Beans Validator. Si vous exécutez des tests sur cet exemple en tant que mvn clean test -Phibernate
(avec Hibernate Validator) et mvn clean test -Papache
(pour Beans Validator), les deux refusent de valider directement les collections, ce qui semble être conforme à la spécification. Étant donné que Hibernate Validator est l'implémentation de référence pour JSR-303, cet exemple est une preuve supplémentaire que les collections doivent être encapsulées dans un bean pour être validées.
Avec cela effacé, je dirais qu'il y a aussi un problème de conception en essayant de passer une collection à une méthode de contrôleur directement de la manière indiquée dans la question. Même si les validations devaient fonctionner directement sur les collections, la méthode du contrôleur ne pourrait pas fonctionner avec d'autres représentations de données telles que XML, SOAP, ATOM, EDI, les tampons de protocole Google, etc. qui ne mappent pas directement aux collections. Pour prendre en charge ces représentations, le contrôleur doit accepter et renvoyer les instances d'objet. Cela nécessiterait de toute façon l'encapsulation de la collection à l'intérieur d'une instance d'objet. Il serait donc fortement conseillé d'envelopper le List
dans un autre objet comme d'autres réponses l'ont suggéré.
La seule façon que je pourrais trouver pour ce faire est d'envelopper la liste, cela signifie également que l'entrée JSON devrait changer .
@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody List<MyBean> request, BindingResult bindingResult) {
devient:
@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody MyBeanList request, BindingResult bindingResult) {
et nous avons également besoin de:
import javax.validation.Valid;
import Java.util.List;
public class MyBeanList {
@Valid
List<MyBean> list;
//getters and setters....
}
Il semble que cela pourrait également être possible avec un validateur personnalisé pour les listes, mais je n'ai pas encore atteint ce stade.
L'annotation @Valid fait partie de l'API de validation de bean JSR-303 standard et n'est pas une construction spécifique à Spring. Spring MVC validera un objet @Valid après la liaison tant qu'un validateur approprié a été configuré.
Référence: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html
Essayez la validation directe. Quelque chose comme ça:
@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())
...
Utilisation de 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;
}
}
Donc pas besoin de l'emballage
vous pouvez utiliser
@RequestMapping(method = RequestMethod.POST, value = { "/doesntmatter" })
@ResponseBody
public List<...> myMethod(@Valid @RequestBody ValidList<MyBean> request, BindingResult bindingResult) {
En utilisant le wrapper, votre JSON doit être changé en
{
"list": []
}
avec cette implémentation, vous pouvez utiliser le JSON d'origine
[]
Il existe une méthode élégante pour emballer votre demande dans un Java.util.List
qui agit à la fois List
et JavaBean
. voir ici
Implémentez votre propre validateur avec org.springframework.validation.beanvalidation.LocalValidatorFactoryBean en tant que membre et appelez ce validateur pour chaque élément.
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
}
Si vous ne voulez pas écrire un wrapper pour chaque liste que vous avez, vous pouvez utiliser un wrapper générique:
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();
}
}
Je pense que votre meilleure option est d'envelopper la liste - Comment valider le paramètre de demande s'il ne s'agit pas d'un bean au printemps MVC?
Il n'y a aucun moyen atm de dire que le @ Valid s'applique aux éléments de la collection.