JSR-303和统一异常处理
一、简介
1. JSR-303
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
2. 统一异常处理
对于项目中出现的异常,我们通常采用ControllerAdvice的方式进行统一处理,简化代码。
1)、编写异常处理类,使用@ControllerAdvice。
2)、使用@ExceptionHandler标注方法可以处理的异常。
二、JSR-303使用步骤
1)、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
2)、开启校验功能@Valid
效果:校验错误以后会有默认的响应;
3)、给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
4)、分组校验(多场景的复杂校验)
1)、 @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
给校验注解标注什么情况需要进行校验
2)、@Validated({AddGroup.class})
3)、默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;
5)、自定义校验
1)、编写一个自定义的校验注解
2)、编写一个自定义的校验器 ConstraintValidator
3)、关联自定义的校验器和自定义的校验注解
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器,适配不同类型的校验】 })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
1. 在实体类上加上校验注解
package com.lyh.mall.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import com.lyh.common.valid.AddGroup;
import com.lyh.common.valid.ListValue;
import com.lyh.common.valid.UpdateGroup;
import com.lyh.common.valid.UpdateStatusGroup;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
/**
* 品牌
*
* @author lyh
* @email 1964059406@qq.com
* @date 2021-09-25 20:00:20
*/
@ApiModel(value="BrandEntity对象", description="品牌")
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能指定id",groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交")
private String name;
/**
* 品牌logo地址
*/
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty(groups={AddGroup.class})
@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(groups={AddGroup.class})
@Min(value = 0,message = "排序必须大于等于0",groups={AddGroup.class,UpdateGroup.class})
private Integer sort;
}
AddGroup.class, UpdateGroup.class, UpdateStatusGroup.class 可以在公共模块添加,如下所示:
package com.lyh.common.valid;
/**
* 新增分组接口,其他分组类似
**/
public interface AddGroup {
}
```
##### 2. 在controller层接口参数前增加@Valid或者@Validated(分组使用)
```java
/**
* 保存
*/
@ApiOperation(value = "保存")
@PostMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*, BindingResult result*/){
/*if (result.hasErrors()){
Map<String, String> map = new HashMap<>();
//1.获取校验的错误结果
result.getFieldErrors().forEach((item)->{
//FieldError获取到错误提示
String message = item.getDefaultMessage();
//获取错误的属性名字
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交数据不合法").put("data",map);
}else {
brandService.save(brand);
}*/
brandService.save(brand);
return R.ok();
}
3. 自定义校验器和校验注解
自定义校验注解@ListValue:
package com.lyh.common.valid;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 自定义注解规则
**/
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.lyh.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
自定义校验器ListValueConstraintValidator.class:
package com.lyh.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<Integer>();
/**
* 初始化方法
* @param constraintAnnotation
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
/**
* 判断是否效验成功
* @param value 需要效验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
//判断是否有包含的值
boolean contains = set.contains(value);
return contains;
}
}
创建校验信息提示配置文件:
在resource文件下创建:ValidationMessages.properties
com.lyh.common.valid.ListValue.message=必须提交指定的值
三、统一异常处理步骤
1)、编写异常处理类,使用@ControllerAdvice。
2)、使用@ExceptionHandler标注方法可以处理的异常。
3)、如果接口有异常直接抛出
1. 编写异常处理类
package com.lyh.mall.product.exception;
import com.lyh.common.exception.BizCodeEnum;
import com.lyh.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 集中处理所有异常
**/
@Slf4j
@RestControllerAdvice(basePackages = "com.lyh.mall.product.controller")
public class MallExceptionControllerAdvice {
/**
* 参数非法(效验参数)异常 MethodArgumentNotValidException
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R handleValidException(MethodArgumentNotValidException e) {
log.error("数据效验出现问题{},异常类型{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError) -> {
//分别获取错误属性字段和错误提示信息
errMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(),BizCodeEnum.VAILD_EXCEPTION.getMessage())
.put("data",errMap);
}
/**
* 其他任何出现的异常捕获
* @param throwable
* @return
*/
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable) {
log.error("错误异常{}",throwable);
return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.UNKNOW_EXCEPTION.getMessage());
}
}
2. 编写异常代码枚举类
package com.lyh.common.exception;
/**
* 错误状态码枚举
*
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 002:短信验证码频率太高
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
* 15:用户
**/
public enum BizCodeEnum {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败"),
TO_MANY_REQUEST(10002,"请求流量过大,请稍后再试"),
SMS_CODE_EXCEPTION(10002,"验证码获取频率太高,请稍后再试"),
PRODUCT_UP_EXCEPTION(11000,"商品上架异常"),
USER_EXIST_EXCEPTION(15001,"存在相同的用户"),
PHONE_EXIST_EXCEPTION(15002,"存在相同的手机号"),
NO_STOCK_EXCEPTION(21000,"商品库存不足"),
LOGINACCT_PASSWORD_EXCEPTION(15003,"账号或密码错误"),
;
private Integer code;
private String message;
BizCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
四、以上代码用到的R类如下
package com.lyh.common.utils;
import org.apache.http.HttpStatus;
import java.util.HashMap;
import java.util.Map;
/**
* 返回数据
*
*/
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
put("msg", "success");
}
public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}
public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok() {
return new R();
}
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}