微服务中如何处理全局异常

微服务中,全局异常处理是一个必须解决的客观课题,如果这些异常处理不好的话,就会给用户看到一些不友好的错误信息,比如客户看到了空指针异常,SQL执行错误等异常,客户肯定是看不懂的,从而大大降低用户体验。

全局异常处理的常见解决方案
  • 定义一个处理异常的类,需要处理异常的Controller直接继承这个类,从而获取到异常处理的方法。虽然这种方式可以解决问题,但是极其不灵活,因为动用了继承机制就只为获取一个默认的方法,这显然是不好的。
  • 将这个基类变为接口,提供此方法的默认实现(也就是接口中的default方法,java8开始支持接口方法的默认实现),几乎所有的Controller都需要进行异常处理,所以Controller都需要去写implement XXXException,这个方法显然看起来不是很好。况且这种方式依赖java8才有的语法,这是一个很大的局限。
  • 使用@controllerAdvice + @ExpectionHandler,接下来就以这种方式来实现一些全局异常处理。@ControllerAdvice作为Spring中默认的注解,提供对所有(你的项目包扫描范围内)Controller的异常捕获功能。
定义默认返回实体
public class JsonEntity<T> implements java.io.Serializable {
    private static final long serialVersionUID = -1771426378340695807L;
    T data;
    private int status = 200;
    private String message;

    public JsonEntity() {
    }

    public JsonEntity(T data) {
        this.data = data;
    }
	// 省略getter/setter
	
    @Override
    public String toString() {
        return "JsonEntity{" +
               "status=" + status +
               ", message='" + message + '\'' +
               ", data=" + data +
               '}';
    }
}

定义ExceptionResponse,当发生异常时返回的实体,放在JsonEntity的data里
public class ExceptionResponse implements Serializable {
    private static final long serialVersionUID = -5599209786899732586L;
    private String errorMessage;
    private String classType;
	// 省略getter/setter
}
定义BaseException,继承RuntimeException
@Slf4j
public abstract class BaseException extends RuntimeException implements Serializable {
    private static final long serialVersionUID = 5047454682872098494L;
    private int responseStatus = HttpStatus.INTERNAL_SERVER_ERROR.value();
    private Object[] parameters = null;
    private Logger logger = LoggerFactory.getLogger(BaseException.class);

    public BaseException() {
    }

    public BaseException(String message) {
        super(message);
    }

    public BaseException(String message, Throwable cause) {
        super(message, cause);
    }

    public BaseException(Throwable cause) {
        super(cause);
    }

    public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public BaseException(Object[] parameters) {
        this.parameters = parameters;
    }

    public BaseException(String message, Object[] parameters) {
        super(message);
        this.parameters = parameters;
    }

    public static int determineResponseStatus(Throwable throwable) {
        if (throwable instanceof BaseException) {
            return ((BaseException) throwable).responseStatus();
        }
        return HttpStatus.INTERNAL_SERVER_ERROR.value();
    }

    public static int determineResponseStatus(Throwable throwable, int defaultValue) {
        if (throwable == null) {
            return defaultValue;
        }
        return determineResponseStatus(throwable);
    }

    public BaseException responseStatus(int responseStatus) {
        this.responseStatus = responseStatus;
        return this;
    }

    public int responseStatus() {
        return responseStatus;
    }

    public String getMessage(String fmt) {
        if (ObjectUtil.isEmpty(parameters)) {//NOPMD
            return fmt;
        }
        return String.format(fmt, parameters);
    }

    public String getMessage() {
        return (super.getMessage() == null ? "" : super.getMessage()) + formatParameters();
    }

    public String getLocalizedMessage() {
        return getLocalizedMessage(LocaleContextHolder.getLocale());
    }

    public String getLocalizedMessage(Locale locale) {
        Assert.notNull(locale);
        return getFromMessageSource(super.getMessage(), parameters, locale);
    }

    private String getFromMessageSource(String messageCode, Object[] params, Locale locale) {
        try {
            MessageSource messageSource = SpringContextHelper.afterSpringFullyStarted().getBeanSilently(MessageSource.class);
            if (messageSource != null) {
                return messageSource.getMessage(messageCode, params, locale);
            }
        } catch (Exception e) {
            logger.trace("error: {}, fallback to non-localized code, code: {}", e.getMessage(), messageCode);
        }
        logger.trace("messageSource absent, fallback to non-localized message, code: {}", messageCode);
        return getMessage();
    }

    private String formatParameters() {
        StringBuffer sb = new StringBuffer();
        if (!isEmpty(parameters)) {
            sb.append(" [params: ");
            for (int i = 0; i < parameters.length; i++) {
                sb.append(i).append(":").append(parameters[i]);
                if (i < parameters.length - 1) {
                    sb.append(", ");
                }
            }
            sb.append("] ");
        }
        return sb.toString();
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void throwIf(boolean condition) {
        if (condition) {
            throw this;
        }
    }
定义BizException,继承BaseException用来处理业务异常
public class BizException extends BaseException {
    private static final long serialVersionUID = 4328407468288938158L;

    public BizException() {
    }

    public BizException(String message) {
        super(message);
    }

    public BizException(String message, int status) {
        super(message);
        this.responseStatus(status);
    }

    public BizException(String message, Throwable cause) {
        super(message, cause);
    }

    public BizException(String message, Throwable cause, int status) {
        super(message, cause);
        this.responseStatus(status);

    }

    public BizException(Throwable cause) {
        super(cause);
    }

    public BizException(Throwable cause, int status) {
        super(cause);
        this.responseStatus(status);
    }

    public BizException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public BizException(Object[] params) {
        super(params);
    }

    public BizException(String message, Object[] params) {
        super(message, params);
    }

    public static BaseException ofMessage(String message) {
        return new BizException(message);
    }
}

定义GlobalExceptionHandler,并使用@ControllerAdvice
@Controller
@ControllerAdvice
public class GlobalExceptionHandler {
    private static Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @Autowired(required = false)
    private ResponseStatusExceptionResolver responseStatusExceptionResolver;

    @Autowired(required = false)
    private DefaultHandlerExceptionResolver defaultHandlerExceptionResolver;

    @PostConstruct
    public final void postInit() {
        if (responseStatusExceptionResolver == null) {
            responseStatusExceptionResolver = new ResponseStatusExceptionResolver();
        }
        if (defaultHandlerExceptionResolver == null) {
            defaultHandlerExceptionResolver = new DefaultHandlerExceptionResolver();
        }
    }

    protected ResponseEntity jsonResponse(HttpServletRequest request, HttpServletResponse response, int status, Exception e) {//NOPMD
        JsonEntity<ExceptionResponse> resp = ResponseHelper.createInstance(ExceptionUtil.getExceptionResponse(request, e));
        resp.setStatus(status);
        resp.setMessage(ExceptionUtil.getErrorMessage(e));
        return new ResponseEntity<>(resp, HttpStatus.valueOf(status));
    }
    
    @ExceptionHandler(value = Exception.class)
    @Order(Ordered.LOWEST_PRECEDENCE)
    public final Object defaultErrorHandler(HttpServletRequest req, HttpServletResponse response, @Nullable Object handler, Exception e) {
        if (e instanceof InternalHttpException) {
            log.warn(e.getMessage());
        } else {
            log.error(e.getMessage(), e);
        }
        ErrorWrapperResponse wrapperResponse = new ErrorWrapperResponse(response);

        ModelAndView modelAndView = responseStatusExceptionResolver.resolveException(req, wrapperResponse, handler, e);
        if (modelAndView == null) {
            modelAndView = defaultHandlerExceptionResolver.resolveException(req, wrapperResponse, handler, e);
        }
        int status;

        if (modelAndView == null) {
            if (e instanceof BaseException) {
                status = ((BaseException) e).responseStatus();
            } else {
                status = HttpStatus.INTERNAL_SERVER_ERROR.value();
            }
            modelAndView = new ModelAndView();
            req.setAttribute("javax.servlet.error.exception", e);
        } else {
            status = wrapperResponse.getStatus();
        }

        if (WebUtil.isAjax(req)) {
            return jsonResponse(req, response, status, e);
        } else {
            return htmlResponse(req, response, modelAndView, status, e);
        }
    }

    private Object htmlResponse(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView, int status, Exception e) {//NOPMD
        modelAndView.addObject("requestId", WebUtil.getRequestId());
        modelAndView.addObject("javax.servlet.error.exception", e);
        modelAndView.addObject("errorStatus", status);
        modelAndView.setStatus(HttpStatus.valueOf(status));
        switch (status) {
            case 400:
            case 401:
            case 403:
            case 404:
            case 405:
            case 410:
            case 415:
            case 500:
                modelAndView.setViewName("error/" + status);
                break;
            default:
                modelAndView.setViewName("error/500");
                break;
        }
        return modelAndView;
    }

    private static class ErrorWrapperResponse extends HttpServletResponseWrapper {

        private int status = 200;

        private String message;

        private boolean hasErrorToSend = false;

        ErrorWrapperResponse(HttpServletResponse response) {
            super(response);
        }

        @Override
        public void sendError(int status) throws IOException {
            sendError(status, null);
        }

        @Override
        public void sendError(int status, String message) throws IOException {
            this.status = status;
            this.message = message;
            this.hasErrorToSend = true;
        }

        @Override
        public int getStatus() {
            if (this.hasErrorToSend) {
                return this.status;
            } else {
                return super.getStatus();
            }
        }

        public String getMessage() {
            return this.message;
        }

        public boolean hasErrorToSend() {
            return this.hasErrorToSend;
        }
    }
}
参考文章
发布了142 篇原创文章 · 获赞 89 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/wangchengming1/article/details/104657448
今日推荐