Spring @Validated в слое сервиса

Хей,

Я хочу использовать @Validated(group=Foo.class) для проверки аргумента перед выполнением метода следующего типа:

public void doFoo(Foo @Validated(groups=Foo.class) foo){}

Когда я помещаю этот метод в контроллер моего приложения Spring, выполняется @Validated и выдает ошибку, если объект Foo недействителен. Однако, если я поместил одно и то же в методе на уровне службы моего приложения, проверка не выполняется, и метод запускается даже тогда, когда объект Foo недействителен.

Не можете ли вы использовать аннотацию @Validated на уровне сервиса? Или мне нужно настроить что-то дополнительное, чтобы заставить его работать?

Обновить:

Я добавил следующие два компонента в мой service.xml:

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

и заменил @Validate на @Null следующим образом:

public void doFoo(Foo @Null(groups=Foo.class) foo){}

Я знаю, что это довольно глупая аннотация, но я хотел бы проверить, что если я сейчас вызову метод и пройдя нуль, это приведет к исключению нарушения, которое оно делает. Итак, почему он выполняет аннотацию @Null а не аннотацию @Validate? Я знаю, что один из javax.validation а другой - с весны, но я не думаю, что это имеет к этому какое-то отношение?

Ответ 1

В глазах стека Spring MVC нет такого уровня, как сервисный уровень. Причина, по которой работает @Controller методов обработчиков класса является то, что Spring использует специальный HandlerMethodArgumentResolver под названием ModelAttributeMethodProcessor, который выполняет проверку, прежде чем разрешения аргумента для использования в методе обработчика.

Уровень сервиса, как мы его называем, представляет собой простой компонент, не добавив к нему дополнительного поведения из стека MVC (DispatcherServlet). Таким образом, вы не можете ожидать какой-либо проверки с весны. Вам нужно катиться самостоятельно, возможно, с АОП.


С помощью MethodValidationPostProcessor взгляните на javadoc

Применимые методы содержат аннотации ограничений JSR-303 по их параметрам и/или по их возвращаемому значению (в последнем случае, указанном на уровне метода, как правило, как встроенная аннотация).

Группы проверки могут быть заданы с помощью знака Spring Validated на уровне типа содержащего целевого класса, применимого ко всем общедоступным методам обслуживания этого класса. По умолчанию JSR-303 будет проверять только свою группу по умолчанию.

@Validated используется только для указания группы проверки, она сама не приводит к какой-либо проверке. Вы должны использовать один из javax.validation аннотаций как @Null или @Valid. Помните, что вы можете использовать столько аннотаций, сколько хотите, по параметру метода.

Ответ 2

@pgiecek Вам не нужно создавать новую аннотацию. Вы можете использовать:

@Validated
public class MyClass {

    @Validated({Group1.class})
    public myMethod1(@Valid Foo foo) { ... }

    @Validated({Group2.class})
    public myMethod2(@Valid Foo foo) { ... }

    ...
}

Ответ 3

Как указано выше, для указания групп проверки возможно только через @Validated аннотацию на уровне класса. Однако это не очень удобно, так как иногда у вас есть класс, содержащий несколько методов с тем же объектом, что и параметр, но каждый из которых требует проверки подмножества свойств. Это также было моим делом, и ниже вы можете найти несколько шагов для его решения.

1) Внедрение пользовательской аннотации, которая позволяет указывать группы проверки на уровне метода в дополнение к группам, указанным через @Validated на уровне класса.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidatedGroups {

    Class<?>[] value() default {};
}

2) Расширение MethodValidationInterceptor и переопределить determineValidationGroups метод следующим образом.

@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) Внедрите свой собственный MethodValidationPostProcessor (просто скопируйте Spring), а в методе afterPropertiesSet используйте afterPropertiesSet перехватчик, реализованный на шаге 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) Зарегистрируйте свой почтовый процессор проверки вместо Spring.

<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/> 

Это. Теперь вы можете использовать его следующим образом.

@Validated(groups = Group1.class)   
public class MyClass {

    @ValidatedGroups(Group2.class)
    public myMethod1(Foo foo) { ... }

    public myMethod2(Foo foo) { ... }

    ...
}

Ответ 4

Будьте осторожны с подходом rubensa.

Это работает, только когда вы объявляете @Valid как единственную аннотацию.

Следующие действия не будут работать, и @NotNull будет проигнорирован:

@Validated
public class MyClass {

    @Validated(Group1.class)
    public myMethod1(@NotNull @Valid Foo foo) { ... }

    @Validated(Group2.class)
    public myMethod2(@NotNull @Valid Foo foo) { ... }

}

В сочетании с другими аннотациями вам также необходимо объявить группу javax.validation.groups.Default следующим образом:

@Validated
public class MyClass {

    @Validated({ Default.class, Group1.class })
    public myMethod1(@NotNull @Valid Foo foo) { ... }

    @Validated({ Default.class, Group2.class })
    public myMethod2(@NotNull @Valid Foo foo) { ... }

}

Ответ 5

В качестве дополнительной заметки о Spring Validation для методов:

Поскольку Spring использует подход перехватчиков, сама проверка выполняется только тогда, когда вы разговариваете с методом Bean:

Когда вы разговариваете с экземпляром этого компонента через интерфейсы Validator Spring или JSR-303, вы будете обращаться к валидатору по умолчанию базового ValidatorFactory. Это очень удобно, потому что вам не нужно выполнять еще один звонок на заводе, предполагая, что вы почти всегда будете использовать валидатор по умолчанию.

Это важно, потому что, если вы пытаетесь реализовать проверку таким образом для вызовов методов внутри класса, это, судя по всему, не сработает. Например:

@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
    }        
}

Надеюсь, кто-то найдет это полезным. Протестировано с весной 4.3, поэтому для других версий могут быть разные.