Hej,
Je veux utiliser l'annotation @Validated(group=Foo.class)
pour valider un argument avant d'exécuter une méthode comme celle-ci:
public void doFoo(Foo @Validated(groups=Foo.class) foo){}
Lorsque je mets cette méthode dans le contrôleur de mon application Spring, le @Validated
est exécuté et génère une erreur lorsque l'objet Foo n'est pas valide. Cependant, si je mets la même chose dans une méthode dans la couche Service de mon application, la validation n'est pas exécutée et la méthode s'exécute juste même lorsque l'objet Foo n'est pas valide.
Ne pouvez-vous pas utiliser le @Validated
annotation dans la couche service? Ou dois-je faire quelque chose de plus pour le faire fonctionner?
Mise à jour:
J'ai ajouté les deux beans suivants à mon service.xml:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
et remplacé le @Validate
avec @Null
ainsi:
public void doFoo(Foo @Null(groups=Foo.class) foo){}
Je sais que c'est une annotation assez idiote à faire, mais je voulais vérifier que si j'appelle la méthode maintenant et en passant null, elle déclencherait une exception de violation, ce qu'elle fait. Alors pourquoi exécute-t-il le @Null
annotation et non @Validate
annotation? Je sais que l'un vient de javax.validation
et l'autre vient du printemps mais je ne pense pas que cela ait quelque chose à voir avec ça?
Aux yeux d'une pile Spring MVC, il n'existe pas de couche de service. La raison pour laquelle cela fonctionne pour @Controller
les méthodes de gestionnaire de classe sont que Spring utilise un HandlerMethodArgumentResolver
spécial appelé ModelAttributeMethodProcessor
qui effectue la validation avant de résoudre l'argument à utiliser dans votre méthode de gestionnaire.
La couche de service, comme nous l'appelons, n'est qu'un simple bean sans aucun comportement supplémentaire ajouté à partir de la pile MVC (DispatcherServlet
). En tant que tel, vous ne pouvez vous attendre à aucune validation de Spring. Vous devez rouler le vôtre, probablement avec AOP.
Avec MethodValidationPostProcessor
, jetez un œil au javadoc
Les méthodes applicables ont des annotations de contrainte JSR-303 sur leurs paramètres et/ou sur leur valeur de retour (dans ce dernier cas spécifié au niveau de la méthode, généralement sous forme d'annotation en ligne).
Les groupes de validation peuvent être spécifiés via l'annotation validée de Spring au niveau du type de la classe cible contenant, s'appliquant à toutes les méthodes de service public de cette classe. Par défaut, JSR-303 sera validé uniquement par rapport à son groupe par défaut.
Le @Validated
l'annotation n'est utilisée que pour spécifier un groupe de validation, elle ne force elle-même aucune validation. Vous devez utiliser l'un des javax.validation
annotations comme @Null
ou @Valid
. N'oubliez pas que vous pouvez utiliser autant d'annotations que vous le souhaitez sur un paramètre de méthode.
@pgiecek Vous n'avez pas besoin de créer une nouvelle annotation. Vous pouvez utiliser:
@Validated
public class MyClass {
@Validated({Group1.class})
public myMethod1(@Valid Foo foo) { ... }
@Validated({Group2.class})
public myMethod2(@Valid Foo foo) { ... }
...
}
En guise de note complémentaire sur la validation printanière des méthodes:
Puisque Spring utilise des intercepteurs dans son approche, la validation elle-même n'est effectuée que lorsque vous parlez à la méthode d'un Bean:
Lorsque vous parlez à une instance de ce bean via les interfaces du validateur Spring ou JSR-303, vous parlerez au validateur par défaut du ValidatorFactory sous-jacent. Ceci est très pratique dans la mesure où vous n'avez pas à effectuer un autre appel en usine, en supposant que vous utiliserez presque toujours le validateur par défaut de toute façon.
Ceci est important car si vous essayez d'implémenter une validation de cette manière pour les appels de méthode dans la classe, cela ne fonctionnera pas. Par exemple.:
@Autowired
WannaValidate service;
//...
service.callMeOutside(new Form);
@Service
public class WannaValidate {
/* Spring Validation will work fine when executed from outside, as above */
@Validated
public void callMeOutside(@Valid Form form) {
AnotherForm anotherForm = new AnotherForm(form);
callMeInside(anotherForm);
}
/* Spring Validation won't work for AnotherForm if executed from inner method */
@Validated
public void callMeInside(@Valid AnotherForm form) {
// stuff
}
}
J'espère que quelqu'un trouvera cela utile. Testé avec Spring 4.3, les choses peuvent donc être différentes pour les autres versions.
Comme indiqué ci-dessus, la spécification des groupes de validation n'est possible que via @Validated
annotation au niveau de la classe. Cependant, ce n'est pas très pratique car parfois vous avez une classe contenant plusieurs méthodes avec la même entité comme paramètre mais chacune nécessitant un sous-ensemble différent de propriétés pour valider. C'était aussi mon cas et ci-dessous, vous pouvez trouver plusieurs mesures à prendre pour le résoudre.
1) Implémentez une annotation personnalisée qui permet de spécifier des groupes de validation au niveau de la méthode en plus des groupes spécifiés via @Validated
au niveau de la classe.
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidatedGroups {
Class<?>[] value() default {};
}
2) Étendez la méthode MethodValidationInterceptor
et remplacez la méthode determineValidationGroups
comme suit.
@Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
final Class<?>[] classLevelGroups = super.determineValidationGroups(invocation);
final ValidatedGroups validatedGroups = AnnotationUtils.findAnnotation(
invocation.getMethod(), ValidatedGroups.class);
final Class<?>[] methodLevelGroups = validatedGroups != null ? validatedGroups.value() : new Class<?>[0];
if (methodLevelGroups.length == 0) {
return classLevelGroups;
}
final int newLength = classLevelGroups.length + methodLevelGroups.length;
final Class<?>[] mergedGroups = Arrays.copyOf(classLevelGroups, newLength);
System.arraycopy(methodLevelGroups, 0, mergedGroups, classLevelGroups.length, methodLevelGroups.length);
return mergedGroups;
}
3) Implémentez votre propre MethodValidationPostProcessor
(copiez simplement celui de Spring) et dans la méthode afterPropertiesSet
utilisez un intercepteur de validation implémenté à l'étape 2.
@Override
public void afterPropertiesSet() throws Exception {
Pointcut pointcut = new AnnotationMatchingPointcut(Validated.class, true);
Advice advice = (this.validator != null ? new ValidatedGroupsAwareMethodValidationInterceptor(this.validator) :
new ValidatedGroupsAwareMethodValidationInterceptor());
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
4) Enregistrez votre post-processeur de validation au lieu de Spring One.
<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/>
C'est ça. Vous pouvez maintenant l'utiliser comme suit.
@Validated(groups = Group1.class)
public class MyClass {
@ValidatedGroups(Group2.class)
public myMethod1(Foo foo) { ... }
public myMethod2(Foo foo) { ... }
...
}
Soyez prudent avec l'approche de rubensa.
Cela ne fonctionne que lorsque vous déclarez @Valid
comme seule annotation. Lorsque vous le combinez avec d'autres annotations comme @NotNull
tout sauf le @Valid
sera ignoré.
Le ce qui suit ne fonctionnera pas et le @NotNull
sera ignoré:
@Validated
public class MyClass {
@Validated(Group1.class)
public void myMethod1(@NotNull @Valid Foo foo) { ... }
@Validated(Group2.class)
public void myMethod2(@NotNull @Valid Foo foo) { ... }
}
En combinaison avec d'autres annotations, vous devez déclarer le javax.validation.groups.Default
Groupez aussi, comme ceci:
@Validated
public class MyClass {
@Validated({ Default.class, Group1.class })
public void myMethod1(@NotNull @Valid Foo foo) { ... }
@Validated({ Default.class, Group2.class })
public void myMethod2(@NotNull @Valid Foo foo) { ... }
}