简介
我在读之前的老接口时,发现大量的校验耦合在服务层,导致我一时半会不知道里面要做什么?所以我想将校验逻辑与业务逻辑拆开,校验框架用的比较多的就是Hibernate-Validator 校验要考虑的事 2.1 校验要有顺序 2.2 校验中有些耗时操作(查库或者RPC调用),希望只调用一次,然后在不同的校验器中进行传播 (1)可能后续的业务逻辑也需要 2.3 同一个校验器可能会有不同的错误返回 Hibernate-Validator的缺陷 3.1 在不同的校验器中不能进行值传播 3.2 貌似校验顺序也不固定(没有debug源码,调试现象观察) 为了解决Hibernate-Validator的缺陷,遇到了fluent-validator 4.1 它的手动配置很好的解决上述问题,但spring-aop集成时,不能从返回值中拿到上下文传递的值,所以改造了一波(见改造后的版本简述段落)
Hibernate-Validator参数校验
Spring参数校验示例 1.1 @Valid 和 BindingResult 是一一对应的,如果有多个@Valid,那么每个@Valid后面跟着的BindingResult就是这个@Valid的验证结果,顺序不能乱 1.2 如果此时去掉实体对象后面的BindingResult,如校验未通过会抛出BindException异常@RequestMapping
@ResponseBody
public Map<String, Object> main(@Validated OrderApiRequest orderApiRequest, BindingResult bindingResult){
Map<String, Object> backMap = new HashMap<>();
// 数据校验
if (bindingResult.hasErrors()) {
backMap.put(WebCst.MSG, bindingResult.getAllErrors().get(0).getDefaultMessage());
return ResponseUtl.response(backMap, StatusCst.sys1);
}
return backMap;
}
@Data
@ToString
public class OrderApiRequest {
@NotBlank(message = "content不能为空")
private String content;
@NotBlank(message = "token不能为空")
private String token;
@NotBlank(message = "sign不能为空")
}
1.3 Hibernate Validator有两种校验模式,默认为普通模式(会校验完所有的属性),快速失败返回模式(只要有一个字段验证失败,就返回结果)@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class)
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
原理分析 2.1 在接受请求后,SpringMvc会将请求参数和方法参数进行绑定:InvocableHandlerMethod#getMethodArgumentValues protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
}
2.2 解析参数:ModelAttributeMethodProcessor#resolveArgumentpublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
BindingResult bindingResult = null;
if (bindingResult == null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
// 校验请求参数(如果参数有Validated或者Valid注解,则DataBinder#validate)
validateIfApplicable(binder, parameter);
}
}
}
2.3 数据绑定校验
分组校验
定义分组类,每个分组类只需要一个接口就可以了public interface OrderAddGroup {}
校验规则上添加分组@Data
@ToString
public class OrderApiRequest {
@NotBlank(message = "content不能为空",groups = {OrderAddGroup.class})
private String content;
}
修改校验接口@RequestMapping
@ResponseBody
public Map<String, Object> main(@Validated(OrderAddGroup.class) OrderApiRequest orderApiRequest, BindingResult bindingResult){
ConstraintValidator注入问题
在配置Validator时,指定constraintValidatorFactory为SpringConstraintValidatorFactory,否则注入的bean为空
自定义错误消息
public class CheckTokenValidator implements ConstraintValidator<CheckToken,String> {
@Override
public boolean isValid(String token, ConstraintValidatorContext constraintContext) {
.........
if (null == customerTokenEnt || !customerTokenEnt.getToken().equals(token)) {
constraintContext.disableDefaultConstraintViolation();
constraintContext.buildConstraintViolationWithTemplate("用户验证失败").addConstraintViolation();
return false;
}
if (1 != customerTokenEnt.getStatus().intValue()) {
constraintContext.disableDefaultConstraintViolation();
constraintContext.buildConstraintViolationWithTemplate("当前用户已停用,请联系客服人员").addConstraintViolation();
return false;
}
return false;
}
}
HibernateConstraintValidatorContext继承ConstraintValidatorContext(不是我想要的,烦) 1.1 在自定义约束启用表达式插值 1.2 使用hibernate econstraintvalidatorcontext#addMessageParameter(String, Object) 或 hibernate econstraintvalidatorcontext #addMessageParameter(String, Object)public class MyFutureValidator implements ConstraintValidator<Future, Instant> {
HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class );
hibernateContext.disableDefaultConstraintViolation();
hibernateContext
.addExpressionVariable( "now", now )
.buildConstraintViolationWithTemplate( "Must be after ${now}" )
.addConstraintViolation();
return false;
}
看一下源码,上下文是怎么玩的?(SimpleConstraintTree#validateConstraints) 2.1 要重新找突破口了protected <T> void validateConstraints(ValidationContext<T> validationContext,
ValueContext<?, ?> valueContext,
Set<ConstraintViolation<T>> constraintViolations) {
// find the right constraint validator (找到正确的校验器)
ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );
// create a constraint validator context (创建上下文,窝草,居然是直接New的)
ConstraintValidatorContextImpl constraintValidatorContext = new ConstraintValidatorContextImpl(
validationContext.getParameterNames(),
validationContext.getClockProvider(),
valueContext.getPropertyPath(),
descriptor,
validationContext.getConstraintValidatorPayload()
);
// validate (校验)
constraintViolations.addAll(
validateSingleConstraint(
validationContext,
valueContext,
constraintValidatorContext,
validator
)
);
}
fluent-validator校验
使用fluent-validator工具库
编写Validator//继承ValidatorHandler可以避免实现一些默认的方法
public class CarSeatCountValidator extends ValidatorHandler<Integer> {
@Override
public boolean validate(ValidatorContext context, Integer t) {
if (t != 2 && t != 5 && t != 7) {
//通过context放入错误消息并且返回false
context.addErrorMsg(String.format(CarError.SEATCOUNT_ERROR.msg(), t));
return false;
}
return true;
}
}
开始验证Car car = getCar();
Result ret = FluentValidator.checkAll() //获取了一个FluentValidator实例
.on(car.getLicensePlate(), new CarLicensePlateValidator())
.on(car.getManufacturer(), new CarManufacturerValidator())
.on(car.getSeatCount(), new CarSeatCountValidator())
.doValidate() //真正执行验证
.result(toSimple());
设置回调函数
Result ret = FluentValidator.checkAll()
.on(car.getSeatCount(), new CarSeatCountValidator())
.doValidate(new DefaulValidateCallback() {
@Override
public void onSuccess(ValidatorElementList validatorElementList) {
LOG.info("all ok!");
}
}).result(toSimple());
集成Hibernate Validator
public class HiberateSupportedValidatorTest {
//声明Hibernate Validator
private static Validator validator;
@BeforeClass
public static void setUpValidator() {
Locale.setDefault(Locale.ENGLISH);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void testCompany() {
Company company = CompanyBuilder.buildSimple();
Result ret = FluentValidator.checkAll()
//HibernateSupportedValidator依赖于javax.validation.Validator的实现,也就是Hibernate Validator
.on(company, new HibernateSupportedValidator<Company>().setHiberanteValidator(validator))
.on(company, new CompanyCustomValidator())
.doValidate().result(toSimple());
System.out.println(ret);
assertThat(ret.isSuccess(), is(true));
}
}
注解验证
@FluentValidate可以装饰在属性上,内部接收一个Class[]数组参数 1.1 这些个classes必须是Validator的子类,这叫表明在某个属性上依次用这些Validator做验证public class Car {
@FluentValidate({CarManufacturerValidator.class})
private String manufacturer;
}
进行验证@Test
public void testCar() {
Car car = getValidCar();
Result ret = FluentValidator.checkAll().configure(new SimpleRegistry())
.on(car)
.doValidate()
.result(toSimple());
System.out.println(ret);
assertThat(ret.isSuccess(), is(true));
}
Spring 注解验证
你的验证器需要Spring IoC容器管理的bean注入,那么你干脆可以把Validator也用Spring托管,使用@Service或者@Component注解在Validator类上就可以做到 使用SpringApplicationContextRegistry 2.1 作用:去Spring的容器中寻找Validator 引入相关依赖<dependency>
<groupId>com.baidu.unbiz</groupId>
<artifactId>fluent-validator-spring</artifactId>
<version>1.0.5</version>
</dependency>
注解验证源码分析
FluentValidator#on方法分析protected <T> MultiValidatorElement doOn(final T t) {
// 获取Bean的所有待验证属性的验证列表
List<AnnotationValidator> anntValidatorsOfAllFields =
AnnotationValidatorCache.getAnnotationValidator(registry, t);
for (final AnnotationValidator anntValidatorOfOneField : anntValidatorsOfAllFields) {
// 反射获取属性的值
Object realTarget = ReflectionUtil.invokeMethod(anntValidatorOfOneField.getMethod(), t);
// 添加到elementList
for (final Validator v : anntValidatorOfOneField.getValidators()) {
elementList.add(new ValidatorElement(realTarget, v, new ToStringable() {
@Override
public String toString() {
return String.format("%s#%s@%s", t.getClass().getSimpleName(),
anntValidatorOfOneField.getField().getName(), v);
}
}));
}
}
MultiValidatorElement m = new MultiValidatorElement(elementList);
// 看到这里,就和之前一样了(验证器链)
validatorElementList.add(m);
}
根据注册器以及类获取所有需要被验证的属性private static List<AnnotationValidator> getAllAnnotationValidators(Registry registry, Class<?> clazz) {
// 获取所有包含指定<code>Annotation</code>的<code>Field</code>数组
// 遍历对象的字段,判断是否有FluentValidate注解
Field[] fields = ReflectionUtil.getAnnotationFields(clazz, FluentValidate.class);
// 遍历带FluentValidate注解的属性
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
FluentValidate fluentValidateAnnt = ReflectionUtil.getAnnotation(field, FluentValidate.class);
// 拿到FluentValidate 注解上的Validator类数组
Class<? extends Validator>[] validatorClasses = fluentValidateAnnt.value();
for (Class<? extends Validator> validatorClass : validatorClasses) {
// 通过Registry查找Validator实例(SimpleRegistry通过直接反射)
List<? extends Validator> validatorsFound = registry.findByType(validatorClass);
}
}
// 将validator转换为AnnotationValidator
AnnotationValidator av = new AnnotationValidator();
av.setField(field);
av.setMethod(ReflectionUtil.getGetterMethod(clazz, field));
av.setValidators(validators);
av.setGroups(groups);
annotationValidators.add(av);
}
Spring AOP集成
@FluentValid注解:表示需要Spring利用切面拦截方法,对参数利用FluentValidator做校验
@Service
public class CarServiceImpl implements CarService {
@Override
public Car addCar(@FluentValid Car car) {
System.out.println("Come on! " + car);
return car;
}
}
定义FluentValidator拦截器:FluentValidateInterceptor 2.1 FluentValidateInterceptor#invoke进行拦截 2.2 配置FluentValidator@Bean
public FluentValidateInterceptor fluentValidateInterceptor(){
FluentValidateInterceptor interceptor = new FluentValidateInterceptor();
interceptor.setCallback(new DefaultFluentCallBack());
return interceptor;
}
@Bean
public Advisor pointcutAdvisor(FluentValidateInterceptor fluentValidateInterceptor) {
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(null, SnailFluentValidate.class);
// 配置增强类advisor
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(fluentValidateInterceptor);
return advisor;
}
源码浅析
FluentValidator#doValidate 1.1 按照默认验证回调条件,开始使用验证 public FluentValidator doValidate() {
return doValidate(defaultCb);
}
FluentValidator#doValidatepublic FluentValidator doValidate(ValidateCallback cb) {
//将分组设置到ThreadLocal中
GroupingHolder.setGrouping(groups);
// 遍历所有校验链进行校验(在 .on(car.getLicensePlate(), new CarLicensePlateValidator())设置)
for (ValidatorElement element : validatorElementList.getAllValidatorElements()) {
// 校验对象
Object target = element.getTarget();
// 对应的校验器
Validator v = element.getValidator();
try {
// 该校验器是否校验该对象
if (v.accept(context, target)) {
// 执行校验
if (!v.validate(context, target)) {
//校验失败后,结果设置false
result.setIsSuccess(false);
// 如果为快速失败,直接从校验链退出
if (isFailFast) {
break;
}
}
}
}catch (Exception e) {
// 校验器的异常回调(ValidatorHandler默认啥都不处理)
v.onException(e, context, target);
// 回调函数的异常回调(默认直接抛出异常)
cb.onUncaughtException(v, e, target);
}
}
if (result.isSuccess()) {
// 回调函数的成功回调
cb.onSuccess(validatorElementList);
} else {
// 回调函数的失败回调
cb.onFail(validatorElementList, result.getErrors());
}
}
改造后的版本简述
配置校验拦截器@Bean
public SnailFluentValidateInterceptor snailFluentValidateInterceptor(){
SnailFluentValidateInterceptor interceptor = new SnailFluentValidateInterceptor();
return interceptor;
}
@Bean
public Advisor pointcutAdvisor(SnailFluentValidateInterceptor snailFluentValidateInterceptor) {
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(null, SnailFluentValidate.class);
// 配置增强类advisor
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(snailFluentValidateInterceptor);
return advisor;
}
SnailFluentValidateInterceptor是对FluentValidateInterceptor拦截器的增强 2.1 只展示改动点public Object invoke(MethodInvocation invocation) throws Throwable {
ValidatorContext context = new ValidatorContext();
context.setResult(new ValidationResult());
FluentValidator fluentValidator = FluentValidator.checkAll(groups)
.setExcludeGroups(excludeGroups)
.configure(registry)
.setIsFailFast(isFailFast)
.withContext(context);
...........
if (result != null) {
LOGGER.debug(result.toString());
if(i+1<=arguments.length){
Object argument = arguments[i+1];
if(argument instanceof SnailFluentResult){
arguments[i+1] = new SnailFluentResult(result,context);
}
}
}
}
使用 3.1 对象属性配置校验器public class OrderApiRequest {
@NotBlank(message = "content不能为空")
@FluentValidate({CheckContentValidator.class})
private String content;
@NotBlank(message = "token不能为空")
@FluentValidate({CheckTokenValidator.class })
private String token;
@NotBlank(message = "sign不能为空")
@FluentValidate({CheckSignValidator.class})
private String sign;
}
3.2 需要拦截校验方法加上注解,并且接受返回@SnailFluentValidate
public Map<String, Object> main(@FluentValid OrderApiRequest orderApiRequest, SnailFluentResult result){
if(!result.isSuccess()){
backMap.put(WebCst.MSG,result.getErrorMsg());
return ResponseUtl.response(backMap, StatusCst.sys1);
}
String service = (String) result.getAttribute("service");
MethodCst methodCst = MethodCst.forName(service);
if (Objects.isNull(methodCst)) {
backMap.put(WebCst.MSG, "请求的服务未定义");
return ResponseUtl.response(backMap, StatusCst.sys1);
}
}
参考资料
Hibernate-Validator官网 fluent-validator