私は、メソッドのパラメータや戻り値に対して異なる検証グループを使用したいと思います。私はこれを達成するためにSpringの@Validated注釈にしようとしています。例えば:
public interface UserOperations {
@Validated({ResponseGroup.class})
@Valid
User createUser(@Validated({RequestGroup.class}) @Valid User user);
}
戻り値が実際に照らして検証なっていることが表示されますResponseGroup
がメソッドの引数は、user
両方に対して検証なっているResponseGroup
とRequestGroup
。私が見たときにこれが起こっている理由を参照してください。org.springframework.validation.beanvalidation.MethodValidationInterceptor#invoke
戻り値にメソッドの引数と異なる検証グループに1検証グループを適用するためのエレガントな方法はありますか?
問題は方法があるということMethodValidationInterceptor.invoke
で定義された検証グループを探し@Validated
個々のパラメータに各メソッドに注釈、またはクラスではなく。指定された検証グループは、パラメータ並びに戻り値に適用されます。
別の検証グループは、パラメータと戻り値に適用されます持っているために、私はそれぞれの引数の戻り値と検証グループの検証グループを指定するために使用される2つのアノテーションを作成しました:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateReturnValue {
Class<?>[] value();
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateArguments {
Class<?>[] value();
}
今、検証グループの1セットは、戻り値を検証するために指定することができ、検証グループの異なるセットは、すべてのメソッドの引数に指定することができます。
@Validated
public interface MyClassWithMethodsToValidate {
@ValidateReturnValue({FooResponseGroup.class})
@ValidateArguments({FooRequestGroup.class})
FooResource createFoo(Foo foo);
}
処理すべきこれらの注釈のために、私は拡張するクラス作成MethodValidationInterceptor
のとオーバーライドinvoke
(コードの多くは、オーバーライドされたメソッドと同じである)の方法を
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.SmartFactoryBean;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.validation.beanvalidation.MethodValidationInterceptor;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.reflect.Method;
import java.util.Set;
class MyMethodValidationInterceptor extends MethodValidationInterceptor {
private final Validator validator;
public MyMethodValidationInterceptor() {
this(Validation.buildDefaultValidatorFactory().getValidator());
}
public MyMethodValidationInterceptor(Validator validator) {
super(validator);
this.validator = validator;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
Method methodToValidate = invocation.getMethod();
Class<?>[] parameterGroups = getArgumentsGroups(methodToValidate);
if(parameterGroups == null){
parameterGroups = super.determineValidationGroups(invocation);
}
Set<ConstraintViolation<Object>> violations;
try {
violations = validator.forExecutables().validateParameters(invocation.getThis(), methodToValidate, invocation.getArguments(), parameterGroups);
}
catch (IllegalArgumentException ex) {
methodToValidate = BridgeMethodResolver.findBridgedMethod(ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
violations = validator.forExecutables().validateParameters(invocation.getThis(), methodToValidate, invocation.getArguments(), parameterGroups);
}
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
Object returnValue = invocation.proceed();
Class<?>[] returnValueGroups = getReturnValueGroups(methodToValidate);
if(returnValueGroups == null){
returnValueGroups = super.determineValidationGroups(invocation);
}
violations = validator.forExecutables().validateReturnValue(invocation.getThis(), methodToValidate, returnValue, returnValueGroups);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
return returnValue;
}
private Class<?>[] getReturnValueGroups(Method methodToValidate){
ValidateReturnValue annotation = AnnotationUtils.findAnnotation(methodToValidate, ValidateReturnValue.class);
if(annotation != null){
return annotation.value();
}else {
return null;
}
}
private Class<?>[] getArgumentsGroups(Method methodToValidate){
ValidateArguments annotation = AnnotationUtils.findAnnotation(methodToValidate, ValidateArguments.class);
if(annotation != null){
return annotation.value();
}else {
return null;
}
}
private boolean isFactoryBeanMetadataMethod(Method method) {
Class<?> clazz = method.getDeclaringClass();
if (clazz.isInterface()) {
return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) &&
!method.getName().equals("getObject"));
}
Class<?> factoryBeanType = null;
if (SmartFactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = SmartFactoryBean.class;
}
else if (FactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = FactoryBean.class;
}
return (factoryBeanType != null && !method.getName().equals("getObject") &&
ClassUtils.hasMethod(factoryBeanType, method));
}
}
それから私は、拡張するクラスを作成MethodValidationPostProcessor
し、その上書きされますcreateMethodValidationAdvice
新しい使用する方法MyMethodValidationInterceptor
クラス:
class MyMethodValidationPostProcessor extends MethodValidationPostProcessor {
@Override
protected Advice createMethodValidationAdvice(Validator validator) {
return (validator != null ? new MyMethodValidationInterceptor(validator) : new MyMethodValidationInterceptor());
}
}
最後に、のBeanを構成します MyMethodValidationPostProcessor
@Configuration
public class MethodValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MyMethodValidationPostProcessor();
}
}