Feign调用接口解决处理内部异常的问题

 更新时间:2021年06月23日 15:49:57   作者:萌中芢  
这篇文章主要介绍了Feign调用接口解决处理内部异常的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

问题描述:

当使用feign调用接口,出现400~500~的接口问题时。会出错feign:FeignException。(因为是错误,只能用catch Throwable,不可使用catch Exception捕获异常)导致程序无法继续运行。

问题原因:

由于feign默认的错误处理类是FunFeignFallback会throw new AfsBaseExceptio导致外部无法捕获异常。

package com.ruicar.afs.cloud.common.core.feign; 
import com.alibaba.fastjson.JSONObject;
import com.ruicar.afs.cloud.common.core.exception.AfsBaseException;
import com.ruicar.afs.cloud.common.core.util.IResponse;
import feign.FeignException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.lang.Nullable; 
import java.lang.reflect.Method;
import java.util.Objects;
 
@Data
@AllArgsConstructor
@Slf4j
public class FunFeignFallback<T> implements MethodInterceptor {
    private final Class<T> targetType;
    private final String targetName;
    private final Throwable cause; 
    private static byte JSON_START = '{';
 
    @Nullable
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        String errorMessage = cause.getMessage();
        if (!(cause instanceof FeignException)) {
            log.error("FunFeignFallback:[{}.{}] serviceId:[{}] message:[{}]", targetType.getName(), method.getName(), targetName, errorMessage);
            log.error("feign调用失败", cause);
            return IResponse.fail("请求失败,请稍后再试");
        }
        int status = ((FeignException.FeignClientException) this.cause).status();
        boolean isAuthFail = (status==426||status==403||status==401)&&"afs-auth".equals(targetName);
        FeignException exception = (FeignException) cause;
        if(isAuthFail){
            log.warn("授权失败==========原始返回信息:[{}]",exception.contentUTF8());
        }else {
            log.error("FunFeignFallback:[{}.{}] serviceId:[{}] message:[{}]", targetType.getName(), method.getName(), targetName, errorMessage);
            log.error("", cause);
            log.error("原始返回信息{}",exception.contentUTF8());
        }
        if(method.getReturnType().equals(Void.class)){
            throw new AfsBaseException("接口调用失败");
        }
        if(method.getReturnType().equals(IResponse.class)){
            if(exception instanceof FeignException.Forbidden){
                return IResponse.fail("没有权限").setCode("403");
            }
            if(exception instanceof FeignException.NotFound){
                return IResponse.fail("请求路径不存在").setCode("404");
            }
            if(exception instanceof FeignException.BadRequest){
                return IResponse.fail("参数错误").setCode("400");
            }
 
            if(exception.content()==null||exception.content().length==0){
                return IResponse.fail("请求失败,请稍后再试");
            }
            if(JSON_START==exception.content()[0]){
                return JSONObject.parseObject(exception.content(),IResponse.class);
            }else{
                return IResponse.fail(exception.contentUTF8());
            }
        }else{
            try {
                if(method.getReturnType().equals(String.class)){
                    return exception.contentUTF8();
                }else if(method.getReturnType().equals(JSONObject.class)){
                    if(JSON_START==exception.content()[0]){
                        return JSONObject.parseObject(exception.content(), JSONObject.class);
                    }
                }else if(!method.getReturnType().equals(Object.class)){
                    return JSONObject.parseObject(exception.content(), method.getReturnType());
                }
                if(JSON_START==exception.content()[0]){
                    JSONObject jsonObject = JSONObject.parseObject(exception.content(), JSONObject.class);
                    if(jsonObject.containsKey("code")&&jsonObject.containsKey("msg")) {
                        return jsonObject.toJavaObject(IResponse.class);
                    }
                }
            }catch (Throwable e){}
            throw new AfsBaseException("接口调用失败");
        }
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        FunFeignFallback<?> that = (FunFeignFallback<?>) o;
        return targetType.equals(that.targetType);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(targetType); 
    } 
}

问题解决:自定义feignFallback异常处理:

1.自定义异常处理 InvoiceApiFeignFallbackFactory

package com.ruicar.afs.cloud.invoice.factory; 
import com.ruicar.afs.cloud.invoice.fallback.InvoiceApiFeignFallback;
import com.ruicar.afs.cloud.invoice.feign.InvoiceApiFeign;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
 
@Component
public class InvoiceApiFeignFallbackFactory implements FallbackFactory<InvoiceApiFeign> {
    @Override
    public InvoiceApiFeign create(Throwable throwable) {
        InvoiceApiFeignFallback invoiceApiFeignFallback = new InvoiceApiFeignFallback();
        invoiceApiFeignFallback.setCause(throwable);
        return invoiceApiFeignFallback;
    }
}

2.feign调用 InvoiceApiFeignFallbackFactory

package com.ruicar.afs.cloud.invoice.feign; 
import com.alibaba.fastjson.JSONObject;
import com.ruicar.afs.cloud.common.core.feign.annotations.AfsFeignClear;
import com.ruicar.afs.cloud.invoice.dto.InvoiceCheckDto;
import com.ruicar.afs.cloud.invoice.factory.InvoiceApiFeignFallbackFactory;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; 
import java.util.Map;
 
/**
 * @description: 发票验证接口
 * @author: rongji.zhang
 * @date: 2020/8/14 10:32
 */
@FeignClient(name = "invoice", url = "${com.greatwall.systems.invoice-system.url}" ,fallbackFactory = InvoiceApiFeignFallbackFactory.class)
public interface InvoiceApiFeign {
    /**
     *
     * @param dto
     * @return
     */
    @ApiOperation("获取业务数据API接口")
    @PostMapping(value = "/vi/check")
    @AfsFeignClear(true)//通过此注解防止添加内部token
    JSONObject InvoiceCheck(@RequestBody InvoiceCheckDto dto, @RequestHeader Map<String, String> headers);
}

3.实现自定义报错处理

package com.ruicar.afs.cloud.invoice.fallback; 
import com.alibaba.fastjson.JSONObject;
import com.ruicar.afs.cloud.invoice.dto.InvoiceCheckDto;
import com.ruicar.afs.cloud.invoice.feign.InvoiceApiFeign;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; 
import java.util.Map;
 
/**
 * @author Fzero
 * @date 2019-01-24
 */
@Slf4j
@Component
public class InvoiceApiFeignFallback implements InvoiceApiFeign { 
    @Setter
    private Throwable cause; 
    /**
     * @param dto
     * @param headers
     * @return
     */
    @Override
    public JSONObject InvoiceCheck(InvoiceCheckDto dto, Map<String, String> headers) {
        log.error("feign 接口调用失败", cause);
        return null;
    }
}

Feign远程调用失败-----丢请求头

@FeignClient("guli-cart")
public interface CartFenignService { 
    @GetMapping("/currentUserCartItems")
    List<OrderItemVo> getCurrentUserCartItems();
}// 这样去掉接口时其实Feign在底层是一个全新的requst所有请求头就没有了

解决办法使用Feign远程掉用拦截器,在远程请求是先创建拦截器

@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
    return new RequestInterceptor() {
        @Override
        public void apply(RequestTemplate template) { 
            /**
             * 把以前的Cookie放到新请求中去   原理就是运用了同一线程数据共享   ThreadLocal
             */
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest(); 
            String cookie = request.getHeader("Cookie"); 
            template.header("Cookie", cookie);
        }
    };
}

但是上面的办法只能解决同意线程问题,在多线程下还是会丢失请求头

多线程下解决办法:

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

把请求单独拿出来给每个线程单独

RequestContextHolder.setRequestAttributes(requestAttributes);

这样就可以了~

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • MyBatis-Plus中自动填充功能的用法示例详解

    MyBatis-Plus中自动填充功能的用法示例详解

    有些时候我们可能会有这样的需求,插入或者更新数据时,希望有些字段可以自动填充数据,比如密码、version、注册时默认的用户角色等,在MP中提供了这样的功能,可以实现自动填充功能,需要的朋友可以参考下
    2022-12-12
  • Java中多线程Reactor模式的实现

    Java中多线程Reactor模式的实现

    多线程Reactor模式旨在分配多个reactor每一个reactor独立拥有一个selector,本文就详细的来介绍一下Java中多线程Reactor模式的实现,需要的朋友可以参考下
    2021-12-12
  • Java 使用Calendar计算时间的示例代码

    Java 使用Calendar计算时间的示例代码

    这篇文章主要介绍了Java 使用Calendar计算时间的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • SpringBoot如何优雅的处理全局异常

    SpringBoot如何优雅的处理全局异常

    这篇文章主要给大家介绍了关于SpringBoot如何优雅的处理全局异常的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用SpringBoot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-05-05
  • Spring与Mybatis基于注解整合Redis的方法

    Spring与Mybatis基于注解整合Redis的方法

    这篇文章主要介绍了Spring与Mybatis基于注解整合Redis的方法,本文通过实例给大家介绍的非常详细,具有参考借鉴价值,需要的朋友可以参考下
    2016-09-09
  • java实现画图板功能

    java实现画图板功能

    这篇文章主要为大家详细介绍了java实现画图板功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-09-09
  • 记一次Feign中实现传实体Bean的问题

    记一次Feign中实现传实体Bean的问题

    这篇文章主要介绍了记一次Feign中如何传实体Bean的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • MyBatis-Plus多表联合查询并且分页(3表联合)

    MyBatis-Plus多表联合查询并且分页(3表联合)

    这篇文章主要介绍了MyBatis-Plus多表联合查询并且分页(3表联合),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • JavaSE的三大接口:Comparator,Comparable和Cloneable详解

    JavaSE的三大接口:Comparator,Comparable和Cloneable详解

    这篇文章主要介绍了详解JavaSE中Comparator,Comparable和Cloneable接口的区别的相关资料,希望通过本文大家能彻底掌握这部分内容,需要的朋友可以参考下
    2021-10-10
  • springboot项目(jar包)指定配置文件启动图文教程

    springboot项目(jar包)指定配置文件启动图文教程

    这篇文章主要给大家介绍了关于springboot项目(jar包)指定配置文件启动的相关资料,在多环境部署过程中、及线上运维中可能会遇到临时指定配置文件的情况,需要的朋友可以参考下
    2023-07-07

最新评论