ヘジ、
@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からのものであることは知っていますが、それとは何の関係もないと思いますか?
Spring MVCスタックの目には、サービスレイヤーのようなものはありません。 @Controller
クラスハンドラーメソッドで機能する理由は、Springが、ハンドラーメソッドで使用する引数を解決する前に検証を実行するHandlerMethodArgumentResolver
という特別なModelAttributeMethodProcessor
を使用するためです。
私たちが呼ぶサービス層は、MVC(DispatcherServlet
)スタックから追加の動作が追加されていない単なるBeanです。そのため、Springからの検証は期待できません。おそらくAOPを使用して、独自にロールする必要があります。
MethodValidationPostProcessor
で、javadocを見てください
適用可能なメソッドには、パラメーターおよび/または戻り値にJSR-303制約注釈があります(後者の場合、メソッドレベルで、通常はインライン注釈として指定されます)。
検証グループは、含まれるターゲットクラスの型レベルでSpringのValidatedアノテーションを使用して指定でき、そのクラスのすべてのパブリックサービスメソッドに適用されます。デフォルトでは、JSR-303はデフォルトグループに対してのみ検証します。
@Validated
注釈は検証グループを指定するためにのみ使用され、それ自体は検証を強制しません。 javax.validation
や@Null
などの@Valid
アノテーションのいずれかを使用する必要があります。メソッドパラメーターには、必要な数の注釈を使用できることに注意してください。
@pgiecek新しい注釈を作成する必要はありません。次を使用できます。
@Validated
public class MyClass {
@Validated({Group1.class})
public myMethod1(@Valid Foo foo) { ... }
@Validated({Group2.class})
public myMethod2(@Valid Foo foo) { ... }
...
}
メソッドの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でテストされているため、他のバージョンでは状況が異なる場合があります。
前述のように、検証グループを指定することは、クラスレベルで@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) { ... }
...
}
ルーベンサのアプローチには注意してください。
これは、@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) { ... }
}