(5)SprintBoot 2.X JSR303 参数校验
1.JSR303参数校验1.1 JSR303简介1.2 为什么使用JSR3031.3 常用注解1.4 代码实现1.pom.xml中引入依赖2.自定义参数校验器@IsMobile3.具体用法
2.全局异常处理2.1 为什么引入全局异常处理?2.2 代码实现全局异常处理2.2.1自定义全局异常类GlobalException,该类需要继承RunTimeException2.2.2 自定义全局异常拦截器2.2.3 业务逻辑代码使用全局异常
1.JSR303参数校验
1.1 JSR303简介
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。此实现与 Hibernate ORM 没有任何关系。 JSR 303 用于对 Java Bean 中的字段的值进行验证。 Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中对表单提交的数据方便地验证。注:可以使用注解的方式进行验证
1.2 为什么使用JSR303
JSR 303 用于对Java Bean 中的字段的值进行验证,使得验证逻辑从业务代码中脱离出来。是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。
1.3 常用注解
Constraint详细信息
@Null被注释的元素必须为 null@NotNull被注释的元素必须不为 null@AssertTrue被注释的元素必须为 true@AssertFalse被注释的元素必须为 false@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值@Size(max, min)被注释的元素的大小必须在指定的范围内@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内@Past被注释的元素必须是一个过去的日期@Future被注释的元素必须是一个将来的日期@Pattern(value)被注释的元素必须符合指定的正则表达式
1.4 代码实现
1.pom.xml中引入依赖
<dependency>
<groupId>org.springframework.boot
</groupId>
<artifactId>spring-boot-starter-validation
</artifactId>
</dependency>
2.自定义参数校验器@IsMobile
除了使用自带的注解之外,JSR303还允许自定义校验注解步骤1: 实现校验器类IsMobileValidator类,需要继承ConstraintValidator校验器,需要重写方法initialize(),指定注解接口 IsMobile,然后查看值是否为必须的,如果是必须的,那么isValid会进行验证逻辑。
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
private boolean required
= false;
@Override
public void initialize(IsMobile isMobile
) {
required
= isMobile
.required();
}
@Override
public boolean isValid(String value
, ConstraintValidatorContext constraintValidatorContext
) {
if (required
) {
return ValidatorUtil
.isMobile(value
);
} else {
if (StringUtils
.isEmpty(value
)) {
return true;
} else {
return ValidatorUtil
.isMobile(value
);
}
}
}
}
步骤2: 自定义注解@IsMobile,并引入相关配置。@Constraint(validatedBy = { IsMobileValidator.class })。指定自定义的一个校验器。
@Target({ElementType
.METHOD
, ElementType
.FIELD
, ElementType
.ANNOTATION_TYPE
, ElementType
.CONSTRUCTOR
, ElementType
.PARAMETER
})
@Retention(RetentionPolicy
.RUNTIME
)
@Documented
@Constraint( validatedBy
= {IsMobileValidator
.class})
public @
interface IsMobile {
boolean required() default true;
String
message() default "手机号码格式错误";
Class
<?>[] groups() default {};
Class
<? extends Payload>[] payload() default {};
}
步骤3: 步骤1中使用的自定义的手机验证逻辑工具类ValidatorUtil的实现
public class ValidatorUtil {
private static final Pattern mobile_pattern
= Pattern
.compile("1\\d{10}");
public static boolean isMobile(String src
){
if(StringUtils
.isEmpty(src
)){
return false;
}
Matcher m
= mobile_pattern
.matcher(src
);
return m
.matches();
}
}
步骤4: 登陆需要用的LoginVo,需要使用自带的检验注解
public class LoginVo {
@NotNull
@IsMobile
private String mobile
;
@NotNull
private String password
;
public String
getMobile() {
return mobile
;
}
public void setMobile(String mobile
) {
this.mobile
= mobile
;
}
public String
getPassword() {
return password
;
}
public void setPassword(String password
) {
this.password
= password
;
}
@Override
public String
toString() {
return "LoginVo{" +
"mobile='" + mobile
+ '\'' +
", password='" + password
+ '\'' +
'}';
}
}
3.具体用法
在需要验证的参数前面打上标签注解@Valid,那么此注解就会自动对该Bean 进行参数校验。具体校验规则在该Bean内部实现。
@RequestMapping("/do_login")
@ResponseBody
public Result
<String> doLogin(HttpServletResponse response
, @Valid LoginVo loginVo
) {
log
.info(loginVo
.toString());
String token
= userService
.login(response
,loginVo
);
return Result
.success(token
);
}
2.全局异常处理
2.1 为什么引入全局异常处理?
当定义了JSR303校验器后,校验不通过都会产生一个BindException( org.springframework.validation.BindException)和一大串错误信息(其中就包括校验的处理信息)。若要对异常处理,我们可以定义一个全局异常处理的拦截器。优点1: 可以实现对项目中所有产生的异常进行拦截,在同一个类中实现统一处理。避免异常漏处理的情况。优点2: 当Service 出现业务逻辑错误的时候,这个时候我们可以直接抛出异常,让拦截器来捕捉,捕捉之后,就不需要冗余的代码来return 一个不符合业务逻辑的返回值来作为输出。优点3: 当参数校验不通过的时候,输出也是Result(CodeMsg),传给前端用于前端显示获取处理
2.2 代码实现全局异常处理
2.2.1自定义全局异常类GlobalException,该类需要继承RunTimeException
public class GlobalException extends RuntimeException {
private static final long servialVersionUID
= 1L
;
private CodeMsg codeMsg
;
public GlobalException(CodeMsg codeMsg
) {
super(codeMsg
.toString());
this.codeMsg
= codeMsg
;
}
public CodeMsg
getCodeMsg() {
return codeMsg
;
}
}
2.2.2 自定义全局异常拦截器
主要用到的是:@ControllerAdvice + @ExceptionHandler (Spring 框架)
@ControllerAdvice是一个@Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。
@ResponseBody 为了方便输出,使得这个GlobalExceptionHandler类里面的方法跟我们Controller类一样是输出的信息,返回值Result类型,可以携带信息。当参数校验不通过的时候,输出也是Result(CodeMsg),传给前端用于前端显示获取处理。
@ExceptionHandler(value = Exception.class)用于指定需要拦截的异常及其子类异常
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(value
= Exception
.class)
public Result
<String> exceptionHandler(HttpServletRequest request
, Exception e
){
e
.printStackTrace();
if(e
instanceof GlobalException) {
GlobalException ex
= (GlobalException
)e
;
return Result
.error(ex
.getCodeMsg());
}else if(e
instanceof BindException) {
BindException ex
= (BindException
)e
;
List
<ObjectError> errors
= ex
.getAllErrors();
ObjectError error
= errors
.get(0);
String msg
= error
.getDefaultMessage();
return Result
.error(CodeMsg
.BIND_ERROR
.fillArgs(msg
));
}else {
return Result
.error(CodeMsg
.SERVER_ERROR
);
}
}
}
2.2.3 业务逻辑代码使用全局异常
全局异常处理场景:先检查异常类型,若是我们业务异常,返回即可。业务中发现异常直接抛出我们自定义的异常即可。
public String
login(HttpServletResponse response
,LoginVo loginVo
){
if (loginVo
== null
) {
throw new GlobalException(CodeMsg
.SERVER_ERROR
);
}
String mobile
= loginVo
.getMobile();
String formPass
= loginVo
.getPassword();
MiaoshaUser user
= getById(Long
.parseLong(mobile
));
if (user
== null
) {
throw new GlobalException(CodeMsg
.MOBILE_NOT_EXIST
);
}
String dbPass
= user
.getPassword();
String saltDB
= user
.getSalt();
String calcPass
= MD5Util
.formPassToDBPass(formPass
, saltDB
);
if (!calcPass
.equals(dbPass
)) {
throw new GlobalException(CodeMsg
.PASSWORD_ERROR
);
}
String token
= UUIDUtil
.uuid();
addCookie(response
, token
, user
);
return token
;
}