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的

基于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;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/329594.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

相控阵天线阵元波程差相位差计算

如下图所示&#xff0c;O点为相位为0的基准点&#xff0c;P(x,y)点为阵元所在位置&#xff0c;需要计算P点相对于基准点在波束方向上的相位差。OP2为波束方向&#xff0c;OP2与Z轴的角度为Theta&#xff0c;OP2在XOY的投影OP1与X轴的角度为Phi。 计算得到波程差OP2&#xff0c;…

机器学习:贝叶斯估计在新闻分类任务中的应用(实验报告)

文章摘要 随着互联网的普及和发展&#xff0c;大量的新闻信息涌入我们的生活。然而&#xff0c;这些新闻信息的质量参差不齐&#xff0c;有些甚至包含虚假或误导性的内容。因此&#xff0c;对新闻进行有效的分类和筛选&#xff0c;以便用户能够快速获取真实、有价值的信息&…

Java分布式锁理论(redis、zookeeper) 详解

目录 一、分布式锁有哪些应用场景&#xff1f; 二、分布式锁的实现方案 三、zookeeper实现分布式锁 一直不释放锁怎么办&#xff1f; 如何避免分布式锁羊群效应问题&#xff1f; 四、redis实现分布式锁 一、分布式锁有哪些应用场景&#xff1f; 1、定时任务 2、秒杀抢购…

Proteus 各版本安装指南

Proteus下载链接 https://pan.baidu.com/s/1vHgg8jK9KSHdxSU9SDy4vQ?pwd0531 1.鼠标右击【Proteus8.15(64bit&#xff09;】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到Proteus8.15(64bit&#xff09; 】。 2.打开解压后的文件夹&#…

dnSpy调试Web应用程序

文章目录 前言一、dnSpy是什么&#xff1f;二、如何使用dnSpy三、如何调试Web应用程序四、下载总结 前言 dnSpy是一个.NET程序集调试器和编辑器&#xff0c;主要用于调试和编辑没有源码的.NET程序集。 一、dnSpy是什么&#xff1f; dnSpy是一个.NET程序集调试器和编辑器&#…

Python武器库开发-武器库篇之子域名扫描器开发(四十一)

Python武器库开发-武器库篇之子域名扫描器开发(四十一) 在我们做红队攻防或者渗透测试的过程中&#xff0c;信息收集往往都是第一步的&#xff0c;有人说&#xff1a;渗透的本质就是信息收集&#xff0c;前期好的信息收集很大程度上决定了渗透的质量和攻击面&#xff0c;本文将…

C语言基础知识(6):UDP网络编程

UDP 是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在 UDP 的情况下&#xff0c;虽然可以确保发送消息的大小&#xff0c;却不能保证消息一定会到达。因此&#xff0c;应用有时会根据自己的需要进行重发处理。 1.UDP协议的主要特点&#xff1a; &#xf…

【C++】STL 算法 ⑥ ( 二元谓词 | std::sort 算法简介 | 为 std::sort 算法设置 二元谓词 排序规则 )

文章目录 一、二元谓词1、二元谓词简介2、 std::sort 算法简介3、 代码示例 - 为 std::sort 算法设置 二元谓词 排序规则 一、二元谓词 1、二元谓词简介 " 谓词 ( Predicate ) " 是一个 返回 布尔 bool 类型值 的 函数对象 / 仿函数 或 Lambda 表达式 / 普通函数 , …

(06)金属布线——为半导体注入生命的连接

01、半导体的核心——“连接” 在上几篇文章中,我们详细讲解了氧化、光刻、刻蚀、沉积等工艺。经过上述工艺,晶圆表面会形成各种半导体元件。半导体制造商会让晶圆表面布满晶体管和电容(Capacitor);而代工厂或CPU制造商则会让晶圆底部排列鳍式场效电晶体(FinFET)等三维…

SSD固态硬盘数据修复方案介绍

这么多故障的可能&#xff0c;那么固态硬盘SSD的数据修复&#xff0c;到底是否有办法呢&#xff1f;我们这里介绍两种尝试修复的方式&#xff0c;不能保证一定会成功。在你误删除一些文件的时候&#xff0c;可以尝试下&#xff0c;市场也有也有很多的修复软件。 方式1:采用修复…

最新版付费进群系统源码 /同城定位付费进群源码 /自带定位完整版/后台分销站点

源码介绍&#xff1a; 最新版付费进群系统源码 &#xff0c;它是同城定位付费进群源码&#xff0c;而且自带定位完整版和后台分销站点。 看到有些人分享一些虚假的内容或者缺少文件的内容。现在分享完整给大家&#xff0c;功能是完整的。它是同城定位付费进群源码。 功能&am…

在Raspberry Pi Zero W中配置TFT LCD Framebuffer驱动

TFT LCD Framebuffer驱动配置 文章目录 TFT LCD Framebuffer驱动配置1、硬件准备2、软件配置2.1 启用SPI驱动2.2 TFT LCD设备驱动树配置 本文将以ILI9341 LCD为例&#xff0c;将详细介绍如何配置TFT LCD的Framebuffer驱动。 1、硬件准备 Raspberry Pi Zero W开发板一个&#x…