web-dev-qa-db-ja.com

Spring @サービス層で検証済み

ヘジ、

@Validated(group=Foo.class)注釈を使用して、次のようなメソッドを実行する前に引数を検証します。

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

このメソッドをSpringアプリケーションのControllerに配置すると、@Validatedが実行され、Fooオブジェクトが無効な場合にエラーがスローされます。ただし、アプリケーションのサービスレイヤーのメソッドに同じものを配置すると、検証は実行されず、Fooオブジェクトが有効でない場合でもメソッドが実行されるだけです。

サービス層で@Validated注釈を使用できませんか?それとも、それを機能させるために余分なものを設定する必要がありますか?

更新:

Service.xmlに次の2つのBeanを追加しました。

<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アノテーションを実行するのでしょうか? 1つはjavax.validationからのものであり、もう1つはSpringからのものであることは知っていますが、それとは何の関係もないと思いますか?

20
Tom

Spring MVCスタックの目には、サービスレイヤーのようなものはありません。 @Controllerクラスハンドラーメソッドで機能する理由は、Springが、ハンドラーメソッドで使用する引数を解決する前に検証を実行するHandlerMethodArgumentResolverという特別なModelAttributeMethodProcessorを使用するためです。

私たちが呼ぶサービス層は、MVC(DispatcherServlet)スタックから追加の動作が追加されていない単なるBeanです。そのため、Springからの検証は期待できません。おそらくAOPを使用して、独自にロールする必要があります。


MethodValidationPostProcessorで、javadocを見てください

適用可能なメソッドには、パラメーターおよび/または戻り値にJSR-303制約注釈があります(後者の場合、メソッドレベルで、通常はインライン注釈として指定されます)。

検証グループは、含まれるターゲットクラスの型レベルでSpringのValidatedアノテーションを使用して指定でき、そのクラスのすべてのパブリックサービスメソッドに適用されます。デフォルトでは、JSR-303はデフォルトグループに対してのみ検証します。

@Validated注釈は検証グループを指定するためにのみ使用され、それ自体は検証を強制しません。 javax.validation@Nullなどの@Validアノテーションのいずれかを使用する必要があります。メソッドパラメーターには、必要な数の注釈を使用できることに注意してください。

13

@pgiecek新しい注釈を作成する必要はありません。次を使用できます。

@Validated
public class MyClass {

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

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

    ...
}
5
rubensa

メソッドの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でテストされているため、他のバージョンでは状況が異なる場合があります。

4
Vladimir Salin

前述のように、検証グループを指定することは、クラスレベルで@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 oneの代わりに検証ポストプロセッサを登録します。

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

それでおしまい。これで、次のように使用できます。

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

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

    public myMethod2(Foo foo) { ... }

    ...
}
4
pgiecek

ルーベンサのアプローチには注意してください。

これは、@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
thunderhook