SpringMVC通过切面,实现超灵活的注解式数据校验
2017-12-12 15:36
736 查看
大家都知道,Spring MVC 默认依赖了
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
}
相信大家都有接触过,使用这种方法来实现整体对象的校验,而且还可以根据不同场景,加上不同的
首先我们要想,如何去获得我们需要的参数。我们需要以下参数:
1.请求执行的目标对象 2.请求执行的方法 3.请求的参数有两种方式来获得:
因为通过拦截器实现,有很多坑要填,这里不推荐使用。主要讲第二个方法,通过AOP来实现校验数据获取。
首先定义一个切面,切入点是所有
当执行到
@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;
}
}
定义控制器增强处理
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(HttpServletRequest request,HttpServletResponse response,Object handler)
这个方法传过来的最后一个参数
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 处理参数的逻辑重新实现一遍。虽然也是可以完成的,但是太过于复杂,所以不推荐使用这种方式。
相关文章推荐
- Spring MVC 通过切面,实现超灵活的注解式数据校验
- Spring boot 通过切面,实现超灵活的注解式数据校验
- Spring MVC 通过切面,实现超灵活的注解式数据校验
- Spring MVC 通过切面,实现超灵活的注解式数据校验
- SpringMVC实现页面和java模型的数据交互以及文件上传下载和数据校验
- struts2:数据校验,通过Action中的validate()方法实现校验,图解
- 通过gradle生成SpringMVC实现RESTful返回JSON格式数据
- struts2:数据校验,通过XWork校验框架实现(validation.xml)
- springmvc controller 面向切面编程,实现数据查询的缓存功能
- SpringMVC(20):数据校验功能 -- 使用JSR303实现服务器端的数据校验
- C#和C实现通过CRC-16 (Modbus)获取CRC值并校验数据(代码)
- struts2:数据校验,通过Action中的validate()方法实现校验,图解
- struts2:数据校验,通过XWork校验框架实现(validation.xml)
- struts2:数据校验,通过Action中的validate()方法实现校验,图解
- struts2:数据校验,通过XWork校验框架实现(validation.xml)
- springmvc(四) springmvc的数据校验的实现
- SpringMVC优雅的实现数据校验
- springmvc(四) springmvc的数据校验的实现
- struts2:数据校验,通过Action中的validate()方法实现校验,图解
- springmvc JSR303 Validate 注解式,校验数据