【问题标题】:Spring @Validated in service layer服务层中的Spring @Validated
【发布时间】:2013-10-25 20:30:35
【问题描述】:

嘿嘿,

我想在执行如下方法之前使用@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,但我认为这与它无关?

【问题讨论】:

    标签: java spring validation spring-mvc


    【解决方案1】:

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

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


    MethodValidationPostProcessor,看看javadoc

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

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

    @Validated 注释仅用于指定验证组,它本身并不强制任何验证。您需要使用javax.validation 注释之一,例如@Null@Valid。请记住,您可以在方法参数上使用任意数量的注释。

    【讨论】:

    【解决方案2】:

    作为方法的 Spring Validation 的附注:

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

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

    这很重要,因为如果您尝试以这种方式为类中的方法调用实现验证,它将无法正常工作。例如:

    @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 进行测试,因此其他版本可能会有所不同。

    【讨论】:

    • 从内部方法执行时我们应该怎么做才能使它工作?
    • @ammy 由于无法捕获类方法调用内部的 Spring AOP - 请阅读代理模式以了解这一点
    【解决方案3】:

    @pgiecek 你不需要创建一个新的注解。您可以使用:

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

    【讨论】:

    • 还要注意接口类型:MethodValidationInterceptor 使用接口类型来收集方法的注解。如果您有 @Validated(Group1.class) 对此组的实施验证将被跳过。
    • @pls,你为什么这么说?我对这个主题非常感兴趣。你能详细说明吗?它只是关于组还是一般验证?我将 Validated 放在一个实现上,并将 Valid 放在方法 args 上,它就像一个魅力。
    【解决方案4】:

    小心鲁本萨的做法。

    这仅在您将 @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 Group,如下所示:

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

    【讨论】:

    • 两者都不起作用,因为语法错误:) 你忘记了返回类型
    【解决方案5】:

    如上所述,指定验证组只能通过类级别的@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) { ... }
    
        ...
    }
    

    【讨论】:

    • 感谢分享。但是,鉴于其他答案在方法级别使用 @Validated 并指定组,这仍然相关吗?
    猜你喜欢
    • 2013-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-04
    • 1970-01-01
    • 2016-07-10
    相关资源
    最近更新 更多