Spring 検証と JSR-303 制約を組み合わせるのはかなり複雑です。そして、「すぐに使える」方法はありません。主な不都合は、Spring 検証が を使用しBindingResult
、JSR-303ConstraintValidatorContext
が検証の結果として使用することです。
Spring AOP を使用して、独自の検証エンジンを作成することができます。そのために何をする必要があるかを考えてみましょう。まず、AOP の依存関係を宣言します (まだ行っていない場合)。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
私はバージョンのSpringを4.2.4.RELEASE
使用していますが、もちろん自分のものを使用できます。アスペクト アノテーションを使用するには、AspectJ が必要です。次のステップでは、単純なバリデータ レジストリを作成する必要があります。
public class CustomValidatorRegistry {
private List<Validator> validatorList = new ArrayList<>();
public void addValidator(Validator validator){
validatorList.add(validator);
}
public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for(Validator validator : validatorList){
if(validator.supports(o.getClass())){
result.add(validator);
}
}
return result;
}
}
ご覧のとおり、これは非常に単純なクラスであり、オブジェクトのバリデーターを見つけることができます。検証が必要なメソッドをマークするアノテーションを作成しましょう。
package com.mydomain.validation;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomValidation {
}
標準BindingException
クラスは is notRuntimeException
であるため、オーバーライドされたメソッドでは使用できません。これは、独自の例外を定義する必要があることを意味します。
public class CustomValidatorException extends RuntimeException {
private BindingResult bindingResult;
public CustomValidatorException(BindingResult bindingResult){
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
これで、ほとんどの作業を行うアスペクトを作成する準備が整いました。アスペクトは、CustomValidation
アノテーションでマークされたメソッドの前に実行されます。
@Aspect
@Component
public class CustomValidatingAspect {
@Autowired
private CustomValidatorRegistry registry; //aspect will use our validator registry
@Before(value = "execution(public * *(..)) && annotation(com.mydomain.validation.CustomValidation)")
public void doBefore(JoinPoint point){
Annotation[][] paramAnnotations =
((MethodSignature)point.getSignature()).getMethod().getParameterAnnotations();
for(int i=0; i<paramAnnotations.length; i++){
for(Annotation annotation : paramAnnotations[i]){
//checking for standard org.springframework.validation.annotation.Validated
if(annotation.annotationType() == Validated.class){
Object arg = point.getArgs()[i];
if(arg==null) continue;
validate(arg);
}
}
}
}
private void validate(Object arg) {
List<Validator> validatorList = registry.getValidatorsForObject(arg);
for(Validator validator : validatorList){
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if(errors.hasErrors()){
throw new CustomValidatorException(errors);
}
}
}
}
execution(public * *(..)) && @annotation(com.springapp.mvc.validators.CustomValidation)
つまり、この側面は、@CustomValidation
注釈でマークされた Bean のすべてのパブリック メソッドに適用されます。また、検証済みのパラメーターをマークするために、標準のorg.springframework.validation.annotation.Validated
注釈を使用していることにも注意してください。しかし、当然のことながら、私たちはカスタムを行うことができました. アスペクトの他のコードは非常にシンプルで、コメントは不要だと思います。サンプルバリデータの追加コード:
public class PersonValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return aClass==Person.class;
}
@Override
public void validate(Object o, Errors errors) {
Person person = (Person)o;
if(person.getAge()<=0){
errors.rejectValue("age", "Age is too small");
}
}
}
これで、構成を調整し、すべて使用する準備が整いました。
@Configuration
@ComponentScan(basePackages = "com.mydomain")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig{
.....
@Bean
public CustomValidatorRegistry validatorRegistry(){
CustomValidatorRegistry registry = new CustomValidatorRegistry();
registry.addValidator(new PersonValidator());
return registry;
}
}
クラスプロキシをproxyTargetClass
使用true
するためです。cglib
サービス クラスのターゲット メソッドの例:
@Service
public class PersonService{
@CustomValidation
public void savePerson(@Validated Person person){
....
}
}
アノテーションのため@CustomValidation
アスペクトが適用され、@Validated
アノテーションのためperson
検証されます。コントローラー(または他のクラス)でのサービスの使用例:
@Controller
public class PersonConroller{
@Autowired
private PersonService service;
public String savePerson(@ModelAttribute Person person, ModelMap model){
try{
service.savePerson(person);
}catch(CustomValidatorException e){
model.addAttribute("errors", e.getBindingResult());
return "viewname";
}
return "viewname";
}
}
@CustomValidation
クラスのメソッドから呼び出す場合PersonService
、検証が機能しないことに注意してください。元のクラスのメソッドを呼び出しますが、プロキシは呼び出さないためです。つまり、検証を機能させたい場合 (例: @Transactional works same way
)、このメソッドをクラスの外部 (他のクラス) からのみ呼び出すことができます。
長文すみません。私の答えは「単純な宣言的な方法」に関するものではなく、必要ない可能性があります。しかし、私はこの問題を解決したいと思っていました。