springboot -- @ControllerAdvice与统一异常处理

@ControllerAdvice

Spring源码中有关@ControllerAdvice的注解如下:

Specialization of {@link Component @Component} 
for classes that declare {@link ExceptionHandler @ExceptionHandler}, 
{@link InitBinder @InitBinder}, 
or {@link ModelAttribute @ModelAttribute} methods 
to be shared across multiple {@code @Controller} classes.

理解:
@ControllerAdvice是一个特殊的@Component,用于标识一个类,这个类中被以下三种注解标识的方法:@ExceptionHandler,@InitBinder,@ModelAttribute,将作用于所有的@Controller类的接口上。

so,这三个注解有啥用呢?

@InitBinder

Annotation that identifies methods which initialize the {
@link org.springframework.web.bind.WebDataBinder} which will be used for 
populating command and form object arguments of annotated handler methods.
Such init-binder methods support all arguments that {@link RequestMapping} supports, 
except for command/form objects and 
corresponding validation result objects. 
Init-binder methods must not have a return value; 
they are usually declared as {@code void}.

作用:注册属性编辑器,对HTTP请求参数进行处理,再绑定到对应的接口,比如格式化的时间转换等。应用于单个@Controller类的方法上时,仅对该类里的接口有效。与@ControllerAdvice组合使用可全局生效。
e.g.

/**
 * 全局日期绑定,只能应对普通表单提交,对@RequestBody无效
 */
@ControllerAdvice
public class DateBindAdvice {

  private static final String[] FORMATS =
      {"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM"};


  @InitBinder
  public void dateBinder(WebDataBinder binder) {
    binder.registerCustomEditor(Date.class, new DatePropertyEditor());
  }

  /**
   * 日期转换器
   */
  private class DatePropertyEditor extends PropertyEditorSupport {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {

      if (StringUtils.isBlank(text)) {
        return;
      }
      Date date;
      try {
        date = DateUtils.parseDate(text, FORMATS);//这个代码就不贴了,网上一大把
      } catch (Exception e) {
        throw new IllegalArgumentException("日期格式不正确 '" + text + "'", e);
      }
      setValue(date);
    }

  }
}

PropertyEditorSupport 类自行百度

@ExceptionHandler

作用:统一异常处理,也可以指定要处理的异常类型
e.g.

/**
 * 全局异常处理
 */

@RestControllerAdvice
@Slf4j
public class ExceptionAdvice {

  private static final Logger logEmail = LoggerFactory.getLogger("email");

  /**
   * 405 not support异常
   */
  @ExceptionHandler(value = {HttpRequestMethodNotSupportedException.class,
      HttpMediaTypeNotSupportedException.class})
  @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
  public LiveResp onNotSupportedException(Exception e,
      HttpServletRequest request) {
    String uri = request.getRequestURI();
    log.error("uri:{},code:{},message:{}", uri, NOT_SUPPORTED.getCode(), e.getMessage());

    return createErrorResp(NOT_SUPPORTED, null);
  }

  /**
   * 404 not support异常
   */
  @ExceptionHandler(value = NoHandlerFoundException.class)
  @ResponseStatus(HttpStatus.NOT_FOUND)
  public LiveResp onException(NoHandlerFoundException e, HttpServletRequest request) {
    String uri = request.getRequestURI();
    log.error("uri:{},code:{},message:{}", uri, NOT_FOUND.getCode(), e.getMessage());

    return createErrorResp(NOT_FOUND, null);
  }

  @ExceptionHandler(value = Exception.class)
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public LiveResp onException(Exception e, HttpServletRequest request) {
    String uri = request.getRequestURI();
    String params = JSONObject.toJSONString(request.getParameterMap());

    if (e instanceof SQLException || e instanceof DataAccessException) {
      createLog(e, uri, params);
      return createErrorResp(DB_ERROR, null);
    } else if (e instanceof FatalException) {//异常这块回头再讲
      FatalException ex = (FatalException) e;
      // 异步发送邮件
      logEmail.error(ex.getMessage(), ex);
      createLog(e, uri, params);
      return createErrorResp(ex.getMsgCode(), ex.getMessage());
    } else if (e instanceof BaseException) {
      BaseException ex = (BaseException) e;
      log.error("uri:{},params:{},code:{},message:{}", uri, params, ex.getMsgCode().getCode(),
          ex.getMessage());
      return createErrorResp(ex.getMsgCode(), ex.getMessage());
    } else if (e instanceof MissingServletRequestParameterException
        || e instanceof BindException
        || e instanceof ConstraintViolationException
        || e instanceof TypeMismatchException
        || e instanceof ServletRequestBindingException) {
      createLog(e, uri, params);
      return createErrorResp(PARAM_ERROR, null);
    } else {
      createLog(e, uri, params);
      return createErrorResp(SERVER_ERROR, null);
    }
  }

  private LiveResp createErrorResp(MsgCode msgCode, String message) {
    return new LiveResp(msgCode.getCode(), isNotBlank(message) ? message : msgCode.getMessage());
  }

  private void createLog(Exception e, String uri, String params) {
    log.error("uri:" + uri + ",params:" + params, e);
  }
}

@ResponseStatus是个标识,标识拦截哪个异常 不懂的自己百度
其中LiveResp是个统一包装返回类

/**
 * 统一返回包装 
 */
@Data
@Accessors(chain = true)//这是lombok的东西 有兴趣自行百度
public class LiveResp<T> {

  private int code = 200;
  private String msg = "";
  private T data;
  private String traceId = TraceUtil.getTraceId();//回头搞
  /**
   * 响应时间戳
   */
  private Long timestamp = System.currentTimeMillis()//回头加上

  public LiveResp() {
  }

  public LiveResp(T data) {
    this.data = data;
  }

  public LiveResp(int code, String msg) {
    this.code = code;
    this.msg = msg;
  }
}
/**
 * 系统异常
 */
public class FatalException extends BaseException {

  public FatalException(String message) {
    this(CommMsgCode.SYSTEM_ERROR, message);
  }

  public FatalException(String message, Throwable cause) {
    this(CommMsgCode.SYSTEM_ERROR, message, cause);
  }

  public FatalException(MsgCode code) {
    super(code);
  }

  public FatalException(MsgCode code, String message) {
    super(code, message);
  }

  public FatalException(MsgCode code, String message, Throwable cause) {
    super(code, message, cause);
  }

  public FatalException(MsgCode code, Throwable cause) {
    super(code, cause);
  }

  public FatalException(MsgCode code, String message, Throwable cause,
      boolean enableSuppression,
      boolean writableStackTrace) {
    super(code, message, cause, enableSuppression, writableStackTrace);
  }
}


public class BaseException extends RuntimeException {

  private final MsgCode msgCode;


  public BaseException(MsgCode msgCode) {
    this.msgCode = msgCode;
  }

  public BaseException(String message) {
    super(message);
    this.msgCode = CommMsgCode.SERVICE_ERROR;
  }

  public BaseException(MsgCode msgCode, String message) {
    super(message);
    this.msgCode = msgCode;
  }

  public BaseException(MsgCode msgCode, String message, Throwable cause) {
    super(message, cause);
    this.msgCode = msgCode;
  }

  public BaseException(MsgCode msgCode, Throwable cause) {
    super(cause);
    this.msgCode = msgCode;
  }

  public BaseException(MsgCode msgCode, String message, Throwable cause,
      boolean enableSuppression, boolean writableStackTrace) {
    super(message, cause, enableSuppression, writableStackTrace);
    this.msgCode = msgCode;
  }

  public MsgCode getMsgCode() {
    return msgCode;
  }

  @Override
  public String getMessage() {
    String msg = super.getMessage();
    if (msg == null || msg.trim().length() == 0) {
      msg = msgCode.getMessage();
    }
    return msg;
  }
}


/**
 * Service 异常
 */
public class ServiceException extends BaseException {

  public ServiceException(String message) {
    this(CommMsgCode.SERVER_ERROR, message);
  }

  public ServiceException(String message, Throwable cause) {
    this(CommMsgCode.SERVER_ERROR, message, cause);
  }

  public ServiceException(MsgCode code) {
    super(code);
  }

  public ServiceException(MsgCode code, String message) {
    super(code, message);
  }

  public ServiceException(MsgCode code, String message, Throwable cause) {
    super(code, message, cause);
  }

  public ServiceException(MsgCode code, Throwable cause) {
    super(code, cause);
  }

  public ServiceException(MsgCode code, String message, Throwable cause,
      boolean enableSuppression,
      boolean writableStackTrace) {
    super(code, message, cause, enableSuppression, writableStackTrace);
  }
}
/**
 * 通用错误码定义
 *
  */
public enum CommMsgCode implements MsgCode {

  /**
   * 协议级返回码
   */
  UPGRADE(100, "您当前版本需要升级"),
  UPGRADE_DATA(101, "需更新配置或数据"),
  SUCCESS(200, "操作成功"),
  REDIRECT(302, "重定向"),
  PARAM_ERROR(400, "请求参数错误"),
  UNAUTHORIZED(401, "没有权限"),
  FORBIDDEN(403, "禁止访问"),
  NOT_FOUND(404, "资源不存在"),
  NOT_SUPPORTED(405, "Method Not SUPPORTED"),
  SERVER_ERROR(500, "内部服务器错误"),
  ROUTER(700, "路由"),

  /**
   * 系统级错误码
   */
  SYSTEM_ERROR(1001, "系统错误"),
  SERVICE_PAUSE(1002, "服务暂停"),
  SERVICE_BUSY(1003, "服务器忙"),

  /**
   * 业务级错误码
   */
  WEB_ERROR(110000, "服务端web异常"),
  SERVICE_ERROR(120000, "服务端service异常"),
  DAO_ERROR(130000, "服务端dao异常"),
  DB_ERROR(191000, "数据库访问异常"),
  IO_ERROR(192000, "IO操作异常"),
  CACHE_ERROR(193000, "cache操作异常"),
  LOCAL_INVOKE_ERROR(194000, "Local api调用错误"),
  RPC_INVOKE_ERROR(195000, "RPC调用错误"),
  SECURITY_ERROR(196000, "安全错误"),
  NO_DATA(197000, "没有数据"),
  INVALID_SIGN(198000, "签名错误"),
  INVALID_TOKEN(198401, "invalid token"),

  /**
   * 其它错误码
   */
  OTHER_ERROR(900000, "其它错误");

  private int code;
  private String message;

  CommMsgCode(int code, String message) {
    this.code = code;
    this.message = message;
  }

  @Override
  public int getCode() {
    return code;
  }

  @Override
  public String getMessage() {
    return message;
  }
}

@ModelAttribute 先mark一下,没怎么看懂

@RequestBody请求怎么搞

上面讲了非@RequestBody请求的处理,那么@RequestBody请求怎么搞,往下看


/**
 * 针对@RequestBody请求的处理 代码很简单 自己看
 */
@ControllerAdvice
@Slf4j
public class LiveRequestBodyAdvice implements RequestBodyAdvice {

  private static final String DEFAULT_CHARSET = "UTF-8";

  @Override
  public boolean supports(MethodParameter methodParameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
    // 可以在些写过滤条件
    return true;
  }

  @Override
  public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
      Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {

    String body = IOUtils.toString(inputMessage.getBody(), DEFAULT_CHARSET);
    log.info("request body:{}", body);
    return new InputMessage(inputMessage.getHeaders(),
        new ByteArrayInputStream(body.getBytes(DEFAULT_CHARSET)));
  }

  @Override
  public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
      Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    return body;
  }

  @Override
  public Object handleEmptyBody(Object body, HttpInputMessage inputMessage,
      MethodParameter parameter, Type targetType,
      Class<? extends HttpMessageConverter<?>> converterType) {
    return body;
  }

  private static class InputMessage implements HttpInputMessage {

    private HttpHeaders headers;
    private InputStream body;

    public InputMessage(HttpHeaders headers, InputStream body) {
      this.headers = headers;
      this.body = body;
    }

    @Override
    public InputStream getBody() throws IOException {
      return body;
    }

    @Override
    public HttpHeaders getHeaders() {
      return headers;
    }
  }

}

@ControllerAdvice
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE) //标识bean加载的顺序
public class LiveRespBodyAdvice implements ResponseBodyAdvice<Object> {

  public static final String LIFE_PACKAGE = "org.bjf.modules";

  @Override
  public boolean supports(MethodParameter methodParameter,
      Class<? extends HttpMessageConverter<?>> converterType) {
    String className = methodParameter.getContainingClass().getName();
    return className.startsWith(LIFE_PACKAGE) &&
        !LiveResp.class.isAssignableFrom(methodParameter.getParameterType()) && !String.class
        .isAssignableFrom(methodParameter.getParameterType());
  }

  @Override
  public Object beforeBodyWrite(Object body, MethodParameter returnType,
      MediaType selectedContentType,
      Class<? extends HttpMessageConverter<?>> selectedConverterType,
      ServerHttpRequest request, ServerHttpResponse response) {

    String path = request.getURI().getPath();
    log.debug("uri:{},response data:{}", path, JSONObject.toJSONString(body));
    return new LiveResp(body != null ? body : "");
  }
}

以上

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值