目录
说明
在分布式系统中,系统与系统之间通常使用FeignClient
进行数据交互,但当被调用方需要抛出特定异常时,调用方不能捕获到异常信息进行业务处理,本文档对此种现象进行了说明,并提供一种解决方案。
准备
代码准备
client
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@FeignClient(name = "service-a", contextId = "service-client-a", url = "${feign.service.url}", path = "/test")
public interface TestApi {
@GetMapping("/no-throws-ex")
String noThrowsEx();
@GetMapping("/has-throws-ex")
String hasThrowsEx(@RequestParam Integer code) throws SysEx;
}
@Getter
@Builder
public class SysEx extends RuntimeException{
private ResCode resCode;
public SysEx(ResCode resCode) {
super(resCode.getMsg());
this.resCode = resCode;
}
}
package org.example;
import lombok.Getter;
@Getter
public enum ResCode {
OK(0,"ok"),
E_500(500,"500的异常"),
E_1000(1000,"1000的异常"),
E_1001(1001,"1001的异常"),
;
private int code;
private String msg;
ResCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
public static ResCode ofCode(int code){
ResCode[] values = ResCode.values();
for (ResCode resCode : values){
if (code == resCode.code){
return resCode;
}
}
return ResCode.E_500;
}
}
package org.example;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class R<T> {
private int code;
private String msg;
private T data;
private boolean ok;
private R(int code, String msg, T data, boolean ok) {
this.code = code;
this.msg = msg;
this.data = data;
this.ok = ok;
}
public static <T> R<T> ok(T t){
return new R(ResCode.OK.getCode(), ResCode.OK.getMsg(), t, true);
}
public static <T> R<T> error(ResCode resCode){
return new R(resCode.getCode(), resCode.getMsg(), null, false);
}
}
service 底层服务(被调用方) 提供2个测试接口,并对特定异常进行统一异常处理
@GetMapping("/no-throws-ex")
public ResponseEntity<String> noThrowsEx(){
return ResponseEntity.ok("noThrowsEx");
}
@GetMapping("/has-throws-ex")
public ResponseEntity<String> hasThrowsEx(@RequestParam Integer code){
switch (code){
case 1:
throw new SysEx(ResCode.E_1000);
default:
return ResponseEntity.ok("hasThrowsEx");
}
}
@ExceptionHandler(SysEx.class)
public ResponseEntity<R<?>> sysEx(SysEx e){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(R.error(e.getResCode()));
}
api 调用方 接口调用service服务中的方法
@Autowired
private TestApi testApi;
@GetMapping("/test")
public ResponseEntity<String> test(@RequestParam Integer code) {
switch (code){
case 1:
case 2:
try {
String s = testApi.hasThrowsEx(code);
return ResponseEntity.ok(s);
} catch (SysEx e){
log.error(e.getResCode());
return ResponseEntity.ok("抛出异常");
}
case 3:
return ResponseEntity.ok(testApi.noThrowsEx());
default:
return ResponseEntity.ok("异常");
}
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> runtimeEx(Exception e){
return ResponseEntity.status(500).body("error");
}
@ExceptionHandler(SysEx.class)
public ResponseEntity<R<?>> sysEx(SysEx e){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(R.error(e.getResCode()));
}
测试
-
在浏览器中访问接口
http://127.0.0.1:9003/test/test?code=3
返回数据noThrowsEx
证明服务之间数据交互正常; -
在浏览器中访问接口
http://127.0.0.1:9003/test/test?code=2
返回数据hasThrowsEx
,接口hasThrowsEx
未抛出异常,正常返回; -
在浏览器中访问接口
http://127.0.0.1:9003/test/test?code=1
,由于调用方也存在异常处理机制,当出现Exception
时,则返回数据error
;证明当code=1时,服务抛出异常,导致系统出现错误,未能响应数据抛出异常
。此时当在浏览器中访问接口http://127.0.0.1:9001/test/has-throws-ex?code=1
时,由于服务中统一异常处理机制,返回数据为:{ "code": 1000, "msg": "1000的异常", "data": null, "ok": false }
由上述几次测试可知,当底层服务出现异常时,上层服务不能正确的捕获底层服务抛出的异常信息,并出现了500的错误;且未能将底层服务相关提示信息展示到上层服务,这种现象对于上层调用方极其不友好,下面对
client
中代码进行了改造:TestApi
@FeignClient(name = "service-a", contextId = "service-client-a", url = "${feign.service.url}", path = "/test",fallback = ErrorFallback.class)
在代码中添加了fallback = ErrorFallback.class
;
ErrorFallback
package org.example;
import cn.hutool.json.JSONUtil;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.nio.charset.Charset;
@Configuration
@Log4j2
public class ErrorFallback implements ErrorDecoder {
@Override
public Exception decode(String s, Response response) {
if (response.status() != HttpStatus.OK.value()){
log.info("s: {}",s);
log.info("status: {}" ,response.status());
try {
String content = StreamUtils.copyToString(response.body().asInputStream(), Charset.defaultCharset());
log.info("content: {}", content);
R r = JSONUtil.toBean(content, R.class);
return new SysEx(ResCode.ofCode(r.getCode()));
} catch (IOException e) {
e.printStackTrace();
}
}
return new SysEx(ResCode.E_500);
}
}
重新启动服务后,访问接口http://127.0.0.1:9003/test/test?code=1
,返回数据为抛出异常
,证明
ErrorFallback
配置成功