Feign 异常传递

2022年7月24日12:15:15

通过Feign交互,如果服务端发送异常,feign默认会将异常包装为自定义FeignException,这样我们就不能直接获取服务端抛出的异常类型和异常描述。下面我们就来使用对象序列化的形式,将服务端的异常(Exception对象)传递到客户端。

思路

实现服务端异常传递,需要满足以下特点

  1. 服务端既能支持http直连,也能支持公共Feign请求异常对象传递

    http直连:需要对异常进行包装,比如返回{"status":false,"message":"余额不足"} REST格式风格响应信息

    Feign客户端:服务端将异常进行序列化,客户端将异常反序列化

  2. 服务端的异常类型在客户端不存在,需要在服务端将异常转换为RunTimeException

    如服务端和客户端依赖不同的jar,会导致服务端的异常无法在客户端进行反序列化,导致客户端解析错误,最好的方式是将可能抛出的异常,在Feign 远程服务api接口声明中显示抛出,这样服务端和客户端的异常类型一致,在序列化时不会报错。

实现

  • 运行环境
    • feignorg.springframework.cloud:spring-cloud-starter-openfeign:2.2.9.RELEASE
    • springorg.springframework.boot:spring-boot-starter-parent:2.3.12.RELEASE

1. 服务端

  • 服务端自定义异常拦截器

    为了兼容Http直连,使用Feign请求时,会在请求Heard中加标签入RemoteConstant.Heard.ERROR_ENCODE=RemoteConstant.Heard.ERROR_ENCODE_SERIAL来标记是Feign请求,并且将异常序列化,如果没有配置这个Heard,或者配置的``RemoteConstant.Heard.ERROR_ENCODE是其他值,代表异常是其他的返回形式(如:{“status”:false,“message”:“余额不足”}),本例中是将异常异常信息直接输出为:异常类型:异常描述`

    publicclassExceptionHandleimplementsHandlerExceptionResolver,Ordered{privatestaticfinalLogger LOGGER=LoggerFactory.getLogger(ExceptionHandle.class);privateint order=Ordered.LOWEST_PRECEDENCE;@OverridepublicModelAndViewresolveException(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex){
    		LOGGER.error("Request error", ex);String errorEncode= request.getHeader(RemoteConstant.Heard.ERROR_ENCODE);if(RemoteConstant.Heard.ERROR_ENCODE_SERIAL.equals(errorEncode)){
    			response.addHeader(RemoteConstant.Heard.ERROR_ENCODE,RemoteConstant.Heard.ERROR_ENCODE_SERIAL);
    			response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());HandlerMethod method=(HandlerMethod) handler;boolean normalException=normalException(ex, method);Exception exception= normalException? ex:newRuntimeException(ExceptionUtils.getStackTrace(ex));try{IOUtils.write(SerializableUtil.serialize(exception), response.getOutputStream());}catch(IOException e){//ignore}returnnewModelAndView();}try{String errorMsg=String.format("%s : %s", ex.getClass().getName(), ex.getMessage());IOUtils.write(errorMsg, response.getOutputStream());}catch(IOException e){//ignore}returnnewModelAndView();}@OverridepublicintgetOrder(){return order;}publicvoidsetOrder(int order){this.order= order;}/**
    	 * 是否是可序列化异常
    	 *
    	 * @param exception
    	 * @param methodHandle
    	 * @return
    	 */privatebooleannormalException(Exception exception,HandlerMethod methodHandle){// Checked Exceptionif(!(exceptioninstanceofRuntimeException)){returntrue;}// 方法声明中的异常Method method= methodHandle.getMethod();for(Class<?> exceptionClass: method.getExceptionTypes()){if(exception.getClass().equals(exceptionClass)){returntrue;}}// 如果异常类和接口类在同一jar文件中,则直接抛出Class<?>[] interfaces= method.getDeclaringClass().getInterfaces();for(Class<?> interfaceClazz: interfaces){RemoteClient remoteClient= interfaceClazz.getDeclaredAnnotation(RemoteClient.class);if(null== remoteClient){continue;}String serviceFile=getCodeBase(interfaceClazz);String exceptionFile=getCodeBase(exception.getClass());if(serviceFile==null|| exceptionFile==null|| serviceFile.equals(exceptionFile)){returntrue;}}// jdk exceptionString className= exception.getClass().getName();if(className.startsWith("java.")|| className.startsWith("javax.")){returntrue;}// customer exceptionif(className.startsWith("com.zto.zbase.common")|| className.startsWith("com.zto.zbase.manager")){returntrue;}returnfalse;}publicstaticStringgetCodeBase(Class<?> cls){if(cls==null){returnnull;}ProtectionDomain domain= cls.getProtectionDomain();if(domain==null){returnnull;}CodeSource source= domain.getCodeSource();if(source==null){returnnull;}URL location= source.getLocation();if(location==null){returnnull;}return location.getFile();}}
  • 设置Spring异常拦截器

    @ConfigurationpublicclassExceptionInterceptorimplementsWebMvcConfigurer{@OverridepublicvoidextendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers){ExceptionHandle exceptionHandle=newExceptionHandle();
    		exceptionHandle.setOrder(1);
    		resolvers.add(exceptionHandle);}}
  • java Exception序列化,反序列化工具SerializableUtil.java

    publicclassSerializableUtil{publicstaticbyte[]serialize(Exception exception)throwsIOException{try(ByteArrayOutputStream byteArrayOutputStream=newByteArrayOutputStream();ObjectOutputStream oo=newObjectOutputStream(byteArrayOutputStream);){
    			oo.writeObject(exception);
    			oo.flush();return byteArrayOutputStream.toByteArray();}}publicstaticExceptiondeserialize(byte[] bytes)throwsIOException,ClassNotFoundException{try(ByteArrayInputStream byteArrayInputStream=newByteArrayInputStream(bytes);ObjectInputStream ois=newObjectInputStream(byteArrayInputStream);){return(Exception) ois.readObject();}}}

2. 客户端

  • 自定义Feign请求过滤器

    将Feign请求Heard中,设置当前异常序列化

    publicclassExceptionRequestInterceptorimplementsRequestInterceptor{@Overridepublicvoidapply(RequestTemplate template){//异常序列化
    		template.header(RemoteConstant.Heard.ERROR_ENCODE,RemoteConstant.Heard.ERROR_ENCODE_SERIAL);}}
  • Feign异常解析

    只会对RemoteConstant.Heard.ERROR_ENCODE=RemoteConstant.Heard.ERROR_ENCODE_SERIAL标记的异常响应反序列化

    publicclassFeignExceptionErrorDecoderimplementsErrorDecoder{@OverridepublicExceptiondecode(String methodKey,Response response){if(response.body()!=null){Collection<String> errorDecodes= response.headers().get(RemoteConstant.Heard.ERROR_ENCODE);if(CollectionUtils.isEmpty(errorDecodes)){returnerrorStatus(methodKey, response);}String decodeType= errorDecodes.toArray()[0].toString();if(ERROR_ENCODE_SERIAL.equals(decodeType)&&HttpStatus.INTERNAL_SERVER_ERROR.value()== response.status()){try(ByteArrayOutputStream byteArrayOutputStream=newByteArrayOutputStream();InputStream inputStream= response.body().asInputStream();){IOUtils.copy(inputStream, byteArrayOutputStream);try{returnSerializableUtil.deserialize(byteArrayOutputStream.toByteArray());}catch(ClassNotFoundException e){returnnewRuntimeException(byteArrayOutputStream.toString());}}catch(IOException e){return e;}}}returnerrorStatus(methodKey, response);}}
  • 设置Feign配置

    @ConfigurationpublicclassFeignConfiguration{@BeanpublicRequestInterceptorexceptionRequestInterceptor(){returnnewExceptionRequestInterceptor();}@BeanpublicErrorDecoderfeignErrorDecoder(){returnnewFeignExceptionErrorDecoder();}}
  • 作者:vhicool
  • 原文链接:https://blog.csdn.net/u011618288/article/details/122740437
    更新时间:2022年7月24日12:15:15 ,共 5907 字。