简易原理图
原理基于请求头传递错误消息,利用aop和全局异常拦截机制实现。
服务提供者
- 远程调用本地方法b,throw异常出来
- FeignExceptionAspect AOP拦截处理异常到请求头中,继续throw
- GlobalExceptionHandler处理,返回响应ResponseVo
服务消费者
- controller方法中的远程调用完毕(异常),被FeignExceptionClient(自定义feign调用处理器)检测到请求头中错误码和错误信息,转化为本地BusinessException throw出来
- FeignExceptionAspect AOP拦截处理异常到请求头中,继续throw
- GlobalExceptionHandler处理,返回响应ResponseVo
上述异常处理机制与使用Decoder处理的异同优劣
- 上述异常处理机制代码简洁程度无法跟解码器处理方式比较,但是处理的细致,可以处理更多自定义的异常,比如:UserLoseException
- 上述处理机制不受限于原生feign的机制,解码器在处理void的时候会出现无法生效情况,并且通过寻找其他博文在低版本中可以通过反射修改强制属性执行解码器的方式得到解决,3.0.5版本不支持这种操作。
博文链接如下:https://blog.csdn.net/m0_37298252/article/details/133690069 - ErrorDecoder生效处理http status 非200的
基于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);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 {@Overridepublic 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 {@Overridepublic Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {String typeName = type.getTypeName();// class voidif (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 businessthrow 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 {@Overridepublic Exception decode(String methodKey, Response response) {return new ErrorDecoder.Default().decode(methodKey, response);}
}
bean注入
@Beanpublic Decoder decoder() {return new FeignDecoder()::decode;}@Beanpublic ErrorDecoder errorDecoder() {return new FeignErrorDecoder()::decode;}