Spring @Validated在服务层

时间:2013-10-17 11:16:19

标签: java spring validation spring-mvc

HeJ小鼠,

我想在执行如下方法之前使用@Validated(group=Foo.class)注释来验证参数:

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

当我将此方法放在我的Spring应用程序的Controller中时,执行@Validated并在Foo对象无效时抛出错误。但是,如果我在应用程序的Service层中的方法中放置相同的东西,则不执行验证,即使Foo对象无效,该方法也会运行。

您不能在服务层中使用@Validated注释吗?或者我是否必须配置额外的东西以使其有效?

更新

我已将以下两个bean添加到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,它将抛出违规异常。那么为什么它会执行@Null注释而不是@Validate注释呢?我知道一个来自javax.validation而另一个来自Spring,但我不认为这与它有什么关系?

5 个答案:

答案 0 :(得分:11)

在Spring MVC堆栈的眼中,没有服务层这样的东西。它适用于@Controller类处理程序方法的原因是Spring使用一个名为HandlerMethodArgumentResolver的特殊ModelAttributeMethodProcessor,它在解析要在处理程序方法中使用的参数之前执行验证。

我们称之为服务层,它只是一个普通的bean,没有从MVC(DispatcherServlet)堆栈添加任何额外行为。因此,您不能指望Spring的任何验证。你需要自己动手,可能是AOP。


使用MethodValidationPostProcessor,查看javadoc

  

适用的方法在其上有JSR-303约束注释   参数和/或它们的返回值(在后一种情况下指定   在方法级别,通常作为内联注释)。

     

可以通过Spring的Validated指定验证组   适用于包含目标类的类型级别的注释   该类的所有公共服务方法。默认情况下,JSR-303会   仅针对其默认组进行验证。

@Validated注释仅用于指定验证组,它本身不会强制进行任何验证。您需要使用其中一个javax.validation注释,例如@Null@Valid。请记住,您可以在方法参数上使用尽可能多的注释。

答案 1 :(得分:5)

@pgiecek您不需要创建新的注释。您可以使用:

@Validated
public class MyClass {

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

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

    ...
}

答案 2 :(得分:4)

如上所述,只能通过类级别的@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中使用步骤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) { ... }

    ...
}

答案 3 :(得分:4)

小心使用rubensa的方法。

这仅在您将@Valid声明为唯一注释时才有效。当您将其与@NotNull之类的其他注释结合使用时,@Valid以外的所有内容都将被忽略。

以下无法使用@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) { ... }

}

结合其他注释,您还需要声明javax.validation.groups.Default组,如下所示:

@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) { ... }

}

答案 4 :(得分:2)

作为方法的Spring Validation的附注:

由于Spring在其方法中使用了拦截器,因此验证本身仅在您使用Bean的方法时执行:

  

当通过Spring或JSR-303 Validator接口与该bean的实例进行通信时,您将与底层ValidatorFactory的默认Validator进行通信。这非常方便,因为您不必在工厂执行另一个调用,假设您几乎总是会使用默认的Validator。

这一点非常重要,因为如果您尝试以类似方式实现类中的方法调用,那么它将无法正常工作。 E.g:

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

希望有人觉得这很有帮助。使用Spring 4.3进行测试,因此其他版本可能会有所不同。