参数校验工具之Validator自定义校验
2019-03-05 本文已影响0人
从入门到脱发
validator百度博客一大堆,本文是摘取的部分内容结合在项目中使用的经验.
一.导入maven依赖
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.0.Final</version>
</dependency>
二.给需要校验的pojo加上注解
@Data
public class InformationBasicInfoAO {
/**
* 主键id
*/
private Long id;
/**
* 文章标题
*/
@NotBlank(message = "文章标题不能为空")
private String title;
/**
* 发布日期
*/
@NotNull(message = "发布时间不能为空")
private Date releaseDate;
/**
* 文章来源
*/
@NotBlank(message = "文章来源不能为空")
private String source;
message的内容是返回给前端的提醒信息,可以不填
三.校验
1.通用的校验工具
@Slf4j
public class ValidatorUtil {
//也可以使用spring注入的方式
private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
/**
* 使用指定分组
*
* @param object 被校验的bean
* @param groups 分组
* @return
*/
public static <T> Map<String, StringBuilder> validate(T object, Class<?>... groups) {
Map<String, StringBuilder> errorMap = new HashMap<>(16);
if (groups == null) {
groups = new Class[]{Default.class};
}
Set<ConstraintViolation<T>> set = validator.validate(object, groups);
if (CollectionUtils.isEmpty(set)) {
return null;
}
String property;
for (ConstraintViolation<T> c : set) {
// 这里循环获取错误信息,可以自定义格式
property = c.getPropertyPath().toString();
if (errorMap.get(property) != null) {
errorMap.get(property).append(",").append(c.getMessage());
} else {
StringBuilder sb = new StringBuilder();
sb.append(c.getMessage());
errorMap.put(property, sb);
}
}
return errorMap;
}
}
2.使用工具校验
这种方式直接抛去异常,全局异常处理
Map<String, StringBuilder> errorMap = ValidatorUtil.validate(object);
if (!CollectionUtils.isEmpty(errorMap)) {
log.info("------参数校验失败:{}", errorMap);
throw new ApplicationException(new ExceptionContext("参数校验失败"), "000005", errorMap.toString());
}
也可以在controller中返回.
Map<String, StringBuilder> errorMap = ValidatorUtils.validate(accountAO);
if (!CollectionUtils.isEmpty(errorMap)) {
simpleData.setData(errorMap);
result.setContent(simpleData);
return result;
}
也可以在controller的形参上使用注解@Valid,这种方式不需要util类,可以直接使用,直接抛出异常,需要加配置项,具体可以百度,个人不喜欢这种方式,不够灵活.
@PostMapping(value = "/order")
public PojoResult<String> purchase(@RequestBody @Valid OrderAO orderAO,
HttpServletRequest request) throws IOException, ServletException {
PojoResult<String> pojoResult = new PojoResult<>();
}
三.自定义注解校验
框架自带的注解只能解决一些常规的校验,一些复杂逻辑的校验,可以使用自定义校验器,这是本人很喜欢的功能,重复的复杂的校验逻辑可以使用注解很优雅的代替,让代码的耦合性降低,也更简洁.
自定义校验的基本思路是--自定义注解+自定义校验器
自定义注解
//这里可以加上原生的注解,直接使用非空等功能
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
//这里需要引入校验器
@Constraint(validatedBy = CheckTagsValidator.class)
@Documented
public @interface CheckTags {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
自定义校验器
//接口的两个泛型,分别是自定义注解和注解校验的参数类型
public class CheckTagsValidator implements ConstraintValidator<CheckTags, List<String>> {
@Override
public void initialize(CheckTags constraintAnnotation) {
}
//方法体内可以写自己需要校验的逻辑,返回值为false则校验不通过
@Override
public boolean isValid(List<String> value, ConstraintValidatorContext context) {
boolean isValid = false;
//限定最多3个tag,最少可以0个,tag不超过4个汉字
if (CollectionUtils.isEmpty(value)) {
return true;
}
String filter = value.stream().filter(tag -> tag.length() > 4).findAny().orElse(null);
if (StringUtils.isBlank(filter) && value.size() < 4) {
isValid = true;
}
if (!isValid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("标签输入错误").addConstraintViolation();
}
return isValid;
}
}
结合ValidatorUtil使用
四.Controller中校验参数
Validator本身是支持用@Valid注解自动校验的,也可可以配置校验返回策略,如下
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
个人并不推荐这种方式,参数不对时会直接抛出异常,然后@ControllerAdvice全局异常处理,返回结果
有两个弊端,一是try/catch相对而言性能较低,二是返回异常结果不够清晰;
推荐使用ValidatorUtil结合AOP,直接返回结果,代码如下
@Slf4j
@Aspect
@Component
public class ValidatorAspect {
private static final String METHOD_POST = "POST";
@Around("execution(* com.test.government.affair.controller..*(..))")
public Object handlerControllerMethod(ProceedingJoinPoint point) throws Throwable {
long s = System.currentTimeMillis();
// 从获取RequestAttributes中获取HttpServletRequest的信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
BucSSOUser user = SimpleUserUtil.getBucSSOUser(request);
Object[] args = point.getArgs();
//校验post请求参数
log.info("Controller: 请求 URI( {} )| user( {} )| args:( {} )| ", request.getRequestURI(), user.getEmpId(), args);
if (METHOD_POST.equals(request.getMethod())) {
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
Map<String, StringBuilder> errorMap = ValidatorUtil.validate(arg);
if (!CollectionUtils.isEmpty(errorMap)) {
log.info("参数校验失败 errorMap:{}", errorMap);
return ServiceResult.error("000005", errorMap.toString());
}
}
}
Object proceed = point.proceed();
log.info("Controller: 响应 result:( {} )| 请求耗时:( {} )", proceed, System.currentTimeMillis() - s);
return proceed;
}
}
统一参数校验,同时打印log