在openfeign客户端如何获取到服务端抛出的准确异常信息?? openfeign调用(请求/响应)的各个大致过程

在openfeign客户端如何获取到服务端抛出的准确异常信息??

  • 相关参考
  • 背景引入
    • 浏览器直接访问Spring的Restful接口(最普遍、简单的访问)
      • 示例
      • 结论
  • openfeign客户端调用的情况
    • 调用过程
    • 示例场景之一(其他场景可类比)
      • 结论1: 服务器端返回的异常信息,在openfeign客户端直接通过通过try。。catch。。。是获取不到的,需要通过响应信息reposne来获取服务器端返回的信息!
    • 如何实现openfeign的response的拦截
    • openfeign拦截到response(含服务器端返回的信息),如何将服务器端返回的信息返回给调用方法的呢?
      • 测试
  • 特别注意

相关参考

  1. openfeign客户端A调用服务B,服务B抛出异常时,客户端A接收的几种情况

  2. openfeign集成sentinel实现服务降级

  3. OpenFeign客户端调用,服务端查询结果为null并返回给feign客户端,引发客户端报错

  4. openfeign客户端调用远程服务端接口,传递参数为null及服务端接口返回值为null的情况

背景引入

浏览器直接访问Spring的Restful接口(最普遍、简单的访问)

示例

直接在controller层抛出一个异常:观察浏览器接收到的准确信息是什么 ?

Controller代码如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ClassForTest {Logger logger = LoggerFactory.getLogger(this.getClass());@GetMapping("/ex/handler")public String testExceptionHandler() throws Exception {throw new Exception("抛出了异常哈。。。");
//		int x = 3/0;
//		return "nothing";}
}

浏览器返回:
在这里插入图片描述
上图的返回信息是SpringMVC默认处理方式,如果服务端抛出了异常默认就会返回上述信息!

那么,如何获取到自己想要的或者服务端返回的真实异常信息!!

编写自定义异常处理即可获取到想要的真实异常信息,如下:

import javax.servlet.http.HttpServletResponse;import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
public class CustomExceptionHandler {/*** 全局异常处理* @param e* @param response* @return*/@ExceptionHandler(value =Exception.class)@ResponseBodypublic TestEntity myExHandler(Exception e,HttpServletResponse response) {TestEntity te = new TestEntity();te.setName("sf solo!");te.setAge(18);te.setErrorCode("500");te.setErrorMsg(e.getMessage());return te;}
}

上述代码中TestEntity为自定义返回实体

import lombok.Data;
@Data
public class TestEntity {Integer age = 10;String name;String errorMsg;String errorCode;	
}

关于上述自定义异常处理类中的@ControllerAdvice注解和@ExceptionHandler注解的说明:

  • @ControllerAdvice
    带此注解的类是一个含有 @ExceptionHandler, @InitBinder, or @ModelAttribute注解方法的组件类,这个组件类可以贯穿于多个Controller类之间!
    在这里插入图片描述
  • @ExceptionHandler
    此注解用于处理异常(在处理类/处理方法中)。
    带有此注解的处理方法有非常复杂的签名。
    参数和返回可以有如下类型:
    在这里插入图片描述
    测试!是否能够获取到真实信息?

在这里插入图片描述
将抛出异常的地方改一下:

	@GetMapping("/ex/handler")public String testExceptionHandler() throws Exception {
//		throw new Exception("抛出了异常哈。。。");int x = 3/0;return "nothing";}

测试:
在这里插入图片描述

结论

浏览器访问服务端,通过自定义异常处理可以获取到服务端准确的异常信息!

openfeign客户端调用的情况

调用过程

  1. 用户端(浏览器/Postman/App)—>服务A(openfeign客户端)—>服务B(服务端)
  2. 其他更复杂的调用链,省。。。

示例场景之一(其他场景可类比)

  • openfeign客户端调用代码
    1.返回的类型为自定类型。
    2.对调用进行异常捕获。
			try {ResponseInfo responseInfo = checklistServiceFeignClient.uploadReportWithCaSignature(endoscopicreport);} catch (Exception e) {logger.error("发生异常(插入报告信息失败) " , e);// 设置操作记录相关信息responseInfo = new ResponseInfo();if (e instanceof CustomException) {responseInfo.setRespCode(ResponseInfo.ERROR_CODE);responseInfo.setRespMsg(e.getMessage());} else {responseInfo.setRespCode(ResponseInfo.ERROR_CODE);responseInfo.setRespMsg("服务器异常");}}
  • 服务端代码: 下面主要贴出实现类的代码(抛出异常的地方)
    1.声明抛出异常
    2.抛出异常
    3.其他业务代码省略
	@Overridepublic ResponseInfo uploadReportWithCaSignature(Endoscopicreport endoscopicreport) throws Exception {if (1==1)throw new CustomException("后台业务异常。");
// 其他,省略。。。。。。}
  • 测试
  1. 因为openfeign调用是内部调用,不像浏览器可以直接看到返回结果,所以需要通过debug模式在代码中查看返回信息或者直接打印信息!
  2. 那么,在代码哪个地方进行debug调试查看服务器端返回的信息呢?
    直接在openfeign客户端调用的地方是看不到服务器端返回的真实信息的!客户端调用处的调试信息如下:
    在这里插入图片描述
    上图中的异常信息是:openfeign客户端接收到服务器端的返回信息后将其转换成 返回类型(这里是ResponseInfo类型)时抛出的异常(HttpMessageConverter转换异常),而并非是服务器端返回的信息。

这就需要在openfeign客户端收到响应response之后(在返回成ResponseInfo类型之前)进行debug。
也就是要写一个response的拦截器(以便查看response中的具体内容—服务器端返回的信息)。

  1. openfeign客户端的reposne拦截器(因为示例整体是使用okhhtp来实现的,所以一下response拦截器涉及okhttp)

import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;import javax.servlet.http.HttpServletRequest;import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import com.alibaba.cloud.commons.lang.StringUtils;
import com.github.pagehelper.PageInfo;import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;@Configuration
public class FeignOkHttpClientConfig {//	private static int MAX_RETRY = 10;@Beanpublic OkHttpClient.Builder okHttpClientBuilder() {return new OkHttpClient.Builder().addInterceptor(new FeignOkHttpClientResponseInterceptor());}public static class FeignOkHttpClientResponseInterceptor implements Interceptor {Logger logger = LoggerFactory.getLogger(this.getClass());@Overridepublic Response intercept(Chain chain) throws IOException {//			int retryNum = 0;Request originalRequest = chain.request();Response response = chain.proceed(originalRequest);MediaType mediaType = response.body().contentType();String bodyContent = response.body().string();HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 根据业务使用requestreturn response.newBuilder().body(ResponseBody.create(mediaType, bodyContent)).build();}}
}

从上述代码中的String bodyContent = response.body().string();的`bodyContent 中可以获取到服务器端返回的信息!
在这里插入图片描述
所以,这才是服务器端返回的真实信息!,而非上面的HttpMessageConverter转换异常信息!

结论1: 服务器端返回的异常信息,在openfeign客户端直接通过通过try。。catch。。。是获取不到的,需要通过响应信息reposne来获取服务器端返回的信息!

例外的情况:

  1. 如果openfeign客户端调用的返回类型是String,则可以直接获取到,因为默认情况下返回的是String类型(上述中的。。。Whitelabel Error Page。。。),因为这样就不存在转换异常了!这个在https://blog.csdn.net/qq_29025955/article/details/134294967这里面有体现。

如何实现openfeign的response的拦截

参考:如何实现对openfeign的请求request和响应response的拦截

openfeign拦截到response(含服务器端返回的信息),如何将服务器端返回的信息返回给调用方法的呢?

通过配置openfeign客户端的fallbackFactory属性,可以获取到信息(异常信息)!

在这里插入图片描述
FallbackFactory的配置参考:openfeign集成sentinel实现服务降级

  • openfeign客户端代码(增加openfeign的fallbackFactory)
@FeignClient(contextId = "202344171019", name = "checklist-service",fallbackFactory=ChecklistFeignFallback.class)
//@FeignClient(contextId = "202344171019", name = "checklist-service")
public interface ChecklistServiceFeignClient {
// 接口清单}
  • ChecklistFeignFallback.java
import org.springframework.stereotype.Component;
import feign.hystrix.FallbackFactory;@Component
public class ChecklistFeignFallback implements FallbackFactory<ChecklistServiceFeignClient> {@Overridepublic ChecklistFeignFallbackImpl create(Throwable cause) {System.out.println("++++++++++++调用了create方法()++++++++++++++++");ChecklistFeignFallbackImpl cffi = new ChecklistFeignFallbackImpl();cffi.setThrowable((Exception)cause);return cffi;}
}

上面的类中涉及到类ChecklistFeignFallbackImpl,此类实现了ChecklistServiceFeignClient接口:

  • ChecklistFeignFallbackImpl.java
public class ChecklistFeignFallbackImpl implements ChecklistServiceFeignClient {private Exception throwable;@Overridepublic ResponseInfo uploadReportWithCaSignature(Endoscopicreport endoscopicreport) throws IOException, Exception{ResponseInfo responseInfo = ResponseInfo.newInstance();responseInfo.setRespCode(ResponseInfo.ERROR_CODE);responseInfo.setRespMsg(throwable.getMessage());return responseInfo;}// 其他接口方法的实现。。。
}

所以,服务器端抛出异常后最终会走到ChecklistFeignFallbackImpl的方法里面去,可以在这里面写具体的业务实现。异常信息private Exception throwable;会通过ChecklistFeignFallback的create方法设值进去!!!,然后在这个实现类里面通过throwable.getMessage()取值出来!

测试

  • 注意,测试前需要在服务器端,添加自定义异常处理,以避免返回SpringMvc处理后的默认的。。。Whitelabel Error Page。。。信息。

在服务器端添加自定义的异常处理

import javax.servlet.http.HttpServletResponse;import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
public class GlobalExceptionAdvice {@ExceptionHandler(value =Exception.class)@ResponseBodypublic String myExceptionHandler(Exception e,HttpServletResponse response) {return "++++++++++全局异常="+e.getMessage();}}
  • 启动并测试
    在这里插入图片描述
    在ChecklistFeignFallback 中debug调试: 出现新问题!!!
    在这里插入图片描述
    原因分析
    为什么会返回HttpMessageConverter的转换异常?
    分析1:因为openfeign的reposne拦截器中获取到的信息是文本“++++++++++全局异常=后台业务异常。” ,然后openfeign将此文本尝试转换成ResponseInfo类型,这是无法转换,自然就会报错了。
    这种情况,不用fallbackFactory,在调用的地方的try…catch…也能够捕获到这个转换异常(上文有提到。)

    兜了半天,虽然服务器端的异常信息已经到了openfeign客户端的response中,但是,仍然没有呈现到用户端(最前端)。 现在就差临门一脚了!!

    分析2:虽然服务器端的异常信息到了reposne中,但是response的状态仍然是200(正常返回),我们(业务上)认为是异常(不正常的),但是对于Http请求/响应来说,这是完全正常的,所以状态是200。 而也正是因为这个200的状态,openfeign认为这次请求完全没有问题,于是就按照正常流程执行,将结果返回给调用接口(尝试返回成接口定义的返回类型ResponseInfo),这样就导致生成了转换异常。

解决方案

  1. 在服务器端的自定义异常处理中将response的状态设置为非200类,即是设值大于等于300!
    在这里插入图片描述

  2. 再次测试
    在这里插入图片描述
    这次获取到了服务器端异常信息,但是多了很多不需要的信息,如下浏览器弹出显示:
    在这里插入图片描述

  3. 如何去掉多余信息?

使用Feign的ErrorDecoder,抛出自定义异常(含异常信息)
注意!只有reposne的状态大于等于300的时候,才会进入下面的Exception ErrorDecoder的decode(String methodKey, Response response) 方法。

import java.io.IOException;import org.springframework.context.annotation.Configuration;import feign.Response;
import feign.Util;
import feign.codec.ErrorDecoder;@Configuration
public class CustomFeignErrorDecoder implements ErrorDecoder {@SuppressWarnings("deprecation")@Overridepublic Exception decode(String methodKey, Response response) {String errorMsg = "";if (response != null) {try {errorMsg = Util.toString(response.body().asReader());} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return new Exception(errorMsg);}}
  1. 再再次测试,成功!
    在这里插入图片描述
    浏览器弹窗提示:
    在这里插入图片描述

特别注意

  1. 服务端抛出的异常,会被服务端吞没掉,并不会直接将异常信息返回给openfeign客户端
  2. 被吞没掉的异常,处理后以。。。Whitelabel Error Page。。。形式返回给openfeign客户端,并且response的状态是200(成功!),所以客户端会认为此次请求完全没问题,正常执行流程。
  3. 所以,当需要服务器端的异常信息时,那就需要在服务器端自定义异常的处理,并返回异常(注意,这里是返回异常信息,不是抛出异常,其实本质就是设置response的body的内容),同时将reposne的状态设置为非200大类,(300及以上)。
  4. openfeign客户端收到服务器端返回的非200信息时,通过ErrorDecoder和fallbackFactory进行处理,最终将异常信息呈现给用户。
  5. 以上的各个过程,都可以根据实际业务需求,灵活处理。

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

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

相关文章

基于SSM+MySQL的的新闻发布系统设计与实现

目录 项目简介 项目技术栈 项目运行环境 项目截图 代码截取 源码获取 项目简介 新闻发布系统是一款基于Servletjspjdbc的网站应用程序&#xff0c;旨在提供一个全面且高效的新闻发布平台。该系统主要包括后台管理和前台新闻展示两个平台&#xff0c;涵盖了新闻稿件的撰写…

嵌入式软件设计

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

Java设计模式 – 四大类型

设计模式 – 四大类型 创建型模式结构型模式行为型模式J2EE模式 设计模式&#xff08;Design pattern&#xff09;是重构解决方案 根据书Design Patterns – Elements of Reusable Object-Oriented Software&#xff08;中文译名&#xff1a;设计模式 – 可复用的面向对象软件元…

【对象属性拷贝】⭐️按照需要转换的类型反射设置拷贝后对象的属性

背景&#xff1a; 小伙伴们大家好&#xff0c;最近开发的时候遇到一种情况&#xff0c;项目引入了全局序列化器来实现Date&#xff0c;LocalDateTime类型的字段根据时区转换&#xff0c;总体来说接口没什么要改动的&#xff0c;只要原来字段的属性是以上两种就行&#xff0c;但…

react+ts

1.概念 React和TypeScript集合使用的重点集中在 存储数据/状态有关的Hook函数以及组件接口的位置&#xff0c;这些地方最需要数据类型校验 2.使用Vite创建项目 Vite是前端工具链工具&#xff0c;可以帮助我们快速创建一个 reactts 的工程化环境出来 Vite官网&#xff1a;ht…

【C++】文件IO

目录 一、C语言的输入输出二、流的概念三、operator bool四、C文件IO流ifstream和ofstreamostringstream和istringstreamstringstream 一、C语言的输入输出 C语言中我们用到的最频繁的输入输出方式就是 scanf() 和 printf()。 scanf()&#xff1a; 从标准输入设备&#xff08…

GitHub的使用操作

记得看目录哦&#xff01; 1. 创建仓库2. 下载desktop3. 把创建的库克隆到本地4. 文件拷贝到本地仓库![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/7171ac6c4ca14e3b8d22717121f79c9e.png)5. 在网址后面加/compare进行比较6. 给系统添加功能 1. 创建仓库 2. 下载…

微信小程序 安卓/IOS兼容问题

一、背景 在开发微信小程序时&#xff0c;不同的手机型号会出现兼容问题&#xff0c;特此记录一下 二、安卓/IOS兼容问题总结 2.1、new Date()时间转换格式时&#xff0c;IOS不兼容 问题&#xff1a;在安卓中时间格式2024-1-31 10:10:10&#xff0c;但是在iOS中是不支持 &q…

LeetCode383. 赎金信

383. 赎金信 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以&#xff0c;返回 true &#xff1b;否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。 示例 1&#xff1…

工业智能网关构建智慧污水处理远程监测及管理

污水处理厂是为了处理生活污水和工业废水而建立的设施。为了监测和控制污水处理过程&#xff0c;现代污水处理厂采用了智能工业网关物联网技术。智慧污水系统能够通过工业网关远程监测厂内各个环节的运行情况&#xff0c;提高处理效率和管理水平。 智能工业网关能够将不同设备…

Vue3学习记录(二)--- 组合式API之计算属性和侦听器

一、计算属性 1、简介 ​ 计算属性computed()&#xff0c;用于根据依赖的响应式变量的变化&#xff0c;进行自动的计算&#xff0c;并返回计算后的结果。当依赖的响应式变量发生变化时&#xff0c;computed()会自动进行重新计算&#xff0c;并返回最新的计算结果。如果依赖的…

9 个成功的会员网站案例

对于许多数字创作者来说&#xff0c;会员网站可以改变游戏规则。 当您建立一个会员网站时&#xff0c;您不仅会提高收入。也可以根据您的主题或想法创建有意义的业务。围绕这种想法将人们联系起来。 如果您正在考虑创建一个会员网站 或想要修改您的会员网站&#xff0c;我们将…