栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Feign 异常传递

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Feign 异常传递

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

思路

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

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

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

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

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

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

实现

运行环境

feign:org.springframework.cloud:spring-cloud-starter-openfeign:2.2.9.RELEASEspring:org.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”:“余额不足”}),本例中是将异常异常信息直接输出为:异常类型:异常描述`

public class ExceptionHandle implements HandlerExceptionResolver, Ordered {

	private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandle.class);

	private int order = Ordered.LOWEST_PRECEDENCE;


	@Override
	public ModelAndView resolveException(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 : new RuntimeException(ExceptionUtils.getStackTrace(ex));
			try {
				IOUtils.write(SerializableUtil.serialize(exception), response.getOutputStream());
			} catch (IOException e) {
				//ignore
			}
			return new ModelAndView();
		}
		try {
			String errorMsg = String.format("%s : %s", ex.getClass().getName(), ex.getMessage());
			IOUtils.write(errorMsg, response.getOutputStream());
		} catch (IOException e) {
			//ignore
		}
		return new ModelAndView();
	}

	@Override
	public int getOrder() {
		return order;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	
	private boolean normalException(Exception exception, HandlerMethod methodHandle) {
		// Checked Exception
		if (!(exception instanceof RuntimeException)) {
			return true;
		}
		// 方法声明中的异常
		Method method = methodHandle.getMethod();
		for (Class exceptionClass : method.getExceptionTypes()) {
			if (exception.getClass().equals(exceptionClass)) {
				return true;
			}
		}
		// 如果异常类和接口类在同一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)) {
				return true;
			}
		}

		// jdk exception
		String className = exception.getClass().getName();
		if (className.startsWith("java.") || className.startsWith("javax.")) {
			return true;
		}
		// customer exception
		if (className.startsWith("com.zto.zbase.common") || className.startsWith("com.zto.zbase.manager")) {
			return true;
		}
		return false;
	}


	public static String getCodebase(Class cls) {
		if (cls == null) {
			return null;
		}
		ProtectionDomain domain = cls.getProtectionDomain();
		if (domain == null) {
			return null;
		}
		CodeSource source = domain.getCodeSource();
		if (source == null) {
			return null;
		}
		URL location = source.getLocation();
		if (location == null) {
			return null;
		}
		return location.getFile();
	}
}

设置Spring异常拦截器

@Configuration
public class ExceptionInterceptor implements WebMvcConfigurer {
  @Override
	public void extendHandlerExceptionResolvers(List resolvers) {
		ExceptionHandle exceptionHandle = new ExceptionHandle();
		exceptionHandle.setOrder(1);
		resolvers.add(exceptionHandle);
	}
}

java Exception序列化,反序列化工具 SerializableUtil.java

public class SerializableUtil {

	public static byte[] serialize(Exception exception) throws IOException {
		try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
			 ObjectOutputStream oo = new ObjectOutputStream(byteArrayOutputStream);) {
			oo.writeObject(exception);
			oo.flush();
			return byteArrayOutputStream.toByteArray();
		}
	}

	public static Exception deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
		try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
			 ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);) {
			return (Exception) ois.readObject();
		}
	}

}

2. 客户端

自定义Feign请求过滤器

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

public class ExceptionRequestInterceptor implements RequestInterceptor {
  @Override
	public void apply(RequestTemplate template) {
		//异常序列化
		template.header(RemoteConstant.Heard.ERROR_ENCODE, RemoteConstant.Heard.ERROR_ENCODE_SERIAL);
	}
}

Feign异常解析

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

public class FeignExceptionErrorDecoder implements ErrorDecoder {

	@Override
	public Exception decode(String methodKey, Response response) {
		if (response.body() != null) {
			Collection errorDecodes = response.headers().get(RemoteConstant.Heard.ERROR_ENCODE);
			if (CollectionUtils.isEmpty(errorDecodes)) {
				return errorStatus(methodKey, response);
			}
			String decodeType = errorDecodes.toArray()[0].toString();
			if (ERROR_ENCODE_SERIAL.equals(decodeType) && HttpStatus.INTERNAL_SERVER_ERROR.value() == response.status()) {
				try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
					 InputStream inputStream = response.body().asInputStream();) {
					IOUtils.copy(inputStream, byteArrayOutputStream);
					try {
						return SerializableUtil.deserialize(byteArrayOutputStream.toByteArray());
					} catch (ClassNotFoundException e) {
						return new RuntimeException(byteArrayOutputStream.toString());
					}
				} catch (IOException e) {
					return e;
				}
			}
		}
		return errorStatus(methodKey, response);
	}
  }

设置Feign配置

@Configuration
public class FeignConfiguration {
  @Bean
	public RequestInterceptor exceptionRequestInterceptor() {
		return new ExceptionRequestInterceptor();
	}
  @Bean
		public ErrorDecoder feignErrorDecoder() {
			return new FeignExceptionErrorDecoder();
		}
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/721013.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号