编码技巧——全局异常捕获&统一的返回体&业务异常

在开发中,关于异常的捕获曾经是一个头疼的问题;本篇介绍几个方法,如何优雅的捕获处理业务异常;

已检查异常和未检查异常?

先做个介绍,异常Exception分为运行时异常(RuntimeException)和非运行时异常,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)

  (1) 可查异常checkedException(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况。不管会不会一定出现,编译器都会强制你去Throws或者用try/catch;除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。

  (2) 不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error),需要自己去抛出/捕获/处理,或者尽量避免。(推荐预测异常并处理异常)

如果使用throw在方法体中抛出可查异常,则需要在方法头部声明方法可能抛出throws的异常类型。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。

运行时异常和非运行时异常?

 (1) 运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。当出现RuntimeException的时候,我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。

如果不想终止,则必须捕获所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理

 (2) 非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。如IOException、SQLException等以及用户自定义的Exception异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch并处理,否则程序就不能编译通过。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。

所以——到底是应该是抛出异常,还是捕获/处理异常?

异常处理原则之一:延迟捕获,意思是,当异常发生时,不应立即捕获,而是应该考虑当前作用域是否有有能力处理这一异常的能力,如果没有,则应将该异常继续向上抛出,交由更上层的作用域来处理。

例如:某方法String readFile(Stringfilename),会去尝试读出指定文件的内容并返回,其使用FileInputStream来读取指定文件,而FileInputStream的构造方法会抛出FileNotFoundException,这是一个CheckedException。那么readFile方法是应该捕获这个异常,还是抛出这个异常呢?很显然应该抛出。因为readFile这个方法可能会在不同的场景下,被不同的代码调用,在这些场景中,出现“文件未找到”的情况时的处理逻辑可能是不同的,例如某场景下要发出告警信息,另一场景下可能会尝试从另一个文件中读取,第三个场景下可能需要将错误信息提示给用户。在这种情况下,在readFile方法内的作用域中,是处理不了这个异常的,需要抛出,交由上层的,具备了处理这个异常的能力的作用域来处理;

总结下来,异常捕获要注意3个点

1. 绝对不能把异常信息抛给用户!对用户需要做成友好的用户提示语,对前端开发者可以给出一些提示方便交互和处理,也可以用来日志和埋点上报;

2. 异常捕获应该考虑当前作用域是否有有能力处理这一异常的能力,如果没有,则应将该异常继续向上抛出,交由更上层的作用域来处理;

3. 业务异常应该是运行时异常,他应该与一个业务状态码枚举绑定,针对不同的状态码可以有不同的处理、提示方式;

这里介绍两种常用的业务异常捕获及处理的方案,可用于DUBBO或者HTTP接口的返回处理:

业务异常的定义

注意:如果是C端,业务异常除了message属性,可以加上toast属性

import AA.common.enums.ResultCodeEnum;
import lombok.Getter;

/**
 * @author AA
 * @description 自定义的业务异常,可封装系统状态码、业务错误信息、异常原因等信息
 */
@Getter
public class BusinessException extends RuntimeException {

    /**
     * 存放业务错误对应的系统返回码
     */
    private ResultCodeEnum code;

    /**
     * 存放业务错误信息,且只在日志中打印
     */
    private String busErrMsg;

    /**
     * 无业务错误信息返回的业务异常
     *
     * @param code
     */
    public BusinessException(ResultCodeEnum code) {
        super(code.getDesc());
        this.code = code;
    }

    /**
     * 有返回业务错误信息的业务异常
     *
     * @param code
     * @param busErrMsg
     */
    public BusinessException(ResultCodeEnum code, String busErrMsg) {
        super(code.getDesc());
        this.code = code;
        this.busErrMsg = busErrMsg;
    }

    /**
     * 封装指定异常类的业务异常
     *
     * @param code
     * @param cause
     */
    public BusinessException(ResultCodeEnum code, Throwable cause) {
        super(code.getDesc(), cause);
        this.code = code;
    }

}

状态码枚举:

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author AA
 * @description 接口请求的返回状态的状态码枚举
 * 一级返回码:系统级别
 * 二级返回码:业务级别
 * @date 2019/11/27
 */
@Getter
@AllArgsConstructor
public enum ResultCodeEnum {

    /**
     * 一级返回码,操作成功
     */
    SUCCESS(0, "返回成功"),

    /**
     * 一级返回码,系统内部异常
     */
    SERVER_ERROR(10000, "服务器内部错误(未知错误)"),

    /**
     * 一级返回码,系统请求第三方系统(如dubbo服务)异常
     */
    REQUEST_THIRD_SYSTEM_EXCEPTION(10001, "系统请求第三方系统异常"),

    /**
     * 一级返回码,服务器繁忙
     */
    SERVER_BUSYNESS(10002, "服务器繁忙"),

    /**
     * 一级返回码,请求方法Request.Method不支持
     */
    REQUEST_METHOD_NOT_SUPPORT(10003, "请求方法不支持"),

    /**
     * 一级返回码,协议不支持
     */
    PROTOCOL_NOT_SUPPORT(10004, "协议不支持"),

    /**
     * 一级返回码,系统未知错误
     */
    UNKNOWN(10010, "系统未知错误"),

    /**
     * 二级返回码,用户未登录
     */
    UN_LOGIN(20000, "用户未登录!"),

    /**
     * 二级返回码,错误参数
     */
    BAD_PARAMS(20001, "请求参数错误"),

    /**
     * 二级返回码,HttpClient请求失败
     */
    HTTPCLIENT_REQUEST_FAILED(20002, "HttpClient 请求失败!"),

    /**
     * 二级返回码,发生熔断
     */
    CIRCUIT_BREAKERS(20003, "发生熔断"),

    /**
     * 业务返回码,礼券发送失败
     */
    SENT_TICKET_FAIL(30001, "礼券发送失败"),

    /**
     * 业务返回码,领取失败
     */
    RECEIVE_FAIL(30004, "领取失败"),

    /**
     * 业务返回码,积分发放失败
     */
    SENT_POINT_FAIL(30005, "积分发放失败"),

    /**
     * 业务返回码,查询数据为空
     */
    QUERY_DATA_EMPTY(30007, "查询数据为空"),

    ;

    /**
     * 状态码
     */
    private int code;

    /**
     * 描述
     */
    private String desc;

    /**
     * 根据状态码找枚举类型
     *
     * @param code
     * @return
     */
    public static ResultCodeEnum byCode(int code) {
        for (ResultCodeEnum resultEnum : ResultCodeEnum.values()) {
            if (resultEnum.getCode() == code) {
                return resultEnum;
            }
        }
        throw new IllegalArgumentException("cannot find resultCode by error code : " + code);
    }
}

返回结果的定义

dubbo:FacadeResult

import XXcommon.enums.FacadeResultCodeEnum;
import lombok.Data;

import java.io.Serializable;

/**
 * @author AA
 * @description 默认的dubbo接口的返回DTO
 * @date 2019/11/27
 */
@Data
public class FacadeDefaultDTO<T> implements Serializable {
    private static final long serialVersionUID = 6804764310822280735L;
    /**
     * 系统返回码
     */
    private Integer code;
    /**
     * 返回码的信息
     */
    private String msg;
    /**
     * 返回数据
     */
    private T data;

    public FacadeDefaultDTO() {
    }

    private FacadeDefaultDTO(ResponseBuilder<T> builder) {
        this.code = builder.code;
        this.msg = builder.msg;
        this.data = builder.data;
    }

    public static ResponseBuilder builder() {
        return new ResponseBuilder();
    }

    public static class ResponseBuilder<T> {
        private int code = FacadeResultCodeEnum.SUCCESS.getCode();
        private String msg = FacadeResultCodeEnum.SUCCESS.getDesc();
        private T data;

        public ResponseBuilder code(int code) {
            this.code = code;
            return this;
        }

        public ResponseBuilder msg(String msg) {
            this.msg = msg;
            return this;
        }

        public ResponseBuilder data(T data) {
            this.data = data;
            return this;
        }

        public FacadeDefaultDTO<T> build() {
            return new FacadeDefaultDTO<>(this);
        }
    }

    /**
     * 返回成功,封装Data,编译器会泛型检查
     *
     * @param data
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> FacadeDefaultDTO<T> success(T data) {
        return FacadeDefaultDTO.builder().code(FacadeResultCodeEnum.SUCCESS.getCode()).msg(FacadeResultCodeEnum.SUCCESS.getDesc()).data(data).build();
    }


    /**
     * 返回失败,封装失败的返回码,data为null
     *
     * @param facadeResultCodeEnum
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> FacadeDefaultDTO<T> fail(FacadeResultCodeEnum facadeResultCodeEnum) {
        return FacadeDefaultDTO.builder().code(facadeResultCodeEnum.getCode()).msg(facadeResultCodeEnum.getDesc()).data(null).build();
    }

    /**
     * 返回失败,自定义失败的返回码code和msg,data为null(用于业务异常码和服务异常码的转换)
     *
     * @param errCode
     * @param errMsg
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> FacadeDefaultDTO<T> fail(int errCode, String errMsg) {
        return FacadeDefaultDTO.builder().code(errCode).msg(errMsg).data(null).build();
    }

    public boolean isSuccess() {
        return FacadeResultCodeEnum.SUCCESS.getCode() == this.getCode();
    }

}

http:BaseResponse

import xx.common.enums.ResultCodeEnum;
import lombok.Data;

/**
 * @author AA
 * @description 默认的controller的返回类
 * @date 2019/11/27
 */
@Data
public class DefaultResponseDTO<T> {
    /**
     * 系统返回码
     */
    private Integer code;
    /**
     * 返回码的信息
     */
    private String msg;
    /**
     * 返回数据
     */
    private T data;

    public DefaultResponseDTO() {
    }

    private DefaultResponseDTO(ResponseBuilder<T> builder) {
        this.code = builder.code;
        this.msg = builder.msg;
        this.data = builder.data;
    }

    public static ResponseBuilder builder() {
        return new ResponseBuilder();
    }

    public static class ResponseBuilder<T> {
        private int code = ResultCodeEnum.SUCCESS.getCode();
        private String msg = ResultCodeEnum.SUCCESS.getDesc();
        private T data;

        public ResponseBuilder code(int code) {
            this.code = code;
            return this;
        }

        public ResponseBuilder msg(String msg) {
            this.msg = msg;
            return this;
        }

        public ResponseBuilder data(T data) {
            this.data = data;
            return this;
        }

        public DefaultResponseDTO<T> build() {
            return new DefaultResponseDTO<>(this);
        }
    }

    /**
     * 返回成功,封装Data,编译器会泛型检查
     *
     * @param data
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> DefaultResponseDTO<T> success(T data) {
        return DefaultResponseDTO.builder().code(ResultCodeEnum.SUCCESS.getCode()).msg(ResultCodeEnum.SUCCESS.getDesc()).data(data).build();
    }


    /**
     * 返回失败,封装失败的返回码
     *
     * @param resultCodeEnum
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> DefaultResponseDTO<T> fail(ResultCodeEnum resultCodeEnum) {
        return DefaultResponseDTO.builder().code(resultCodeEnum.getCode()).msg(resultCodeEnum.getDesc()).build();
    }

    /**
     * 返回失败,用errMsg替换错误码的desc
     *
     * @param resultCodeEnum
     * @param errMsg
     * @return
     */
    @SuppressWarnings("unchecked")
    public static DefaultResponseDTO fail(ResultCodeEnum resultCodeEnum, String errMsg) {
        return DefaultResponseDTO.builder().code(resultCodeEnum.getCode()).msg(errMsg).data(null).build();
    }

    /**
     * 返回失败,封装失败的返回码和异常err_data
     *
     * @param resultCodeEnum
     * @param err_data
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> DefaultResponseDTO<T> fail(ResultCodeEnum resultCodeEnum, T err_data) {
        return DefaultResponseDTO.builder().code(resultCodeEnum.getCode()).msg(resultCodeEnum.getDesc()).data(err_data).build();
    }

    public boolean isSuccess() {
        return ResultCodeEnum.SUCCESS.getCode() == this.getCode();
    }

}

方式一:Controller的全局异常捕获器

import com.alibaba.csp.sentinel.slots.block.BlockException;
import XXcommon.enums.ResultCodeEnum;
import XXcommon.exception.BusinessException;
import XXcommon.model.DefaultResponseDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.validation.ConstraintViolationException;
import java.util.Optional;

/**
 * @author AA
 * @description 应用层全局异常处理类
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    private static String CLIENT_ABORT_EXCEPTION_PACKAGE_NAME = "org.apache.catalina.connector.ClientAbortException";

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public DefaultResponseDTO handleException(Exception e) {
        //接口限流异常处理
        if (e != null && e.getCause() instanceof BlockException) {
            log.warn("[exception handler]BlockException occur! cause:{}", e.getCause() != null ? e.getCause() : "");
            return DefaultResponseDTO.fail(ResultCodeEnum.RATE_LIMIT);
        }

        //异常捕获的太全了,用户主动断开连接时服务端会产生ClientAbortException,该异常需要忽略
        if (e != null && CLIENT_ABORT_EXCEPTION_PACKAGE_NAME.equals(e.getClass().getName())) {
            log.error("[exception handler]ClientAbortException occur! ignore this exception.");
            return null;
        }
        log.error("[exception handler]controllerAdvice handleException, e:{}", e);
        return DefaultResponseDTO.fail(ResultCodeEnum.SERVER_BUSYNESS);
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public DefaultResponseDTO handleBusinessException(BusinessException e) {
        String busErrMsg = e.getBusErrMsg();
        Throwable cause = e.getCause();
        if (null != cause) {
            log.error("[exception handler]handle BusinessException with Throwable=[{}]", cause);
        } else {
            String errorMessage = String.format("%s %s", e.getCode(), e.getMessage());
            log.error("[exception handler]handle BusinessException advice, errorMessage=[{}]", errorMessage);
        }
        return DefaultResponseDTO.fail(e.getCode(), StringUtils.isNotBlank(busErrMsg) ? busErrMsg : e.getMessage());
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public DefaultResponseDTO handleValidException(BindException e) {
        StringBuilder builder = new StringBuilder();
        builder.append("bad params: ");
        Optional.ofNullable(e.getFieldErrors()).ifPresent(
                fieldErrors -> fieldErrors.forEach(
                        f -> {
                            builder.append(f.getField());
                            builder.append(f.getDefaultMessage());
                            builder.append("|");
                        }
                )
        );
        String message = removeLastSeparation(builder);
        log.error("[exception handler]handle ValidException, bad param=[{}]", message);
        return DefaultResponseDTO.fail(ResultCodeEnum.BAD_PARAMS, message);
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public DefaultResponseDTO handleConstraintViolationException(ConstraintViolationException e) {

        StringBuilder builder = new StringBuilder();
        builder.append("bad params: ");
        Optional.ofNullable(e.getConstraintViolations()).ifPresent(
                constraintViolationSet -> constraintViolationSet.forEach(
                        c -> {
                            builder.append(c.getPropertyPath().toString());
                            builder.append(StringUtils.isNotEmpty(c.getMessage()) ? " " + c.getMessage() : null);
                            builder.append("|");
                        }
                )
        );
        String message = removeLastSeparation(builder);
        log.error("[exception handler]handle ConstraintViolationException, param=[{}]", message);
        return DefaultResponseDTO.fail(ResultCodeEnum.BAD_PARAMS, message);
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public DefaultResponseDTO<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        StringBuilder builder = new StringBuilder();
        BindingResult bindingResult = e.getBindingResult();
        List<ObjectError> allErrors = bindingResult.getAllErrors();
        String parameterName = e.getParameter().getParameterName();
        if (CollectionUtils.isNotEmpty(allErrors)) {
            builder.append(" bad params: ");
            allErrors.forEach(
                    f -> {
                        StringBuilder argNames = new StringBuilder();
                        Object[] arguments = f.getArguments();
                        if (arguments != null && arguments.length > 0) {
                            DefaultMessageSourceResolvable argument = (DefaultMessageSourceResolvable) arguments[0];
                            argNames.append(argument.getDefaultMessage());
                        }
                        builder.append(argNames).append("-").append(f.getDefaultMessage());
                        builder.append("|");
                    }
            );
        }
        String message = removeLastSeparation(builder);
        log.error("[exception handler]handle MethodArgumentNotValidException, bad param=[{}]", message);
        return DefaultResponseDTO.fail(ResultCodeEnum.BAD_PARAMS, message);
    }

    private String removeLastSeparation(StringBuilder builder) {
        String message;
        if (null != builder && builder.length() > 0) {
            message = builder.substring(0, builder.length() - 1);
        } else {
            message = "";
        }
        return message;
    }


}

方式二:DubboFilter

SPI方式:

dubboBizExceptionFilter=com.xx.filter.DubboBizExceptionFilter
package com.AA.service.helper.filter;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.xx.common.exception.BusinessException;
import com.xx.facade.common.enums.FacadeResultCodeEnum;
import com.xx.facade.common.model.FacadeDefaultDTO;
import org.apache.dubbo.rpc.service.GenericService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.springframework.validation.BindException;

import javax.validation.ConstraintViolationException;
import java.util.Optional;

/**
 * @author AA
 * @description dubbo异常拦截器
 */
@Slf4j
@Activate(group = CommonConstants.PROVIDER)
public class DubboBizExceptionFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            org.apache.dubbo.rpc.Result result = invoker.invoke(invocation);
            if (result.hasException() && GenericService.class != invoker.getInterface()) {
                try {
                    Throwable exception = result.getException();
					
					// 限流异常
                    if (exception.getCause() != null && exception.getCause() instanceof BlockException) {
					    log.warn("[DubboFilter]BlockException_occurred. [serviceName={} methodName={} args={}]", invocation.getServiceName(), invocation.getMethodName(), JSON.toJSONString(invocation.getArguments()));
                        AsyncRpcResult rpcResult = AsyncRpcResult.newDefaultAsyncResult(invocation);
                        rpcResult.setValue(FacadeDefaultDTO.builder().code(FacadeResultCodeEnum.RATE_LIMITED.getCode()).msg(FacadeResultCodeEnum.RATE_LIMITED.getDesc()).build());
                        return rpcResult;
                    }

					// 业务异常 需注意: 不满足条件 被RuntimeException包裹时 需要特殊处理
                    if (exception instanceof BusinessException) {
					    log.warn("[DubboFilter]BusinessException_occurred. [serviceName={} methodName={} args={}]", invocation.getServiceName(), invocation.getMethodName(), JSON.toJSONString(invocation.getArguments()));
                        BusinessException busException = (BusinessException) exception;
                        AsyncRpcResult rpcResult = AsyncRpcResult.newDefaultAsyncResult(invocation);
                        rpcResult.setValue(FacadeDefaultDTO.builder().code(busException.getCode().getCode()).msg(busException.getMessage()).build());
                        return rpcResult;
                    }

					// 参数validate     
                    if (exception instanceof ConstraintViolationException) {
                        ConstraintViolationException constraintViolationException = (ConstraintViolationException) exception;
                        StringBuilder builder = new StringBuilder();
                        Optional.ofNullable(constraintViolationException.getConstraintViolations()).ifPresent(constraintViolationSet -> {
                            constraintViolationSet.forEach(c -> {
                                log.warn(String.format("Call %s bad param: %s", c.getPropertyPath(), c.getMessage()));
                                builder.append(c.getMessage());
                                builder.append(" | ");
                            });
                        });
                        String errorMessage = removeLastSeparation(builder);
                        log.warn("[DubboFilter]ConstraintViolationException_occurred. [serviceName={} methodName={} args={} errorMessage={}]", invocation.getServiceName(), invocation.getMethodName(), JSON.toJSONString(invocation.getArguments()), errorMessage);
                        AsyncRpcResult rpcResult = AsyncRpcResult.newDefaultAsyncResult(invocation);
                        rpcResult.setValue(FacadeDefaultDTO.builder().code(FacadeResultCodeEnum.BAD_PARAMS.getCode()).msg(errorMessage).build());
                        return rpcResult;
                    }
					// spring validation参数异常
                    if (exception instanceof BindException) {
                        BindException bindException = (BindException) exception;
                        StringBuilder builder = new StringBuilder();
                        Optional.ofNullable(bindException.getFieldErrors()).ifPresent(fieldErrors -> {
                            fieldErrors.forEach(f -> builder.append(f.getField()).append("|"));
                            builder.deleteCharAt(builder.length() - 1);
                        });
                        String errorMessage = removeLastSeparation(builder);
						log.warn("[DubboFilter]BindException_occurred. [serviceName={} methodName={} args={} errorMessage={}]", invocation.getServiceName(), invocation.getMethodName(), JSON.toJSONString(invocation.getArguments()), errorMessage);
                        AsyncRpcResult rpcResult = AsyncRpcResult.newDefaultAsyncResult(invocation);
                        rpcResult.setValue(FacadeDefaultDTO.builder().code(FacadeResultCodeEnum.BAD_PARAMS.getCode()).msg(errorMessage).build());
                        return rpcResult;
                    }

                    // 否则,RPC异常包装成本地BizException抛给客户端
                    if (!(exception instanceof org.apache.dubbo.rpc.RpcException)) {
                        log.error("[DubboFilter]OtherException_occurred. [serviceName={} methodName={} args={}] e:{}", invocation.getServiceName(), invocation.getMethodName(), JSON.toJSONString(invocation.getArguments()), exception);
                        AsyncRpcResult rpcResult = AsyncRpcResult.newDefaultAsyncResult(invocation);
                        rpcResult.setValue(FacadeDefaultDTO.builder().code(FacadeResultCodeEnum.SERVER_ERROR.getCode()).build());
                        return rpcResult;
                    } else {
                        // 是Dubbo本身的异常,直接抛出
                        log.error("[DubboFilter]RPCException_occurred. [serviceName={} methodName={} args={} e:{}]", invocation.getServiceName(), invocation.getMethodName(), JSON.toJSONString(invocation.getArguments()), exception);
                        return result;
                    }
                } catch (Throwable e) {
                    log.error("[DubboFilter]Fail_to_ExceptionFilter_when_called_by: [serviceName={} methodName={} args={} exception={}] e:{}", invocation.getServiceName(), invocation.getMethodName(), JSON.toJSONString(invocation.getArguments()), e.getClass().getName(), e);
                    return result;
                }
            }
            return result;
        } catch (RuntimeException e) {
            log.error("[DubboFilter]Got_exception_which_called_by: [serviceName={} methodName={} args={} exception={}] e:{}", invocation.getServiceName(), invocation.getMethodName(), JSON.toJSONString(invocation.getArguments()), e.getClass().getName(), e);
            AsyncRpcResult rpcResult = AsyncRpcResult.newDefaultAsyncResult(invocation);
            rpcResult.setValue(FacadeDefaultDTO.builder().code(FacadeResultCodeEnum.SERVER_ERROR.getCode()).build());
            return rpcResult;
        }
    }

    private String removeLastSeparation(StringBuilder builder) {
        String message;
        if (null != builder && builder.length() > 0) {
            message = builder.toString().substring(0, builder.length() - 2);
        } else {
            message = "";
        }
        return message;
    }

}

注意,使用时可能出现:" 工程引入dubboFilter时,发现自定义业务异常不能被filter捕获,被包裹在运行时异常中 "

分析源码:org.apache.dubbo.rpc.filter.ExceptionFilter

做法:将Dubbo业务异常放在二方jar包中(这也是中间件二方包的常用做法);

方式三:切面捕获

这种方式较为灵活,精度可以到方法,但是要注意切面失效的一些情况,如同类调用;

注解:

package com.AA.web.helper.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author AA
 * @description API通用处理注解
 * @date 2022/3/10
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResultHandler {

}

切面:

package com.AA.web.helper;

import com.AA.dto.BaseResponse;
import com.AA.enums.ResultCodeEnum;
import com.AA.exception.BusinessException;
import com.AA.ConfigManager;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author AA
 * @description API通用处理
 * @date 2022/3/10
 */
@Aspect
@Component
public class ResultHandlerAspect {

    private final static Logger LOGGER = LoggerFactory.getLogger(ResultHandlerAspect.class);

    private static final String GLOBAL_ERROR_LOG_SWITCH = "global.error.log.switch";

    @Around("@annotation(com.AA.helper.annotation.ResultHandler)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        Object proceed;
        try {
            proceed = joinPoint.proceed();
        }
        // 参数异常
        catch (IllegalArgumentException ie) {
            return BaseResponse.buildResult(ResultCodeEnum.PARAMS_ERROR);
        }
        // 业务异常
        catch (BusinessException e) {
            final ResultCodeEnum code = e.getCode();
            final String busErrMsg = e.getBusErrMsg();
            final String toast = e.getToast();
            if (code != null) {
                if (StringUtils.isNotBlank(busErrMsg)) {
                    return BaseResponse.buildResult(code, String.join(" detail: ", code.getMessage(), busErrMsg), null);
                }
                return BaseResponse.buildResult(code);
            }
            // 无状态码则返回默认的系统异常
            return BaseResponse.buildResult(ResultCodeEnum.SYSTEM_ERROR);
        }
        // 其他异常封装 "系统异常"
        catch (Throwable e) {
            if (ConfigManager.getBoolean(GLOBAL_ERROR_LOG_SWITCH, true)) {
                LOGGER.error("unhandled_exception:", e);
            }
            return BaseResponse.buildResult(ResultCodeEnum.SYSTEM_ERROR);
        }
        return proceed;
    }

}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值