Spring Advice 全局异常处理

Advice类

  • @ControllerAdvice 和 多个@ExceptionHandler 组成
  • @ControllerAdvice 只作用于 控制器 相关的功能。默认是全局应用。
  • 通过 @ControllerAdvice 的属性 value / basePackages = {"com.didi.controller"} 代表范围限定在某个包,basePackageClasses 是前面的类型安全写法,表示类所在的包。
  • 通过 @ControllerAdvice 的属性 assignableTypes 代表范围限定在一个数组中的类。
  • 通过 @ControllerAdvice 的属性 annotations 代表范围限定在对应注解标记的类中。
  • 多个 @ControllerAdvice 属性应用 OR 逻辑
  • @ExceptionHandlervalue 代表对应的异常类型,等同于 catch。如果没有 @ControllerAdvice 则只处理本类中关于 servlet 的异常。(如 ServletRequest / HttpServletRequest / HttpSession(会话对象))
@Slf4j
@RestControllerAdvice
@Order(1) // 越小越优先
public class BizExceptionHandler {

  public static final String PACKAGE_NAME = "com.didi";

  @ExceptionHandler(value = BizException.class)
  public Result<?> handleBizException(BizException e){
    log.error("业务异常: {}", getStackTraceByPackage(e, PACKAGE_NAME));
    return Result.error(e.getCode(), e.getMsg());
  }

  @ExceptionHandler(value = Exception.class)
  public Result<?> handleException(Exception e){
    log.error("系统异常: {}", getStackTraceByPackage(e, PACKAGE_NAME));
    return Result.error(e.getMessage());
  }

  public static String getStackTraceByPackage(Throwable e, String packagePrefix){
    StringBuilder sb = new StringBuilder("\n").append(e);
    // 仅筛选改所选包下面异常
    for(StackTraceElement stackTraceElement : e.getStackTrace()){
      if(!stackTraceElement.getClassName().startsWith(packagePrefix)){
        continue;
      }
      sb.append("\n\tat ").append(stackTraceElement);
    }
    return sb.toString();
  }
}

异常类

@Data
public class BizException extends RuntimeException {

  private Integer code = 999;
  private String msg;

  public BizException(Integer code, String msg) {
    super(msg);
    this.code = code;
    this.msg = msg;
  }

  public BizException(String msg) {
    super(msg);
    this.msg = msg;
  }
}

断言类

工具类: 使用抽象类 abstract
( 常量类: final + private constructor, 或者直接使用枚举 )

public abstract class BizAssert {

    /** 工具类(仅含有static成员 和 常量)需要进行「final+私有化构造器」 */
    private BizAssert() {}

    public static final String UN_CHOOSE_ARG = "未选中数据";

    public static void notNull(Object pojo, String msg) {
        if (pojo == null) {
            throw new BizException(msg);
        }
    }

    public static void notNull(Object pojo) {
        notNull(pojo, UN_CHOOSE_ARG);
    }

    public static void notEmpty(Object target, String msg) {
        if (isEmpty(target)) {
            throw new BizException(msg);
        }
    }

    public static void notEmpty(Object pojo) {
        notEmpty(pojo, UN_CHOOSE_ARG);
    }

    public static boolean isEmpty(Object obj) {
        if (obj == null) {
            return true;
        }
        if (obj instanceof Optional) {
            return !((Optional<?>) obj).isPresent();
        }
        if (obj instanceof CharSequence) {
            return ((CharSequence) obj).length() == 0;
        }
        if (obj.getClass().isArray()) {
            return Array.getLength(obj) == 0;
        }
        if (obj instanceof Collection) {
            return ((Collection<?>) obj).isEmpty();
        }
        if (obj instanceof Map) {
            return ((Map<?, ?>) obj).isEmpty();
        }
        return false;
    }
}

Advice类·补充

@ControllerAdvice 其实就是一个作用于某个 范围 且专门 增强 Controller 功能的工具。使用 ControllerAdvice, 主要实现三个方面的功能:

  1. 全局异常处理
  2. 全局数据绑定
  3. 全局数据处理
  • RequestBodyAdviceAdapter : 对所有请求进行后续处理
@ControllerAdvice
public class PrintRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage,MethodParameter parameter, Type targetType,
            Class<? extends HttpMessageConverter<?>> converterType) {
        System.out.println("print request body in advice:" + body);
        return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
    }
}

工作原理

当一个 Body 被解析出来后,会调用 getAdvice() 来获取 RequestResponseBodyAdviceChain

此时 Body 已经解析完毕了,传递给 PrintRequestBodyAdviceAdapter 的是一个解析过的对象,不再是一个流

代码: AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType){
    // ...
    if (message.hasBody()) {
        HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message,      parameter, targetType, converterType);
        //读取请求中的body数据流,并转化
        body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
			((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
        //转换完,找到对应的Advice,并执行他的afterBodyRead()
        body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
        // ...
    }
    // ...
    return body;
}

补充 Result 类

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.ToString;

import java.io.Serializable;

@Getter
@ToString
@ApiModel(value = "接口返回对象", description = "接口返回对象")
public class Result<T> implements Serializable {

    public static final String OPS_SUCCESS = "操作成功!";
    private static final long serialVersionUID = 1L;
    /**
     * 时间戳
     */
    @ApiModelProperty("时间戳")
    private final long timestamp = System.currentTimeMillis();
    /**
     * 成功标志
     */
    @ApiModelProperty("成功标志")
    private Boolean success = true;
    /**
     * 返回处理消息
     */
    @ApiModelProperty("返回处理消息")
    private String message = OPS_SUCCESS;
    /**
     * 返回代码
     */
    @ApiModelProperty("返回代码")
    private Integer code = 0;
    /**
     * 返回数据对象 data
     */
    @ApiModelProperty("返回数据对象")
    private T data;

    public Result() {

    }

    public static <T> Result<T> ok(T data) {

        return ok("成功", data);
    }

    public static <T> Result<T> ok() {

        return ok("成功");
    }

    public static <T> Result<T> ok(String msg) {

        return ok(msg, null);
    }

    public static <T> Result<T> ok(String msg, T data) {

        Result<T> r = new Result<>();
        r.setMessage(msg);
        r.setCode(200);
        r.setData(data);
        return r;
    }

    public static <T> Result<T> error(String msg) {

        return error(500, msg);
    }

    public static <T> Result<T> error(int code, String msg) {

        return error(code, msg, null);
    }

    public static <T> Result<T> error(String msg, T data) {

        return error(500, msg, data);
    }

    public static <T> Result<T> error(int code, String msg, T data) {

        Result<T> r = new Result<>();
        r.setSuccess(false);
        r.setMessage(msg);
        r.setCode(code);
        r.setData(data);
        return r;
    }

    public void setSuccess(Boolean success) {

        this.success = success;
    }

    public void setMessage(String message) {

        this.message = message;
    }

    public void setCode(Integer code) {

        this.code = code;
    }

    public void setData(T data) {

        this.data = data;
    }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值