openfeign客户端A调用服务B,服务B抛出异常时,客户端A接收的几种情况
openfeign集成sentinel实现服务降级
OpenFeign客户端调用,服务端查询结果为null并返回给feign客户端,引发客户端报错
openfeign客户端调用远程服务端接口,传递参数为null及服务端接口返回值为null的情况
直接在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)
@ResponseBody
public 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注解的说明:
带此注解的类是一个含有 @ExceptionHandler, @InitBinder, or @ModelAttribute注解方法的组件类,这个组件类可以贯穿于多个Controller类之间!
此注解用于处理异常(在处理类/处理方法中)。
带有此注解的处理方法有非常复杂的签名。
参数和返回可以有如下类型:
测试!是否能够获取到真实信息?
@GetMapping("/ex/handler")
public String testExceptionHandler() throws Exception {
// throw new Exception("抛出了异常哈。。。");
int x = 3/0;
return "nothing";
}
浏览器访问服务端,通过自定义异常处理可以获取到服务端准确的异常信息!
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.其他业务代码省略
@Override
public ResponseInfo uploadReportWithCaSignature(Endoscopicreport endoscopicreport) throws Exception {
if (1==1)
throw new CustomException("后台业务异常。");
// 其他,省略。。。。。。
}
直接在openfeign客户端调用的地方是看不到服务器端返回的真实信息的!客户端调用处的调试信息如下:
上图中的异常信息是:openfeign客户端接收到服务器端的返回信息后将其转换成 返回类型(这里是ResponseInfo类型)时抛出的异常(HttpMessageConverter转换异常),而并非是服务器端返回的信息。
这就需要在openfeign客户端收到响应response之后(在返回成ResponseInfo类型之前)进行debug。
也就是要写一个response的拦截器(以便查看response中的具体内容—服务器端返回的信息)。
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;
@Bean
public OkHttpClient.Builder okHttpClientBuilder() {
return new OkHttpClient.Builder().addInterceptor(new FeignOkHttpClientResponseInterceptor());
}
public static class FeignOkHttpClientResponseInterceptor implements Interceptor {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public 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();
// 根据业务使用request
return response.newBuilder().body(ResponseBody.create(mediaType, bodyContent)).build();
}
}
}
从上述代码中的String bodyContent = response.body().string();的`bodyContent 中可以获取到服务器端返回的信息!
所以,这才是服务器端返回的真实信息!,而非上面的HttpMessageConverter转换异常信息!
例外的情况:
如果openfeign客户端调用的返回类型是String,则可以直接获取到,因为默认情况下返回的是String类型(上述中的。。。Whitelabel Error Page。。。),因为这样就不存在转换异常了!这个在https://blog.csdn.net/qq_29025955/article/details/134294967这里面有体现。
参考:
如何实现对openfeign的请求request和响应response的拦截
通过配置openfeign客户端的fallbackFactory属性,可以获取到信息(异常信息)!
FallbackFactory的配置参考
:openfeign集成sentinel实现服务降级
@FeignClient(contextId = "202344171019", name = "checklist-service",fallbackFactory=ChecklistFeignFallback.class)
//@FeignClient(contextId = "202344171019", name = "checklist-service")
public interface ChecklistServiceFeignClient {
// 接口清单
}
import org.springframework.stereotype.Component;
import feign.hystrix.FallbackFactory;
@Component
public class ChecklistFeignFallback implements FallbackFactory<ChecklistServiceFeignClient> {
@Override
public ChecklistFeignFallbackImpl create(Throwable cause) {
System.out.println("++++++++++++调用了create方法()++++++++++++++++");
ChecklistFeignFallbackImpl cffi = new ChecklistFeignFallbackImpl();
cffi.setThrowable((Exception)cause);
return cffi;
}
}
上面的类中涉及到类ChecklistFeignFallbackImpl,此类实现了ChecklistServiceFeignClient接口:
public class ChecklistFeignFallbackImpl implements ChecklistServiceFeignClient {
private Exception throwable;
@Override
public 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)
@ResponseBody
public 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),这样就导致生成了转换异常。
解决方案
如何去掉多余信息?
使用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")
@Override
public 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 block
e.printStackTrace();
}
}
return new Exception(errorMsg);
}
}
被吞没掉的异常,处理后以。。。Whitelabel Error Page。。。形式返回给openfeign客户端,并且response的状态是200(成功!),所以客户端会认为此次请求完全没问题,正常执行流程。
openfeign客户端收到服务器端返回的非200信息时,通过ErrorDecoder和fallbackFactory进行处理,最终将异常信息呈现给用户。