我将@Ken 的答案标记为正确,因为它是正确的。但我更进一步,想发布我所做的。我希望任何来到这个页面的人都会觉得它很有趣。我可能会尝试将它放在 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 的示例显示了带有注释的类,@Validated
因此我想保留它。上述方面仅针对具有@Validated
类级别的类。而且,就像您使用 时一样@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;
}
}
就像第一个答案一样,一个注册所有实现 Springorg.springframework.validation.Validator
接口的类的地方。
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 {
}
这只是额外的调味料,使注册/查找更容易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());
}
}
查看,扫描您的类路径并查找@SpringValidator
类并注册它们。然后注册 Aspect 并离开。
以下是此类验证器的示例:
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
注解的服务类:
我的服务.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,而且它并不是那么好。所以我希望这对仍然想进行程序验证的人有所帮助。