9

JSON メッセージをスプリングに送信するために Java Bean を使用してい@RestControllerます@Valid。しかし、私は Protobuf/Thrift に移行し、REST から離れたいと考えています。これは内部 API であり、多くの大企業が内部で REST を廃止しています。これが実際に意味することは、メッセージ オブジェクトを制御できなくなったことです。メッセージ オブジェクトは外部で生成されたものです。もう注釈を付けることはできません。

したがって、私の検証はプログラムで行う必要があります。どうすればいいですか?私はaをコーディングしましたがValidator、うまく機能します。@Validしかし、niceアノテーションは使用しません。私は次のことをしなければなりません:

@Service
public StuffEndpoint implements StuffThriftDef.Iface {

    @Autowired
    private MyValidator myValidator;

    public void things(MyMessage msg) throws BindException {
        BindingResult errors = new BeanPropertyBindingResult(msg, msg.getClass().getName());
        errors = myValidator.validate(msg);
        if (errors.hasErrors()) {
            throw new BindException(errors);
        } else {
            doRealWork();
        }
    }
}

これは臭い。私はすべての方法でこれを行う必要があります。これで、多くのことをスローする 1 つのメソッドに入れることができBindException、すべてのメソッドに 1 行のコードを追加することができます。しかし、それはまだ素晴らしいことではありません。

私が欲しいのは、次のように見えることです:

@Service
@Validated
public StuffEndpoint implements StuffThriftDef.Iface {

    public void things(@Valid MyMessage msg) {
        doRealWork();
    }
}

それでも同じ結果が得られます。私の Bean には注釈がないことを思い出してください。はい、@InitBinderメソッドで注釈を使用できることはわかっています。ただし、これは Web リクエストに対してのみ機能します。

このクラスに正しいものを注入することは気にしませんが、ValidatorFactory がメソッドValidatorに基づいて正しいものを引き出すことができればと思います。supports()

これは可能ですか?代わりに実際に Spring 検証を使用するように Bean 検証を構成する方法はありますか? どこかでアスペクトをハイジャックする必要がありますか? LocalValidatorFactoryまたはにハックしMethodValidationPostProcessorますか?

ありがとう。

4

3 に答える 3

16

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)、このメソッドをクラスの外部 (他のクラス) からのみ呼び出すことができます。

長文すみません。私の答えは「単純な宣言的な方法」に関するものではなく、必要ない可能性があります。しかし、私はこの問題を解決したいと思っていました。

于 2016-03-19T12:42:07.883 に答える
2

それが誰かを助けることを願っています。次の構成を追加することで機能するようになりました。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class ValidatorConfiguration {

    @Bean
    public MethodValidationPostProcessor getMethodValidationPostProcessor(){
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
         processor.setValidator(this.validator());
         return processor;
     }

     @Bean
     public LocalValidatorFactoryBean validator(){
         return new LocalValidatorFactoryBean();
     }

 }

次に、サービスに同じ方法で注釈が付けられ (クラスに @Validated、パラメーターに @Valid)、別の Bean に注入して、メソッドを直接呼び出すことができ、検証が行われます。

于 2017-01-04T19:31:05.797 に答える