(5)SprintBoot 2.X JSR303 参数校验 + 全局异常处理

it2024-10-09  17

(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;//@IsMobile 假如手机号码格式错误将抛出绑定异常 @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 是 Spring 框架抛出的Validation异常*/ 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; }
最新回复(0)