# feign-exception **Repository Path**: kingwashes/feign-exception ## Basic Information - **Project Name**: feign-exception - **Description**: 服务于服务间进行调用时将异常进行传递 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2022-05-30 - **Last Updated**: 2022-05-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [TOC] # feign-exception 使用说明 ##### 前言 *在springcloud中 服务与服务之间,通常使用feign进行服务调用。但是在fei中,默认返回feign包装后的异常。eg:如果服务a调用服务b,当服务b发生异常时,如果什么都不处理的话,将抛出feign自带的异常,**并不会携带服务b本身抛出的异常**,本组件就是为了解决这个问题。* [跳转到github](https://github.com/mintonzhang/feign-exception) ### 1.版本 ```xml maven用户 cn.minsin.feign feign-exception ${last-release} ``` ### 2.优势 ##### 2.1.体积小 ​ jar包没有集成任何额外第三方jar,纯spring相关组件。 ##### 2.2.使用简单,注解启动 ​ 只需要一个注解即可开启,如不加注解,不会自动配置任何启动项。 ##### 2.3.重写printStackTrace, 链路清晰,排查异常清晰(栈信息是倒序打印出来的) ```text Slf4j打印到控制台: cn.minsin.feign.exception.RemoteCallException: 模拟错误 at [HAPPEN]:[provider] timestamp:'2020-06-05 17:18:56.103',exceptionClass:'java.lang.RuntimeException',message:'模拟错误',path: '/data2'.(:0) ~[na:na] at [THROW]:[consumer] timestamp:'2020-06-05 17:18:58.121',exceptionClass:'cn.minsin.feign.exception.RemoteCallException',message:'模拟错误',path: '/cdata2'.(:0) ~[na:na] at [THROW]:[provider] timestamp:'2020-06-05 17:18:58.219',exceptionClass:'cn.minsin.feign.exception.RemoteCallException',message:'模拟错误',path: '/data1'.(:0) ~[na:na] at [END]:[consumer] timestamp:'2020-06-05 17:18:58.222',exceptionClass:'cn.minsin.feign.exception.RemoteCallException',message:'模拟错误',path: '/cdata1'.(:0) ~[na:na] slf4j打印到日志: cn.minsin.feign.exception.RemoteCallException: 模拟错误 at [HAPPEN]:[provider] timestamp:'2020-06-05 17:18:56.103',exceptionClass:'java.lang.RuntimeException',message:'模拟错误',path: '/data2'.(:0) at [THROW]:[consumer] timestamp:'2020-06-05 17:18:58.121',exceptionClass:'cn.minsin.feign.exception.RemoteCallException',message:'模拟错误',path: '/cdata2'.(:0) at [THROW]:[provider] timestamp:'2020-06-05 17:18:58.219',exceptionClass:'cn.minsin.feign.exception.RemoteCallException',message:'模拟错误',path: '/data1'.(:0) at [END]:[consumer] timestamp:'2020-06-05 17:18:58.222',exceptionClass:'cn.minsin.feign.exception.RemoteCallException',message:'模拟错误',path: '/cdata1'.(:0) 直接使用异常.printStackTrace 打印到控制台 cn.minsin.feign.exception.RemoteCallException : 模拟错误 [HAPPEN]:[provider] timestamp:'2020-06-05 17:25:53.933',exceptionClass:'java.lang.RuntimeException',message:'模拟错误',path: '/data2'.(:0) [THROW]:[consumer] timestamp:'2020-06-05 17:25:54.025',exceptionClass:'cn.minsin.feign.exception.RemoteCallException',message:'模拟错误',path: '/cdata2'.(:0) [THROW]:[provider] timestamp:'2020-06-05 17:25:54.071',exceptionClass:'cn.minsin.feign.exception.RemoteCallException',message:'模拟错误',path: '/data1'.(:0) [END]:[consumer] timestamp:'2020-06-05 17:25:54.072',exceptionClass:'cn.minsin.feign.exception.RemoteCallException',message:'模拟错误',path: '/cdata1'.(:0) ``` ###### 输出格式: **格式==>[status]:[applicationName] timestamp:'timestamp',exceptionClass:'exception',message:'message',path: 'url'** - status 状态 有HAPPEN、THROW、END三种。流程 HAPPEN==>THROW==>END 当多个服务调用时 THROW会出现多个 - applicationName 发生异常的application-name - timestamp 出现异常的时间 格式yyyy-MM-dd HH:mm:ss.SSS - exception 出现的异常全称 - message 异常返回的message - path feign请求的url **注意:错误栈信息是以发生时间升序排列,也就是最开始发生的异常在最上面。** ### 4.快速上手 在启动类上加上@EnableFeignExceptionHandler注解即可开启 ```java @EnableDiscoveryClient @SpringBootApplication //只需要开启此注解 @EnableFeignExceptionHandler // @EnableFeignClients public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } } ``` ### 5.核心功能及拓展 - 默认异常处理器 ```java @Slf4j public class FeignExceptionHandler extends DefaultErrorAttributes { @Override public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace); Throwable error = super.getError(webRequest); List exceptionChains = null; if (error instanceof RemoteCallException) { exceptionChains = ((RemoteCallException) error).getExceptionChains(); } else { Object attribute = webRequest.getAttribute(ExceptionConstant.EXCEPTION_CHAIN_KEY, RequestAttributes.SCOPE_REQUEST); if (attribute != null) { exceptionChains = JSON.parseArray(attribute.toString(), ExceptionChain.class); } if (exceptionChains == null) { exceptionChains = new ArrayList<>(1); } } ExceptionChain exceptionChain = new ExceptionChain(); exceptionChain.setMessage(error.getMessage()); exceptionChain.setPath(errorAttributes.get("path").toString()); exceptionChain.setTimestamp(new Date()); exceptionChain.setApplicationName(FeignExceptionHandlerContext.getApplicationName()); //添加发生的异常类信息 以便下一步处理 if (error.getClass() != null) { exceptionChain.setExceptionClass(error.getClass().getTypeName()); } exceptionChains.add(exceptionChain); errorAttributes.put(ExceptionConstant.EXCEPTION_CHAIN_KEY, exceptionChains); return errorAttributes; } } ``` 作用:当其他feign客户端在调用接口时,如果本服务发送异常,应该怎么返回异常信息?可以理解为异常处理器抛出=>异常解析器。需要实现ErrorAttributes - 默认异常解析器 ```java //FeignExceptionDecoder @Slf4j public class FeignExceptionDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { try { Reader reader = response.body().asReader(); String body = Util.toString(reader); ExceptionModel exceptionModel = JSON.parseObject(body, ExceptionModel.class); return new RemoteCallException(exceptionModel.getMessage(), exceptionModel.getExceptionChain()); } catch (Exception e) { log.error("{} has an unknown exception.", methodKey, e); return new RemoteCallException("unKnowException", e); } } } ``` 作用:异常解析器,需要实现ErrorDecoder。作用是,当feign服务调用其他服务出现异常,收到的异常数据流处理类. - 自定义异常处理器及解析器 异常处理器需要实现ErrorAttributes、异常解析器需要实现ErrorDecoder .然后使用@EnableFeignExceptionHandler,代码如下 ```java @EnableDiscoveryClient @SpringBootApplication @EnableFeignClients @EnableFeignExceptionHandler(decoderClass = DecodeCoderClass.class,handlerClass =handlerClass.class ) public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } } ``` 要点: 无论是decoderClass 还是handlerClass 都需要无参构造方法。 因为EnableFeignExceptionHandler注册bean是需要初始化一个对象 如果,确实需要注入bean或者属性, 需要实现BeanFactoryPostProcessor 来获取BeanFactory 这样就可以获取到bean了。 但是,一般来说,处理器和解析器是不需要bean的. ### 5.静态spring常量容器 ```java public final class FeignExceptionHandlerContext { private static Environment ENVIRONMENT; public static String getApplicationName() { return ENVIRONMENT == null ? "unknownServer" : ENVIRONMENT.getProperty("spring.application.name"); } public static Environment getEnvironment() { return ENVIRONMENT; } public static void setEnvironment(Environment environment) { if (ENVIRONMENT == null) { FeignExceptionHandlerContext.ENVIRONMENT = environment; } } } ``` 说明:这个类,在注入时会将当前环境存放进去,但是只能赋值一次。通过Environment 可以获取到yaml或properties中的配置,默认提供获取application name的方法。 ### 6.CA条件断言 (condition assert) ``` //DEMO public static void main(String[] args) { String s =null; CA.isNull(s).withRuntimeException("测试异常"); } console log: Exception in thread "main" java.lang.RuntimeException: 测试异常 at cn.minsin.feign.assert_.Then.withRuntimeException(Then.java:81) at cn.minsin.feign.default_.FeignExceptionDecoder.main(FeignExceptionDecoder.java:40) ``` 说明:条件断言 默认的判断,默认只有在判断条件为true才会执行. 可以通过with方法进行手动判断. 这样一来断言的灵活性大大增强。 这是从[mutils-core](https://github.com/mintonzhang/mutils-spring-boot-starter/tree/master/mutils-dependencies/mutils-core-manage)中移植过来的。 ### 7.DEMO [下载之后在本地运行测试](https://github.com/mintonzhang/feign-exception/tree/master/demo)。