java - validations - Spring @Validated en capa de servicio
spring validation required (5)
Hej
Quiero usar la @Validated(group=Foo.class)
para validar un argumento antes de ejecutar un método como el siguiente:
public void doFoo(Foo @Validated(groups=Foo.class) foo){}
Cuando coloco este método en el Controlador de mi aplicación Spring, @Validated
se ejecuta y @Validated
un error cuando el objeto Foo no es válido. Sin embargo, si pongo lo mismo en un método en la capa de servicio de mi aplicación, la validación no se ejecuta y el método simplemente se ejecuta incluso cuando el objeto Foo no es válido.
¿No puede utilizar la anotación @Validated
en la capa de servicio? ¿O tengo que configurar algo extra para que funcione?
Actualizar:
He agregado los siguientes dos beans a mi service.xml:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
y reemplazó el @Validate
con @Null
así:
public void doFoo(Foo @Null(groups=Foo.class) foo){}
Sé que es una anotación bastante tonta, pero quería comprobar que si llamo al método ahora y pasa nulo, se generará una excepción de infracción, lo que sí ocurre. Entonces, ¿por qué ejecuta la anotación @Null
y no la anotación @Validate
? Sé que una es de javax.validation
y la otra es de Spring, pero no creo que tenga nada que ver con eso.
@pgiecek No es necesario crear una nueva anotación. Puedes usar:
@Validated
public class MyClass {
@Validated({Group1.class})
public myMethod1(@Valid Foo foo) { ... }
@Validated({Group2.class})
public myMethod2(@Valid Foo foo) { ... }
...
}
A los ojos de una pila Spring MVC, no existe una capa de servicio. La razón por la que funciona para los métodos del controlador de clase @Controller
es que Spring usa un HandlerMethodArgumentResolver
especial llamado ModelAttributeMethodProcessor
que realiza la validación antes de resolver el argumento para usar en su método de controlador.
La capa de servicio, como la llamamos, es solo un bean sin agregación de comportamiento adicional de la pila MVC ( DispatcherServlet
). Como tal, no puede esperar ninguna validación de Spring. Necesitas rodar el tuyo, probablemente con AOP.
Con MethodValidationPostProcessor
, eche un vistazo al javadoc
Los métodos aplicables tienen anotaciones de restricción JSR-303 en sus parámetros y / o en su valor de retorno (en el último caso especificado en el nivel de método, generalmente como anotación en línea).
Los grupos de validación se pueden especificar a través de la anotación Validada de Spring en el nivel de tipo de la clase objetivo que contiene, aplicándose a todos los métodos de servicio público de esa clase. De forma predeterminada, JSR-303 se validará solo en su grupo predeterminado.
La anotación @Validated
solo se usa para especificar un grupo de validación, no fuerza por sí misma ninguna validación. javax.validation
usar una de las anotaciones de @Null
como @Null
o @Valid
. Recuerde que puede usar tantas anotaciones como desee en un parámetro de método.
Como nota al margen sobre la validación de primavera para los métodos
Dado que Spring utiliza interceptores en su enfoque, la validación en sí misma solo se realiza cuando se está hablando con un método de Bean:
Cuando hable con una instancia de este bean a través de las interfaces Spring o JSR-303 Validator, hablará con el Validator predeterminado de la ValidatorFactory subyacente. Esto es muy conveniente ya que no tiene que realizar otra llamada en la fábrica, suponiendo que casi siempre usará el Validador predeterminado de todos modos.
Esto es importante porque si intenta implementar una validación de tal manera para las llamadas de método dentro de la clase, no funcionará. P.ej:
@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
}
}
Espero que alguien encuentre esto útil. Probado con Spring 4.3, por lo que las cosas podrían ser diferentes para otras versiones.
Como se indicó anteriormente, para especificar grupos de validación es posible solo a través de la anotación @Validated
a nivel de clase. Sin embargo, no es muy conveniente ya que a veces tiene una clase que contiene varios métodos con la misma entidad como parámetro, pero cada uno de los cuales requiere validar un subconjunto diferente de propiedades. También fue mi caso y a continuación puede encontrar varios pasos a seguir para resolverlo.
1) Implementar una anotación personalizada que permita especificar grupos de validación a nivel de método además de los grupos especificados a través de @Validated
a nivel de clase.
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidatedGroups {
Class<?>[] value() default {};
}
2) Extienda MethodValidationInterceptor
y anule el método determineValidationGroups
siguiente manera.
@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) Implemente su propio MethodValidationPostProcessor
(solo copie el Spring one) y en el método afterPropertiesSet
utilice el interceptor de validación implementado en el paso 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) Registre su post procesador de validación en lugar de Spring One.
<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/>
Eso es. Ahora puedes usarlo de la siguiente manera.
@Validated(groups = Group1.class)
public class MyClass {
@ValidatedGroups(Group2.class)
public myMethod1(Foo foo) { ... }
public myMethod2(Foo foo) { ... }
...
}
Ten cuidado con el enfoque de rubensa.
Esto solo funciona cuando declara @Valid
como la única anotación. Cuando lo combine con otras anotaciones como @NotNull
todo, excepto el @Valid
, será ignorado.
Lo siguiente no funcionará y se ignorará @NotNull
:
@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 combinación con otras anotaciones, debe declarar también el Grupo javax.validation.groups.Default
, como este:
@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) { ... }
}