【Spring Cloud】OpenFeign与全局异常处理

简易原理图

原理基于请求头传递错误消息,利用aop和全局异常拦截机制实现。

在这里插入图片描述

服务提供者

  1. 远程调用本地方法b,throw异常出来
  2. FeignExceptionAspect AOP拦截处理异常到请求头中,继续throw
  3. GlobalExceptionHandler处理,返回响应ResponseVo

服务消费者

  1. controller方法中的远程调用完毕(异常),被FeignExceptionClient(自定义feign调用处理器)检测到请求头中错误码和错误信息,转化为本地BusinessException throw出来
  2. FeignExceptionAspect AOP拦截处理异常到请求头中,继续throw
  3. GlobalExceptionHandler处理,返回响应ResponseVo

上述异常处理机制与使用Decoder处理的异同优劣

  1. 上述异常处理机制代码简洁程度无法跟解码器处理方式比较,但是处理的细致,可以处理更多自定义的异常,比如:UserLoseException
  2. 上述处理机制不受限于原生feign的机制,解码器在处理void的时候会出现无法生效情况,并且通过寻找其他博文在低版本中可以通过反射修改强制属性执行解码器的方式得到解决,3.0.5版本不支持这种操作。
    博文链接如下:https://blog.csdn.net/m0_37298252/article/details/133690069
  3. ErrorDecoder生效处理http status 非200的

总结

  • feign本身不提供异常处理的方案,但是提供入参返参的编解码机制,有上述的分析我们知道Decoder生效必须是有返回值的方法,而不是void,所以全局微服务接口规范为必须有返参或者统一返回值即可。
  • 基于AOP的处理方案,前提是方法必须真正执行到,比如说MVC处理直接抛错,还没进入controller方法,则无法正确抛出错误信息,比如:404或者missig body等错误。如果出现这种情况,需要在全局异常拦截时特殊处理下。

基于AOP和自定义feign执行器代码实现

/**
 * 全局异常处理器
 */
@RestControllerAdvice
@Configuration
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public Object defaultErrorHandler(Exception e) throws Exception {
        log.error("系统异常:{}", e);
        return ResultUtils.error("系统异常", String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()));
    }


    /**
     * 处理运行时异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = RuntimeException.class)
    public Object runtimeException(RuntimeException ex) {
        log.error("系统异常:{}", ex);
        return ResultUtils.error("系统异常!", String.valueOf(HttpStatus.EXPECTATION_FAILED.value()));
    }

    @ExceptionHandler(value = DecodeException.class)
    public Object decodeException(DecodeException ex) {
        log.error("系统异常:{}", ex);
        return ResultUtils.error(ex.getMessage(), String.valueOf(ex.status()));
}

    /**
     * 处理自定义业务异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = BusinessException.class)
    public Object exceptionHandler(BusinessException ex) {
        log.error("业务异常详情:{}", ex);
        return ResultUtils.error(ex.getMessage(), ex.getCode());
    }


    @ExceptionHandler(value = UserContextLoseException.class)
    public Object notLoginException(UserContextLoseException e) {
        log.error("当前用户信息不存在异常:{}", e);
        return ResultUtils.notLogin();
    }

    /**
     * 方法入参校验异常处理 content-type!=application/json
     *
     * @param ex 异常
     * @return Object
     */
    @ExceptionHandler({BindException.class})
    public Object bindExceptionException(BindException ex) {
        List<String> validResult = ex.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        log.warn("参数非法:{}", ex);
        return ResultUtils.error(validResult.get(0));
    }

    /**
     * 方法入参校验异常处理 content-type=application/json
     *
     * @param ex 异常
     * @return Object
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Object methodArgumentNotValidException(MethodArgumentNotValidException ex) {
        List<String> validResult = ex.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        log.warn("参数非法:{}", ex);
        // TODO 需要特殊处理异常,否则微服务接口被@Validated校验未通过时无法抛出对应的异常信息
        return ResultUtils.error(validResult.get(0));
    }

    /**
     * 请求类型不支持
     *
     * @param httpRequestMethodNotSupportedException
     * @return
     */
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public Object handleNotSupportedHttpMethodException(HttpRequestMethodNotSupportedException httpRequestMethodNotSupportedException) {
        log.error("HttpRequestMethodNotSupportedException:{}", httpRequestMethodNotSupportedException);
        return ResultUtils.error(httpRequestMethodNotSupportedException.getMessage(), String.valueOf(HttpStatus.EXPECTATION_FAILED.value()));
    }

    /**
     * media type not support
     *
     * @param httpMediaTypeNotSupportedException
     * @return
     */
    @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public Object handleNotSupportedHttpMethodException(HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) {
        log.error("HttpMediaTypeNotSupportedException:{}", httpMediaTypeNotSupportedException);
        return ResultUtils.error(httpMediaTypeNotSupportedException.getMessage(), String.valueOf(HttpStatus.EXPECTATION_FAILED.value()));
    }
}
@Slf4j
@Aspect
@Order(value = 100)
@Component
public class FeignExceptionAspect {

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.GetMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.PostMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.PutMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        try {
            Object proceed = joinPoint.proceed();
            return proceed;
        } catch (BusinessException e) {
            log.error("feign调用异常:{}", e.getMessage());
            if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {
                HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
                response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, e.getCode());
                response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode(e.getMessage(), "UTF-8"));
            }
            throw e;
        } catch (UserContextLoseException e) {
            log.error("用户信息缺失:{}", e);
            if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {
                HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
                response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, e.getCode());
                response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode(e.getMessage(), "UTF-8"));
            }
            throw e;
        } catch (FeignException e) {
            // 存在未发起远程调用前抛出的FeignException异常
            if (e instanceof FeignException.ServiceUnavailable) {
                FeignException.ServiceUnavailable serviceUnavailable = (FeignException.ServiceUnavailable) e;
                log.error(serviceUnavailable.getMessage());
                throw BusinessException.createException("服务不可用");
            }

            throw e;
        } catch (Throwable throwable) {
            Throwable cause = throwable.getCause();
            while (null != cause && null != cause.getCause()) {
                cause = cause.getCause();
            }

            if (cause instanceof BusinessException) {
                if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {
                    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
                    response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, ((BusinessException) cause).getCode());
                    response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode((cause).getMessage(), CommonConsts.UTF8));
                }
                throw (BusinessException) cause;
            } else if (cause instanceof UserContextLoseException) {
                if (null != RequestContextHolder.getRequestAttributes() && null != ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse()) {
                    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
                    response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY, ((UserContextLoseException) cause).getCode());
                    response.setHeader(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY, Base64.encode((cause).getMessage(), CommonConsts.UTF8));
                }
                throw (UserContextLoseException) cause;
            }
            log.error("接口调用异常:{}", throwable);
            throw new RuntimeException("未知异常");
        }
    }

}
@Slf4j
public class FeignExceptionClient implements Client {

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        Response response = new ApacheHttpClient().execute(request, options);

        RequestTemplate requestTemplate = response.request().requestTemplate();
        Target<?> target = requestTemplate.feignTarget();

        String serviceName = target.name();
        Class<?> type = target.type();
        String api = type.getName();
        String url = requestTemplate.url();

        Collection<String> errorCodes = response.headers().get(FEIGN_RESPONSE_HEADER_ERROR_CODE_KEY);
        Collection<String> errormessage = response.headers().get(FEIGN_RESPONSE_HEADER_ERROR_MESSAGE_KEY);
        String errorMessage = null;
        if (null != errormessage && !errormessage.isEmpty()) {
            errorMessage = (String) ((List) errormessage).get(0);
            errorMessage = Base64.decodeStr(errorMessage, CommonConsts.UTF8);
        }

        if (CollectionUtils.isNotEmpty(errorCodes)) {
            logInvokeError(serviceName, api, url);

            Object errorCode = ((List) errorCodes).get(0);
            if (ResultConsts.NOT_LOGIN.toString().equals(errorCode)) {
                throw UserContextLoseException.createException();
            }

            if (String.valueOf(HttpStatus.EXPECTATION_FAILED.value()).equals(errorCode)) {
                throw BusinessException.createException("系统异常");
            }

            if (String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()).equals(errorCode)) {
                throw BusinessException.createException("系统异常");
            }

            if (StringUtils.isNotEmpty(errorMessage)) {
                throw BusinessException.createException(errorMessage);
            }
            throw BusinessException.createException("系统异常");
        }

        if (StringUtils.isNotEmpty(errorMessage)) {
            logInvokeError(serviceName, api, url);
            throw BusinessException.createException(errorMessage);
        }

        return response;
    }

    private void logInvokeError(String serviceName, String api, String method) {
        log.error("调用微服务[{}]-Api[{}]-Uri[{}]异常", serviceName, api, method);
    }
}

基于Decoder处理

public class FeignDecoder implements Decoder {
    @Override
    public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
        String typeName = type.getTypeName();//   class  void
        if (StringUtils.isNotEmpty(typeName)) {
            Response.Body body = response.body();
            String resultString = Util.toString(body.asReader(Util.UTF_8));

            ResponseVO responseVO = JSONObject.parseObject(resultString, ResponseVO.class);
            if (null != responseVO) {
                if (null != responseVO.getCode() && !responseVO.getCode().toString().endsWith(String.valueOf(ResultConsts.SUCCESS_STATUS))) {
                    // 2002000100   417 not-login  100  business
                    throw new DecodeException(responseVO.getCode(), responseVO.getMessage(), response.request());
                }
            }
            Class<?> responseCls;
            try {
                responseCls = Class.forName(typeName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            return JSONObject.parseObject(resultString, responseCls);
        }
        return Util.emptyValueOf(type);
    }
}
public class FeignErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        return new ErrorDecoder.Default().decode(methodKey, response);
    }
}

bean注入

   @Bean
    public Decoder decoder() {
        return new FeignDecoder()::decode;
    }

    @Bean
    public ErrorDecoder errorDecoder() {
        return new FeignErrorDecoder()::decode;
    }
  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值