Spring Cloud之OpenFeign异常处理

news2025/6/9 18:34:08

简易原理图

原理基于请求头传递错误消息,利用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 {

    @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;
    }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

腾讯云Centos9使用docker的方式安装APISIX

在虚拟机中安装Docker、Docker-compose 安装Docker 清除旧版本的docker yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine 安装docker的依赖 yum install -y yum-utils device-ma…

NE555学习笔记-2024

实物图片 NE555引脚图 内部时序图 示列1&#xff0c;红外接收电路 红外接收电路的工作原理&#xff1a;在上述电路中&#xff0c;TSOP1738构成了该电路的主要组成部分&#xff0c;旨在检测来自任何来源的红外信号。这用于检测38 KHz范围的信号&#xff0c;因此命名为“TSOP173…

LeetCode 2487. 从链表中移除节点:单调栈

【LetMeFly】2487.从链表中移除节点&#xff1a;单调栈 力扣题目链接&#xff1a;https://leetcode.cn/problems/remove-nodes-from-linked-list/ 给你一个链表的头节点 head 。 移除每个右侧有一个更大数值的节点。 返回修改后链表的头节点 head 。 示例 1&#xff1a; 输…

详谈电商网站建设的四大流程!

在21世纪的互联网时代&#xff0c;电商网站的建设是每个企业发展不可缺少的一次机遇。企业商城网站建设成功也许会获得更大的了利润&#xff1b;如果网站建设不成功&#xff0c;那么也会带来一定的损失。所以建设电商网站不是那么一件简单的事情。那么电商网站制作流程是怎样的…

2024年【上海市安全员C3证】试题及解析及上海市安全员C3证模拟考试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 上海市安全员C3证试题及解析根据新上海市安全员C3证考试大纲要求&#xff0c;安全生产模拟考试一点通将上海市安全员C3证模拟考试试题进行汇编&#xff0c;组成一套上海市安全员C3证全真模拟考试试题&#xff0c;学员…

el-select下拉框 change事件返回该项所有数据

主要代码 value-key <template><div><el-selectv-model"value"value-key"label"placeholder"请选择"change"selectChange"><el-optionv-for"item in options":key"item.label":label"…

数据库:基础SQL知识+SQL实验2

&#xff08;1&#xff09;基础知识&#xff1a; 1.JOIN&#xff08;连接&#xff09;&#xff1a; 连接操作用于根据指定的条件将两个或多个表中的数据行合并在一起。JOIN 可以根据不同的条件和方式执行&#xff0c;包括等值连接、不等值连接等。 &#xff08;1&#xff09…

5分钟了解接口测试

接口测试是指对系统接口进行测试的一种质量保障手段&#xff0c;主要是验证接口的功能、性能、安全性等方面是否符合预期。 在接口测试中&#xff0c;可以测试以下内容&#xff1a; 功能测试&#xff1a;验证接口的输入和输出是否符合预期&#xff0c;包括参数的正确性、返回结…

【Docker】docker部署conda并激活环境

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 文章目录 前言一、新建dockerfile文件二、使用build创建镜像1.报错&#xff1a;Your shell has not been properly configured to use conda activate.…

基于LLM+RAG的问答

每日推荐一篇专注于解决实际问题的外文&#xff0c;精准翻译并深入解读其要点&#xff0c;助力读者培养实际问题解决和代码动手的能力。 欢迎关注公众号 原文标题&#xff1a;LLMRAG based Question Answering 原文地址&#xff1a;https://teemukanstren.com/2023/12/25/llm…

【网络安全】上网行为代理服务器Network Agent配置

文章目录 About Network Agent SettingsIgnore Internal TrafficInternal Traffic to MonitorAdditional SettingsBandwidth calculation intervalLog protocol traffic periodically 推荐阅读 本文基于websense &#xff08;现在称为Forcepoint&#xff09;的Network Agent 配…

npm发布js工具包

一、创建项目 1、在github上创建一个项目&#xff0c;然后拉取至本地&#xff0c;进入项目目录2、执行 npm init 生成json文件3、创建 src/index.ts 入口文件和 src/isObject.ts 工具方法 src/index.ts export { default as isObject } from ./isObject src/isObject.ts /…

Zookeeper 分布式服务协调治理框架介绍入门

文章目录 为甚么需要Zookeeper一、Zookeeper 介绍1.1 介绍1.2 Zookeeper中的一些概念1.2.1 集群角色1.2.2 会话 session1.2.3 数据节点 Znode1.2.4 版本1.2.5 事件监听器 Watcher1.2.6 ACL 权限控制表(Access Control Lists) 二、 Zookeeper的系统模型2.1.1 ZNode节点2.1.2 ZNo…

亿可达:提升工作效能的秘诀

在竞争激烈的职场中&#xff0c;提高工作效率对于个人和团队都至关重要。而选择适合自己的工作效率软件&#xff0c;可以为我们提供更好的工作协作和任务管理体验。下面是我个人推荐的一些实用工作效率软件&#xff0c;希望能对您有所帮助。 1. Any.do&#xff1a;Any.do是一款…

AI小冰入驻淘宝 将提供虚拟人陪伴服务

AI小冰正式入驻淘宝&#xff01; 据悉&#xff0c;小冰在淘宝开出了“小冰旗舰店”、以及手淘小程序“X Eva 克隆人的平行世界”&#xff0c;为消费者提供基于KOL虚拟人带来的陪伴服务体验。用户搜索“小冰旗舰店”就可以直达店铺进行选购。 ​小冰旗舰店的首批商品包括冰花直充…

‘react-native‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。

原因&#xff1a;没有下载react-native 解决下载react-native npm i -g react-native-cli

19|BabyAGI:根据气候变化自动制定鲜花存储策略

19&#xff5c;BabyAGI&#xff1a;根据气候变化自动制定鲜花存储策略 随着 ChatGPT 的崭露头角&#xff0c;我们迎来了一种新型的代理——Autonomous Agents&#xff08;自治代理或自主代理&#xff09;。这些代理的设计初衷就是能够独立地执行任务&#xff0c;并持续地追求长…

使用 Jupyter 分析 ROS 消息时间间隔抖动数据

ROS 是一个分布式机器人操作系统软件框架&#xff0c;节点之间通过松耦合的方式进行组合&#xff0c;包括使用 Topic、RPC 服务和参数服务器等方式进行通信。其中&#xff0c;Topic 是最常见的一种通信方式&#xff0c;例如一个雷达传感器节点实时采集三维点云数据&#xff0c;…

WEB 3D技术 three.js 几何体uv属性讲解与基本演示

本文 我们来说说uv 那么 它是什么呢&#xff1f; 首先 比如 我们几何体 贴一个图 那么 为什么我们图的四个边就能正好贴到几何体的边 为什么不可以图就在几何体中间呢&#xff1f; 中心为什么能对齐 它就不能偏一点吗&#xff1f; 这是第一个问题 还有我们 gltf 这种文件 其实…

服务器防护怎么做

随着网络攻击的日益猖獗&#xff0c;服务器安全已成为关注的焦点。如何有效防御各种网络威胁&#xff0c;确保数据安全与业务连续性&#xff0c;已成为一项迫切的需求。目前服务器所面临的主要威胁包括但不限于&#xff1a;DDoS攻击、SQL注入、跨站脚本攻击(XSS)、远程命令执行…