SpringMVC通过切面,实现超灵活的注解式数据校验

来源:互联网 发布:数据挖掘 招聘 长沙 编辑:程序博客网 时间:2024/05/18 01:10

大家都知道,Spring MVC 默认依赖了 hibernate-validator校验框架。使用这个,我们可以在可以在model的字段上,加相应的校验注解来轻松的实现数据校验。 例如:

  1. public class Message implements java.io.Serializable {
  2.  
  3. private static final long serialVersionUID = 1L;
  4.  
  5. /** 发送系统编号*/
  6. @NotEmpty
  7. private String sysCode;
  8.  
  9. /** 前置机编号*/
  10. @NotEmpty
  11. private String frontName;
  12.  
  13. /** 前置机时间*/
  14. @NotEmpty
  15. @Pattern(regexp = "^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|"
  16. + "(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|"
  17. + "[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)\\s+([01][0-9]|"
  18. + "2[0-3]):[0-5][0-9]:[0-5][0-9]$",message = "must be yyyy-MM-dd HH:mm:ss" )
  19. private String frontTime;
  20.  
  21. /** 签名*/
  22. @NotEmpty
  23. @JSONField(serialize = false)
  24. private String sign;
  25.  
  26. //getter setter
  27. }
相信大家都有接触过,使用这种方法来实现整体对象的校验,而且还可以根据不同场景,加上不同的 @Group 注解,来实现不同请求对数据的校验规则。




首先我们要想,如何去获得我们需要的参数。我们需要以下参数:

1.请求执行的目标对象 
2.请求执行的方法 
3.请求的参数

有两种方式来获得:



因为通过拦截器实现,有很多坑要填,这里不推荐使用。主要讲第二个方法,通过AOP来实现校验数据获取。




首先定义一个切面,切入点是所有 controllers包下所有类的所有方法。 最后我们定义一个方法,在切入点方法之前执行。

当执行到Controller这一层的时候,所有的数据已经被Spring MVC处理好了,包括数据类型的转换,自定义的WebDataBinder等。所以我们可以直接通过切面获得所需的校验参数,做最终校验。

  1. @Aspect
  2. @Component
  3. @Order(1)
  4. public class ValidationAspect {
  5.  
  6. private transient Logger log = LoggerFactory.getLogger(this.getClass());
  7.  
  8. @Around("execution(public * *(..)) && @within(org.springframework.validation.annotation.Validated)")
  9. public Object validateMethodInvocation(ProceedingJoinPoint pjp) throws Throwable {
  10.  
  11. MethodSignature signature = (MethodSignature) pjp.getSignature();
  12.  
  13. ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables();
  14.  
  15. log.info("args:{}",ArrayUtils.toString(pjp.getArgs()));
  16.  
  17. Set<ConstraintViolation<Object>> validResult = executableValidator.
  18. validateParameters(pjp.getTarget(), signature.getMethod(), pjp.getArgs());
  19.  
  20. if (!validResult.isEmpty()) {
  21. List<FieldError> errors = validResult.stream().map(constraintViolation -> {
  22. String path = constraintViolation.getPropertyPath().toString();
  23. int index = path.lastIndexOf('.');
  24. index = index>0 ? index+1 : 0;
  25. FieldError error = new FieldError();
  26. // 参数名称(校验错误的参数名称)
  27. error.setAttribute(path.substring(index));
  28. // 校验的错误信息
  29. error.setErrMsg(constraintViolation.getMessage());
  30. return error;
  31. }).collect(Collectors.toList());
  32.  
  33. throw new ParamValidException(errors);
  34. }
  35. Object result = pjp.proceed(); //Execute the method
  36.  
  37. return result;
  38. }
  39.  
  40. }
  1. package com.somnus.solo.message;
  2.  
  3. import org.apache.commons.lang3.builder.ToStringBuilder;
  4. import org.apache.commons.lang3.builder.ToStringStyle;
  5.  
  6. public class FieldError {
  7.  
  8. private String attribute;
  9.  
  10. private String errMsg;
  11.  
  12. public String getAttribute() {
  13. return attribute;
  14. }
  15.  
  16. public void setAttribute(String attribute) {
  17. this.attribute = attribute;
  18. }
  19.  
  20. public String getErrMsg() {
  21. return errMsg;
  22. }
  23.  
  24. public void setErrMsg(String errMsg) {
  25. this.errMsg = errMsg;
  26. }
  27.  
  28. @Override
  29. public String toString() {
  30. return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  31. }
  32.  
  33. }
  1. package com.somnus.solo.support.exceptions;
  2.  
  3. import java.util.List;
  4.  
  5. import com.somnus.solo.message.FieldError;
  6.  
  7. public class ParamValidException extends RuntimeException {
  8. private static final long serialVersionUID = 1L;
  9.  
  10. private List<FieldError> fieldErrors;
  11.  
  12. public ParamValidException() {
  13. super();
  14. }
  15.  
  16. public ParamValidException(List<FieldError> fieldErrors) {
  17. this.fieldErrors = fieldErrors;
  18. }
  19.  
  20.  
  21. public ParamValidException(Throwable cause) {
  22. super(cause);
  23. }
  24.  
  25. public List<FieldError> getFieldErrors() {
  26. return fieldErrors;
  27. }
  28.  
  29. }

定义控制器增强处理ParamValidException异常

  1. @ControllerAdvice
  2. public class ExceptionAdvice {
  3.  
  4. @ExceptionHandler({ParamValidException.class})
  5. @ResponseBody
  6. public JsonResult handleArrayIndexOutOfBoundsException(ParamValidException e) {
  7.  
  8. JsonResult result = new JsonResult(false, e.getFieldErrors());
  9.  
  10. return result;
  11. }
  12.  
  13. }

通过这样的方式,我们请求这个方法:

  1. @RequestMapping(value = "token")
  2. public JsonResult token(@NotBlank String username, @NotBlank String password){
  3. String token = userService.login(username, PwdUtils.pwd(password));
  4. JsonResult result = new JsonResult(token);
  5. return result;
  6. }
  1. 模拟请求不传参数 http://localhsot/token

  1. {
  2. "success": false,
  3. "msg": "invalid params: [`password` 不能为空, `username` 不能为空]",
  4. "code": 10012,
  5. "data": [
  6. {
  7. "attribute": "password",
  8. "errMsg": "不能为空"
  9. },
  10. {
  11. "attribute": "username",
  12. "errMsg": "不能为空"
  13. }
  14. ]
  15. }

     2.模拟请求,只传username参数 http://localhost/token?username=testusername

  1. {
  2. "success": false,
  3. "msg": "invalid params: [`password` 不能为空]",
  4. "code": 10012,
  5. "data": [
  6. {
  7. "attribute": "password",
  8. "errMsg": "不能为空"
  9. }
  10. ]
  11. }
    3.模拟请求,传正确参数 http://localhost/token?username=testusername&password=testpassword

  1. {
  2. "success": true,
  3. "code": 0,
  4. "data": "token-data"
  5. }




实现拦截器后,拦截器提供 preHandle 方法,在请求处理之前执行。

  1. preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

这个方法传过来的最后一个参数 Object handler实际上是一个 HandlerMethod 对象。

可以通过强制转换获得 HandlerMethod methodHandler = (HandlerMethod) handler;

通过这个对象,我们可以获取到处理本次请求的处理对象 HandlerMethod.getBean(),本次请求的处理方法 MethodHandler.getMethod()。 至此,我们校验需要的前两个参数都有了。

问题就在这最后一个参数上,最后一个参数我们需要获得前端传过来的数据,在这里,我们只能从 HttpServletRequest request 里面获取。

从 request 获取的参数,都只是原始的 String[]没有经过处理和转换。

如果要实际使用,还需要转换成 方法 对应的数据类型,并考虑自定义的 WebDataBinder 或其复杂类型的数据转换。 相当于要把 Spring MVC 处理参数的逻辑重新实现一遍。虽然也是可以完成的,但是太过于复杂,所以不推荐使用这种方式。


阅读全文
0 0