Feign异常捕获

目录

说明

准备

代码准备

测试


说明

在分布式系统中,系统与系统之间通常使用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()));
    }

 

测试

  1. 在浏览器中访问接口http://127.0.0.1:9003/test/test?code=3返回数据noThrowsEx证明服务之间数据交互正常;

  2. 在浏览器中访问接口http://127.0.0.1:9003/test/test?code=2返回数据hasThrowsEx,接口hasThrowsEx未抛出异常,正常返回;

  3. 在浏览器中访问接口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配置成功

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用FeignClient进行接口调用时,通常情况下,客户端无法捕获被调用方抛出的特定异常信息。这是因为FeignException会被作为默认的异常类型返回给客户端,而不是被调用方真正抛出的异常。 为了解决这个问题,可以通过在被调用方的服务中使用@ExceptionHandler注解来统一处理被抛出的自定义异常。这样,在被调用方抛出异常后,可以在异常处理方法中对异常进行适当的处理,并将自定义的异常信息返回给FeignClient。 然后,在FeignClient中可以通过对FeignException进行处理,提取出被调用方真正抛出的异常信息。这样,客户端就能根据不同的异常进行有针对性的处理了。具体的处理方式可以根据业务需求来确定。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Feign异常捕获](https://blog.csdn.net/u010659671/article/details/113887146)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [FeignClient 客户端如何捕获到服务端抛出的自定义异常](https://blog.csdn.net/yangchao1125/article/details/112179075)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值