后端开发过程中,免不了进行异常处理。怎么进行异常处理。如果在业务代码中进行异常处理的话,侵入太大,也不便于后续维护管理。
校验和异常处理相辅相成。校验失败就应该发生异常。此处使用一个JSR303校验规则实现校验,使用spring提供的异常统一处理方式。把异常处理逻辑抽取出来维护。@ControllerAdvice 注解标识位一个异常处理类。@ExceptionHandler注解分类处理异常。
校验方式中,适用不同的场景。可以分组校验,自定义校验。给我们开发中提供了很大的便利。
1.编写一个自定义校验注解
2.编写一个自定义的校验器
3.关联注解和校验器,并使用
自定义校验注解:
package com.wang.common.annotation;
import com.wang.common.valid.ListValueConstraintValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* 自定义校验注解
* @author zhipan.wang
*/
@Documented
/*
* 指定校验器
*/
@Constraint(
validatedBy = {ListValueConstraintValidator.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
//异常信息定义
String message() default "{com.wang.common.annotation.ListValue.message}";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
int[] vals() default {};
}
异常信息:ValidationMessages.properties
#传递的状态值不是指定的值!
com.wang.common.annotation.ListValue.message = \u4f20\u9012\u7684\u72b6\u6001\u503c\u4e0d\u662f\u6307\u5b9a\u7684\u503c!
自定义注解检验器
package com.wang.common.valid;
import com.wang.common.annotation.ListValue;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* 自定义注解校验器
* 1.实现ConstraintValidator<注解,类型>
* @author zhipan
*/
public class ListValueConstraintValidator implements ConstraintValidator {
private Set set = new HashSet<>(2);
@Override
public void initialize(ListValue constraintAnnotation) {
for (int val : constraintAnnotation.vals()) {
set.add(val);
}
}
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
return set.contains(integer);
}
}
使用自定义注解@ListValue
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = AddGroup.class)
@ListValue(vals = {0,1},groups = {AddGroup.class,UpdateGroup.class})
private Integer showStatus;
1 利用接口标识需要校验的分组
2 字段添加groups属性标识接收校验的分组
3 方法注解@Validated({AddGroup.class})
接口编写,用于标识异常处理组
package com.wang.common.valid;
/**
* 分组校验标志接口,用于标识新增时需要校验
* @author zhipan.wang
*
*/
public interface AddGroup {
}
校验注解中group参数指定分组,标识该分组需要校验
@Null(message = "新增不能传品牌id",groups = AddGroup.class)
@NotNull(message = "修改必须有品牌id",groups = UpdateGroup.class)
@Validated中指定该方法的分组
/**
* 保存
*/
@PostMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand) {
brandService.save(brand);
return R.ok();
}
发送异常时,抛出由ControllerAdvice来统一处理。
1 @ControllerAdvice 异常处理类
2 @ExceptionHandler 注解,处理异常
自定义ControllerAdvice 处理异常:
package com.wang.gulimall.product.exception;
import com.wang.common.exception.BizCodeEnums;
import com.wang.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 统一处理异常类
* @author zhipan.wang
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.wang.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = Exception.class)
public R handleValidException(MethodArgumentNotValidException e){
log.error("数据校验错误:{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map map = new HashMap<>();
bindingResult.getFieldErrors().forEach(item->{
String defaultMessage = item.getDefaultMessage();
String field = item.getField();
map.put(field,defaultMessage);
});
return R.error(BizCodeEnums.VALID_EXCEPTION.getCode(),BizCodeEnums.VALID_EXCEPTION.getMsg()).put("data",map);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
//异常状态码,5位数字
//前两位位业务场景,后三位表示错误码 10001 10通用 001参数格式异常
//11 商品 12订单 13 购物车 14 物流
return R.error(BizCodeEnums.UNKNOWN_EXCEPTION.getCode(),BizCodeEnums.UNKNOWN_EXCEPTION.getMsg());
}
}
/*
JSR303
*/
异常码枚举类
package com.wang.common.exception;
public enum BizCodeEnums {
UNKNOWN_EXCEPTION(10000, "系统未知异常"),
VALID_EXCEPTION(10001, "数据校验异常");
private int code;
private String msg;
BizCodeEnums(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
1.Entity添加校验注解
例如@NotBlank @Min @Patten...
2.接口方法参数标注@Valid
3.BindlingResult 获取校验结果
Entity代码:
package com.wang.gulimall.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
import lombok.Value;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
/**
* 品牌
*
* @author zhipan.wang
* @email [email protected]
* @date 2020-05-25 18:26:43
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
private Long brandId;
/**
* 品牌名
* 入参校验该字段必须有至少一个字符,message为自定义错误信息
*/
@NotEmpty
@NotBlank(message="品牌名不能为空")
private String name;
/**
* 品牌logo地址
*/
@NotEmpty
@URL(message = "必须是一个合法的URL地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty
@Pattern(regexp = "^[a-zA-Z]$",message="检索字母必须是一个字母")
private String firstLetter;
/**
* 排序
*/
@NotNull
@Min(value=0,message="排序必须大于0")
private Integer sort;
}
Controller代码,@Valid使用,@BindlingResult
@PostMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result) {
if (result.hasErrors()) {
Map map = new HashMap<>();
//获取错误结果
result.getFieldErrors().forEach((item) -> {
//获取错误消息
String msg = item.getDefaultMessage();
//获取错误字段
String field = item.getField();
map.put(field, msg);
});
return R.error(400, "提交的数据不合法!").put("data", map);
} else {
brandService.save(brand);
return R.ok();
}
}