Spring MVC - 自动查找验证器

时间:2015-03-07 14:29:05

标签: java spring validation spring-mvc

假设我有一个这样的样本实体类:

public class Address {
    ...
}

和相应的验证器:

@Component
public AddressValidator implements Validator {

    @Override
    public boolean supports(Class<?> entityClass) {
        return entityClass.equals(Address.class);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        ...
    }
}

当我使用如下控制器时,一切正常:

@RestController
@RequestMapping("/addresses")
public class AddressController {

    @Autowired
    private AddressValidator validator;

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(validator);
    }

    @RequestMapping(method=POST)
    public Long addNewAddress(@Valid @RequestBody Address address) {
        ...
    }
}

但是,如果省略验证器注册部分(即以下内容),则不会执行验证。

@Autowired
private AddressValidator validator;

@InitBinder
protected void initBinder(WebDataBinder binder) {
    binder.setValidator(validator);
}

手动注册验证器似乎毫无意义。我可以指示Spring自动查找验证器(类似于查找控制器的方式)吗?

它是一个基于Spring Boot的应用程序。

3 个答案:

答案 0 :(得分:2)

您可以使用gist或更低版本中的示例。我们的想法是拥有一个主要的CompositeValidator,它将成为所有Validator或SmartValidator实例的持有者。

它支持提示,也可以与Hibernate Annotation Validator(LocalValidatorFactoryBean)集成。并且每个特定模型可以有更多的验证器。

<强> CompositeValidator.java

@Component
public class CompositeValidator implements SmartValidator {
    @Autowired
    private List<Validator> validators = Collections.emptyList();

    @PostConstruct
    public void init() {
        Collections.sort(validators, AnnotationAwareOrderComparator.INSTANCE);
    }

    @Override
    public boolean supports(Class<?> clazz) {
         for (Validator validator : validators) {
            if (validator.supports(clazz)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void validate(Object target, Errors errors) {
        validate(target, errors, javax.validation.groups.Default.class);
    }

    @Override
    public void validate(Object target, Errors errors, Object... validationHints) {
        Class<?> clazz = target.getClass();

        for (Validator validator : validators) {
            if (validator.supports(clazz)) {
                if (validator instanceof SmartValidator) {
                    ((SmartValidator) validator).validate(target, errors, validationHints);
                } else {
                    validator.validate(target, errors);
                }
            }
        }
    }
}

<强> SomeController.java

@Controller
@RequestMapping("/my/resources")
public class SomeController {

    @RequestMapping(method = RequestMethod.POST)
    public Object save(
            @Validated(javax.validation.groups.Default.class) // this interface descriptor (class) is used by default
            @RequestBody MyResource myResource
    ) { return null; }
}

Java配置

 @Configuration
    public class WebConfig {
        /** used for Annotation based validation, it can be created by spring automaticaly and you don't do it manualy */
        // @Bean
        // public Validator jsr303Validator() {
        //   LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        //   // validator.setValidationMessageSource(...);
        //   return validator;
        // }

        @Bean
        public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
            return new WebMvcConfigurerAdapter() {
                @Autowired
                private CompositeValidator validator;

                @Override
                public Validator getValidator() {
                    return validator;
                }
            }
        }

或XML配置

<!-- used for Annotation based validation, it can be created by spring automaticaly and you don't do it manualy -->
<!--<bean id="jsr303Validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">-->
<!--  <property name="validationMessageSource" ref="messageSource"/>-->
<!--</bean>-->

<mvc:annotation-driven validator="compositeValidator">
  //...
</mvc:annotation-driven>

答案 1 :(得分:1)

您可以配置全局验证器。

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#validation-mvc

如果您使用基于Java的Spring配置和WebMvcConfigurationSupport,则可以覆盖getValidator()

/**
 * Override this method to provide a custom {@link Validator}.
 */
protected Validator getValidator() {
    return null;
}

您可以在全局WebBindingInitializer上调用setValidator(Validator)。这允许您跨所有@Controller类配置Validator实例。这可以通过使用Spring MVC命名空间轻松实现:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven validator="globalValidator"/>

</beans>

答案 2 :(得分:0)

我没有为此找到Spring解决方案的构建,但这就是我的工作。

我在spring java配置中声明我的验证器bean,如下所示:

@Bean
public Validator studentValidator(){
    return new StudentValidator();
}

@Bean
public Validator carValidator(){
    return new CarValidator();
}

然后我让所有控制器像这样扩展一个基本控制器:

public abstract class BaseController <T> {
     public BaseController(List<Validator> validators) {
        super();
        this.validators = validators;
      }
  //Register all validators
  @InitBinder
  protected void initBinder(WebDataBinder binder) {
    validators.stream().forEach(v->binder.addValidators(v));
  }


}

此控制器的具体类获取通过依赖注入注入的List,如下所示:

@Controller
public class StudentController extends BaseController<Student>{

    @Inject
      public StudentController(List<Validator> validators) {
        super(validators);
      }

}

基本控制器使用@InitBinder回调方法注册所有验证器。

我很惊讶Spring并没有自动注册实现Validator接口的类路径中的所有对象。