将@Validated 和@Valid 与spring 验证器一起使用
Use @Validated and @Valid with spring validator
我有一个 java bean 用于向 spring @RestController
发送 JSON 消息,我有 bean 验证设置和 运行 就好了使用 @Valid
。但我想转向 Protobuf/Thrift 并远离 REST。它是一个内部 API,许多大公司已经在内部废除了 REST。这真正意味着我不再控制消息对象——它们是在外部生成的。我不能再给它们添加注释了。
所以现在我的验证必须是程序化的。我该怎么做呢?我编写了一个 Validator
并且效果很好。但它没有使用漂亮的 @Valid
注释。我必须执行以下操作:
@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();
}
}
}
这太臭了。我必须在每一种方法中都这样做。现在,我可以将其中的很多内容放入一个抛出 BindException
的方法中,这样就可以将一行代码添加到每个方法中。但这仍然不是很好。
我想要的是看到它看起来像这样:
@Service
@Validated
public StuffEndpoint implements StuffThriftDef.Iface {
public void things(@Valid MyMessage msg) {
doRealWork();
}
}
仍然得到相同的结果。请记住,我的 bean 没有注释。是的,我知道我可以在方法上使用 @InitBinder
注释。但这仅适用于网络请求。
我不介意将正确的 Validator
注入此 class,但我更希望我的 ValidatorFactory 可以根据 supports()
方法提取正确的 [=]。 23=]
这可能吗?有没有办法将 bean 验证配置为实际使用 Spring 验证?我必须在某处劫持一个方面吗?侵入 LocalValidatorFactory
或 MethodValidationPostProcessor
?
谢谢。
将 Spring 验证和 JSR-303 约束相结合是一件相当复杂的事情。而且没有 'ready to use' 方式。主要的不便之处在于 Spring 验证使用 BindingResult
,而 JSR-303 使用 ConstraintValidatorContext
作为验证结果。
您可以尝试使用 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;
}
}
如您所见,它非常简单class,它允许我们找到对象的验证器。现在让我们创建注释,这将是需要验证的标记方法:
package com.mydomain.validation;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomValidation {
}
因为标准 BindingException
class 不是 RuntimeException
,我们不能在覆盖方法中使用它。这意味着我们需要定义我们自己的异常:
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)
意味着,这个方面将应用于 bean 的任何 public 方法,这些方法用 @CustomValidation
注释标记。另请注意,为了标记经过验证的参数,我们使用标准 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
class 代理。
服务中的目标方法示例 class:
@Service
public class PersonService{
@CustomValidation
public void savePerson(@Validated Person person){
....
}
}
因为 @CustomValidation
注释方面将被应用,并且因为 @Validated
注释 person
将被验证。以及在控制器(或任何其他 class)中使用服务的示例:
@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";
}
}
请记住,如果您从 PersonService
class 的方法中调用 @CustomValidation
,验证将不起作用。因为它会调用原始 class 的方法,而不是代理。这意味着,如果您希望验证有效(例如 @Transactional works same way
),您只能从 class 外部(从其他 classes)调用此方法(例如 @Transactional works same way
)。
很抱歉 post。我的回答与 'simple declarative way' 无关,您可能不需要它。但我很好奇解决了这个问题。
我将@Ken 的回答标记为正确,因为它是正确的。但我更进一步,想 post 我所做的。我希望任何访问此页面的人都会发现它很有趣。我可能会尝试将它展示在 Spring 人面前,看看它是否会包含在未来的版本中。
想法是用一个新的注释来替换 @Valid
。所以我称之为@SpringValid
。使用此注释将启动上面组合的系统。以下是所有作品:
SpringValid.java
package org.springframework.validation.annotation;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface SpringValid {
}
SpringValidationAspect.java
package org.springframework.validation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Aspect
@Component
public class SpringValidationAspect {
private SpringValidatorRegistry springValidatorRegistry;
@Autowired
public SpringValidationAspect(final SpringValidatorRegistry springValidatorRegistry) {
this.springValidatorRegistry = springValidatorRegistry;
}
public SpringValidatorRegistry getSpringValidatorRegistry() {
return springValidatorRegistry;
}
@Before("@target(org.springframework.validation.annotation.Validated) "
+ "&& execution(public * *(@org.springframework.validation.annotation.SpringValid (*), ..)) "
+ "&& args(validationTarget)")
public void beforeMethodThatNeedsValidation(Object validationTarget) {
validate(validationTarget);
}
private void validate(Object arg) {
List<Validator> validatorList = springValidatorRegistry.getValidatorsForObject(arg);
for (Validator validator : validatorList) {
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if (errors.hasErrors()) {
throw new SpringValidationException(errors);
}
}
}
}
Spring 的示例显示 classes 用 @Validated
注释,所以我想保留它。上述方面仅针对 class 具有 @Validated
的 class 级别。而且,就像您使用 @Valid
时一样,它会查找固定在方法参数上的 @SpringValid
注释。
SpringValidationException.java
package org.springframework.validation;
import org.springframework.validation.BindingResult;
public class SpringValidationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private BindingResult bindingResult;
public SpringValidationException(final BindingResult bindingResult) {
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
SpringValidatorRegistry.java
package org.springframework.validation;
import org.springframework.validation.Validator;
import java.util.ArrayList;
import java.util.List;
public class SpringValidatorRegistry {
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;
}
}
就像第一个答案一样,一个注册所有实现 Spring 的 org.springframework.validation.Validator
接口的 classes 的地方。
SpringValidator.java
package org.springframework.validation.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface SpringValidator {
}
这只是额外的调味料,可以让 register/find Validators
更容易。您可以手动注册所有 Validators
,也可以通过反射找到它们。所以这部分不是必需的,我只是觉得它让事情变得更容易。
MyConfig.java
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.SpringValidationAspect;
import org.springframework.validation.SpringValidatorRegistry;
import org.springframework.validation.annotation.SpringValidator;
import java.util.Map;
import javax.validation.Validator;
@Configuration
public class MyConfig {
@Autowired
private ApplicationContext applicationContext;
@Bean
public SpringValidatorRegistry validatorRegistry() {
SpringValidatorRegistry registry = new SpringValidatorRegistry();
Map<String, Object> validators =
applicationContext.getBeansWithAnnotation(SpringValidator.class);
validators.values()
.forEach(v -> registry.addValidator((org.springframework.validation.Validator) v));
return registry;
}
@Bean
public SpringValidationAspect springValidationAspect() {
return new SpringValidationAspect(validatorRegistry());
}
}
看,扫描您的 class 路径并查找 @SpringValidator
classes 并注册它们。然后注册看点然后离开。
以下是此类验证器的示例:
MyMessageValidator.java
package com.example.validators;
import com.example.messages.MyMessage;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.SpringValidator;
@SpringValidator
public class MyMessageValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return MyMessage.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "firstField", "{javax.validation.constraints.NotNull}",
"firstField cannot be null");
MyMessage obj = (MyMessage) target;
if (obj.getSecondField != null && obj.getSecondField > 100) {
errors.rejectField(errors, "secondField", "{javax.validation.constraints.Max}", "secondField is too big");
}
}
}
这里是使用 @SpringValid
注释的服务 class:
MyService.java
package com.example.services;
import com.example.messages.MyMessage;
import org.springframework.validation.annotation.SpringValid;
import org.springframework.validation.annotation.Validated;
import javax.inject.Inject;
@Validated
public class MyService {
public String doIt(@SpringValid final MyMessage msg) {
return "we did it!";
}
}
希望这在某些时候对某些人有意义。我个人觉得还是蛮好用的。许多公司开始将他们的内部 API 从 REST 转移到 Protobuf 或 Thrift 之类的东西。您仍然可以使用 Bean Validation,但您必须使用 XML,而且它并不是那么好。所以我希望这对仍然想进行程序化验证的人有所帮助。
希望对大家有所帮助。我通过添加以下配置让它工作:
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 在 class 和 @Valid 在参数上)并且可以注入到另一个 bean 中,在那里可以直接调用该方法并进行验证。
我有一个 java bean 用于向 spring @RestController
发送 JSON 消息,我有 bean 验证设置和 运行 就好了使用 @Valid
。但我想转向 Protobuf/Thrift 并远离 REST。它是一个内部 API,许多大公司已经在内部废除了 REST。这真正意味着我不再控制消息对象——它们是在外部生成的。我不能再给它们添加注释了。
所以现在我的验证必须是程序化的。我该怎么做呢?我编写了一个 Validator
并且效果很好。但它没有使用漂亮的 @Valid
注释。我必须执行以下操作:
@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();
}
}
}
这太臭了。我必须在每一种方法中都这样做。现在,我可以将其中的很多内容放入一个抛出 BindException
的方法中,这样就可以将一行代码添加到每个方法中。但这仍然不是很好。
我想要的是看到它看起来像这样:
@Service
@Validated
public StuffEndpoint implements StuffThriftDef.Iface {
public void things(@Valid MyMessage msg) {
doRealWork();
}
}
仍然得到相同的结果。请记住,我的 bean 没有注释。是的,我知道我可以在方法上使用 @InitBinder
注释。但这仅适用于网络请求。
我不介意将正确的 Validator
注入此 class,但我更希望我的 ValidatorFactory 可以根据 supports()
方法提取正确的 [=]。 23=]
这可能吗?有没有办法将 bean 验证配置为实际使用 Spring 验证?我必须在某处劫持一个方面吗?侵入 LocalValidatorFactory
或 MethodValidationPostProcessor
?
谢谢。
将 Spring 验证和 JSR-303 约束相结合是一件相当复杂的事情。而且没有 'ready to use' 方式。主要的不便之处在于 Spring 验证使用 BindingResult
,而 JSR-303 使用 ConstraintValidatorContext
作为验证结果。
您可以尝试使用 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;
}
}
如您所见,它非常简单class,它允许我们找到对象的验证器。现在让我们创建注释,这将是需要验证的标记方法:
package com.mydomain.validation;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomValidation {
}
因为标准 BindingException
class 不是 RuntimeException
,我们不能在覆盖方法中使用它。这意味着我们需要定义我们自己的异常:
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)
意味着,这个方面将应用于 bean 的任何 public 方法,这些方法用 @CustomValidation
注释标记。另请注意,为了标记经过验证的参数,我们使用标准 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
class 代理。
服务中的目标方法示例 class:
@Service
public class PersonService{
@CustomValidation
public void savePerson(@Validated Person person){
....
}
}
因为 @CustomValidation
注释方面将被应用,并且因为 @Validated
注释 person
将被验证。以及在控制器(或任何其他 class)中使用服务的示例:
@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";
}
}
请记住,如果您从 PersonService
class 的方法中调用 @CustomValidation
,验证将不起作用。因为它会调用原始 class 的方法,而不是代理。这意味着,如果您希望验证有效(例如 @Transactional works same way
),您只能从 class 外部(从其他 classes)调用此方法(例如 @Transactional works same way
)。
很抱歉 post。我的回答与 'simple declarative way' 无关,您可能不需要它。但我很好奇解决了这个问题。
我将@Ken 的回答标记为正确,因为它是正确的。但我更进一步,想 post 我所做的。我希望任何访问此页面的人都会发现它很有趣。我可能会尝试将它展示在 Spring 人面前,看看它是否会包含在未来的版本中。
想法是用一个新的注释来替换 @Valid
。所以我称之为@SpringValid
。使用此注释将启动上面组合的系统。以下是所有作品:
SpringValid.java
package org.springframework.validation.annotation;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface SpringValid {
}
SpringValidationAspect.java
package org.springframework.validation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Aspect
@Component
public class SpringValidationAspect {
private SpringValidatorRegistry springValidatorRegistry;
@Autowired
public SpringValidationAspect(final SpringValidatorRegistry springValidatorRegistry) {
this.springValidatorRegistry = springValidatorRegistry;
}
public SpringValidatorRegistry getSpringValidatorRegistry() {
return springValidatorRegistry;
}
@Before("@target(org.springframework.validation.annotation.Validated) "
+ "&& execution(public * *(@org.springframework.validation.annotation.SpringValid (*), ..)) "
+ "&& args(validationTarget)")
public void beforeMethodThatNeedsValidation(Object validationTarget) {
validate(validationTarget);
}
private void validate(Object arg) {
List<Validator> validatorList = springValidatorRegistry.getValidatorsForObject(arg);
for (Validator validator : validatorList) {
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if (errors.hasErrors()) {
throw new SpringValidationException(errors);
}
}
}
}
Spring 的示例显示 classes 用 @Validated
注释,所以我想保留它。上述方面仅针对 class 具有 @Validated
的 class 级别。而且,就像您使用 @Valid
时一样,它会查找固定在方法参数上的 @SpringValid
注释。
SpringValidationException.java
package org.springframework.validation;
import org.springframework.validation.BindingResult;
public class SpringValidationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private BindingResult bindingResult;
public SpringValidationException(final BindingResult bindingResult) {
this.bindingResult = bindingResult;
}
public BindingResult getBindingResult() {
return bindingResult;
}
}
SpringValidatorRegistry.java
package org.springframework.validation;
import org.springframework.validation.Validator;
import java.util.ArrayList;
import java.util.List;
public class SpringValidatorRegistry {
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;
}
}
就像第一个答案一样,一个注册所有实现 Spring 的 org.springframework.validation.Validator
接口的 classes 的地方。
SpringValidator.java
package org.springframework.validation.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface SpringValidator {
}
这只是额外的调味料,可以让 register/find Validators
更容易。您可以手动注册所有 Validators
,也可以通过反射找到它们。所以这部分不是必需的,我只是觉得它让事情变得更容易。
MyConfig.java
package com.example.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.SpringValidationAspect;
import org.springframework.validation.SpringValidatorRegistry;
import org.springframework.validation.annotation.SpringValidator;
import java.util.Map;
import javax.validation.Validator;
@Configuration
public class MyConfig {
@Autowired
private ApplicationContext applicationContext;
@Bean
public SpringValidatorRegistry validatorRegistry() {
SpringValidatorRegistry registry = new SpringValidatorRegistry();
Map<String, Object> validators =
applicationContext.getBeansWithAnnotation(SpringValidator.class);
validators.values()
.forEach(v -> registry.addValidator((org.springframework.validation.Validator) v));
return registry;
}
@Bean
public SpringValidationAspect springValidationAspect() {
return new SpringValidationAspect(validatorRegistry());
}
}
看,扫描您的 class 路径并查找 @SpringValidator
classes 并注册它们。然后注册看点然后离开。
以下是此类验证器的示例: MyMessageValidator.java
package com.example.validators;
import com.example.messages.MyMessage;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.SpringValidator;
@SpringValidator
public class MyMessageValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return MyMessage.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "firstField", "{javax.validation.constraints.NotNull}",
"firstField cannot be null");
MyMessage obj = (MyMessage) target;
if (obj.getSecondField != null && obj.getSecondField > 100) {
errors.rejectField(errors, "secondField", "{javax.validation.constraints.Max}", "secondField is too big");
}
}
}
这里是使用 @SpringValid
注释的服务 class:
MyService.java
package com.example.services;
import com.example.messages.MyMessage;
import org.springframework.validation.annotation.SpringValid;
import org.springframework.validation.annotation.Validated;
import javax.inject.Inject;
@Validated
public class MyService {
public String doIt(@SpringValid final MyMessage msg) {
return "we did it!";
}
}
希望这在某些时候对某些人有意义。我个人觉得还是蛮好用的。许多公司开始将他们的内部 API 从 REST 转移到 Protobuf 或 Thrift 之类的东西。您仍然可以使用 Bean Validation,但您必须使用 XML,而且它并不是那么好。所以我希望这对仍然想进行程序化验证的人有所帮助。
希望对大家有所帮助。我通过添加以下配置让它工作:
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 在 class 和 @Valid 在参数上)并且可以注入到另一个 bean 中,在那里可以直接调用该方法并进行验证。