您的位置:首页 > 编程语言 > Java开发

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

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

public class Message implements java.io.Serializable {
 
private static final long serialVersionUID = 1L;
 
/** 发送系统编号*/
@NotEmpty
private String sysCode;
 
/** 前置机编号*/
@NotEmpty
private String frontName;
 
/** 前置机时间*/
@NotEmpty
@Pattern(regexp = "^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|"
+ "(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|"
+ "[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)\\s+([01][0-9]|"
+ "2[0-3]):[0-5][0-9]:[0-5][0-9]$",message = "must be yyyy-MM-dd HH:mm:ss" )
private String frontTime;
 
/** 签名*/
@NotEmpty
@JSONField(serialize = false)
private String sign;
 
//getter setter
}

相信大家都有接触过,使用这种方法来实现整体对象的校验,而且还可以根据不同场景,加上不同的 
@Group
 注解,来实现不同请求对数据的校验规则。



首先我们要想,如何去获得我们需要的参数。我们需要以下参数:
1.请求执行的目标对象 2.请求执行的方法 3.请求的参数有两种方式来获得:


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



首先定义一个切面,切入点是所有 
controllers
包下所有类的所有方法。 最后我们定义一个方法,在切入点方法之前执行。
当执行到
Controller
这一层的时候,所有的数据已经被Spring MVC处理好了,包括数据类型的转换,自定义的
WebDataBinder
等。所以我们可以直接通过切面获得所需的校验参数,做最终校验。
@Aspect
@Component
@Order(1)
publicclassValidationAspect{
 
privatetransientLogger log=LoggerFactory.getLogger(this.getClass());
 
@Around("execution(public * *(..)) && @within(org.springframework.validation.annotation.Validated)")
publicObject validateMethodInvocation(ProceedingJoinPoint pjp)throwsThrowable{
 
MethodSignature signature=(MethodSignature) pjp.getSignature();
 
ExecutableValidator executableValidator=Validation.buildDefaultValidatorFactory().getValidator().forExecutables();
 
log.info("args:{}",ArrayUtils.toString(pjp.getArgs()));
 
Set<ConstraintViolation<Object>> validResult = executableValidator.
validateParameters(pjp.getTarget(), signature.getMethod(), pjp.getArgs());
 
if(!validResult.isEmpty()){
List<FieldError> errors = validResult.stream().map(constraintViolation->{
String path= constraintViolation.getPropertyPath().toString();
int index= path.lastIndexOf('.');
index = index>0? index+1:0;
FieldError error=newFieldError();
// 参数名称(校验错误的参数名称)
error.setAttribute(path.substring(index));
// 校验的错误信息
error.setErrMsg(constraintViolation.getMessage());
return error;
}).collect(Collectors.toList());
 
thrownewParamValidException(errors);
}
Object result= pjp.proceed();//Execute the method
 
return result;
}
 
}
package com.somnus.solo.message;
 
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
 
publicclassFieldError{
 
privateString attribute;
 
privateString errMsg;
 
publicString getAttribute(){
return attribute;
}
 
publicvoid setAttribute(String attribute){
this.attribute= attribute;
}
 
publicString getErrMsg(){
return errMsg;
}
 
publicvoid setErrMsg(String errMsg){
this.errMsg= errMsg;
}
 
@Override
publicString toString(){
returnToStringBuilder.reflectionToString(this,ToStringStyle.SHORT_PREFIX_STYLE);
}
 
}
package com.somnus.solo.support.exceptions;
 
import java.util.List;
 
import com.somnus.solo.message.FieldError;
 
publicclassParamValidExceptionextendsRuntimeException{
privatestaticfinallong serialVersionUID=1L;
 
privateList<FieldError> fieldErrors;
 
publicParamValidException(){
super();
}
 
publicParamValidException(List<FieldError> fieldErrors){
this.fieldErrors= fieldErrors;
}
 
 
publicParamValidException(Throwable cause){
super(cause);
}
 
publicList<FieldError> getFieldErrors(){
return fieldErrors;
}
 
}
定义控制器增强处理
ParamValidException
异常@ControllerAdvice
publicclassExceptionAdvice{
 
@ExceptionHandler({ParamValidException.class})
@ResponseBody
publicJsonResult handleArrayIndexOutOfBoundsException(ParamValidException e){
 
JsonResult result=newJsonResult(false, e.getFieldErrors());
 
return result;
}
 
}

通过这样的方式,我们请求这个方法:
@RequestMapping(value="token")
publicJsonResult token(@NotBlankString username,@NotBlankString password){
String token= userService.login(username,PwdUtils.pwd(password));
JsonResult result=newJsonResult(token);
return result;
}
模拟请求不传参数 http://localhsot/token
{
"success":false,
"msg":"invalid params: [`password` 不能为空, `username` 不能为空]",
"code":10012,
"data":[
{
"attribute":"password",
"errMsg":"不能为空"
},
{
"attribute":"username",
"errMsg":"不能为空"
}
]
}
     2.模拟请求,只传username参数 http://localhost/token?username=testusername
{
"success":false,
"msg":"invalid params: [`password` 不能为空]",
"code":10012,
"data":[
{
"attribute":"password",
"errMsg":"不能为空"
}
]
}
    3.模拟请求,传正确参数 http://localhost/token?username=testusername&password=testpassword
{
"success":true,
"code":0,
"data":"token-data"
}





实现拦截器后,拦截器提供 
preHandle
 方法,在请求处理之前执行。
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 处理参数的逻辑重新实现一遍。虽然也是可以完成的,但是太过于复杂,所以不推荐使用这种方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: