OpenFegin+hystrix实现远程HTTP服务调用、服务降级(深度解析~保姆级)

简介

OpenFeign 是 Spring Cloud 家族的一个成员, 它最核心的作用是为 HTTP 形式的 Rest API 提供了非常简洁高效的 RPC 调用方式。支持Hystrix 、Ribbon和SpringMVC 注解。

Feign和OpenFeign的区别?

1、Feign:

Feign是Netflix公司(第一代SpringCloud)研发的一个轻量级RESTful的伪HTTP服务客户端。

Feign内置了Ribbon逻辑,通过负载均衡算法可以从注册中心中寻找服务。

2、OpenFeign:

OpenFeign是SpringCloud自己研发的,在Feign的基础上做了增强。

OpenFeign除了原有Ribbon逻辑外,还支持了Hystrix和Spring MVC注解。

一、服务端Provider

注意: 当前是springboot整合OpenFegin 服务端没有配置注册中心, 如分布式 微服务请自行配置

  1. 服务端Controller层

@CrossOrigin
@RestController
@RequestMapping("/customer/information")
public class CrmCustomersController {
   
    @Autowired
    private CrmCustomersService crmCustomersService;

    /**
     * 服务数据
     */
    @RequestMapping(value = "customerlist", method = {RequestMethod.GET,RequestMethod.POST})
    public PmpResult  queryCustomerList (@RequestParam(value="pageNum",defaultValue="1") int pageNum, @RequestParam(value="pageSize",defaultValue="10") int pageSize,String customersno, String customersname,String daogouid){
        try {

            PageInfo<CustomerInformationVo> pageInfo = crmCustomersService.queryCustomerList(pageNum,pageSize,customersno,customersname,daogouid);

            logger.info("查询成功");
            return PmpResult.success(pageInfo);
        }catch (Exception e) {
            logger.error(e.getMessage(), e);
            String errorMessage = "查询异常";
            if (isDev){
                errorMessage = e.getMessage();
            }
            return PmpResult.paramError(errorMessage);
        }

    }

 }

二、消费端Consumer

  1. 消费端~例图

  1. Pom.xml

  1. <!-- openFeing --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>

  1. Api

  1. @FeignClient注解

@FeignClient参数:

name: 指定FeignClient的(服务)名称,在Eureka/Nacos/其他注册中心 中对应的服务名称。

url: 指定远程调用地址。

configuration: 指定配置类,可以自定义FeignClient的配置。

fallback: 指定降级类,在服务调用失败时返回降级类中的内容。

fallbackFactory: 指定降级工厂类,在服务调用失败时返回降级工厂类中的内容。

decode404: 指定404响应是否解码,默认为true。

path: 指定服务请求的基础路径。

contextId: 当一个服务有多个接口时,我们又不想把所有接口都写到一个类中是时候就用到了 contextId为当前类设置一个唯一ID。不然就回报如下错误 。

  1. [扩展]@FeignClient注解 通过属性 contextId 解决名称重复无法启动的问题 错误如下:

Description:
The bean 'optimization-user.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

导致 名称重复问题 复现:

/*
*Consumer1
*/
@FeignClient(name = "xj-user")
public interface UserRemoteConsumer1 {
    @GetMapping("/user1/get")
    public User getUser(@RequestParam("id") int id);
}

/*
*Consumer2
*/
@FeignClient(name = "xj-user")
public interface UserRemoteConsumer2 {
    @GetMapping("/user2/get")
    public User getUser(@RequestParam("id") int id);
}

这种情况下启动就会报错了,因Bean的名称冲突。

原因:由于name重复,而又不允许BeanDefinition重复,所以导致在进行注册时报错。

解决方案一:

增加下面的配置,作用允许出现beanName一样的BeanDefinition。

 spring.main.allow-bean-definition-overriding=true

解释:

spring.main.allow-bean-definition-overriding=true应用程序的一个配置属性,它允许在应用程序上下文中覆盖bean定义。如果设置为true,则可以在应用程序上下文中定义多个具有相同名称的bean,后定义的bean将覆盖先前定义的bean。但是,这可能会导致不可预测的行为和错误,因此应该谨慎使用。

解决方案二(推荐):

每个FeignClient手动指定不同的contextId,这样就不会冲突了。

@FeignClient添加contextId属性

/*
*Consumer1
*/
@FeignClient(name = "xj-user" contextId = UserRemoteConsumer1)
public interface UserRemoteConsumer1 {
    @GetMapping("/user1/get")
    public User getUser(@RequestParam("id") int id);
}

/*
*Consumer2
*/
@FeignClient(name = "xj-user" contextId = UserRemoteConsumer2)
public interface UserRemoteConsumer2 {
    @GetMapping("/user2/get")
    public User getUser(@RequestParam("id") int id);
}

总结:想创建多个具有相同名称或url的外部客户端,以便它们指向同一台服务器,但每个服务器都有不同的自定义配置,那么可以使用@FeignClient的contextId属性,以避免这些配置bean的名称冲突。

  1. @RequestMapping(value = "/customer/information/customerlist", method = RequestMethod.GET)是要调的接口名

openFegin调用如下:

import com.alibaba.fastjson.JSONObject;
import com.lt.crm.service.fallback.TransactionFallBackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Repository;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;


@Repository
@FeignClient(name = "service-client", url = "http://10.1.8.22:9001", contextId = "ConsumerService", fallbackFactory = TransactionFallBackFactory.class)
public interface ConsumerService {

    @RequestMapping(value = "/customer/information/customerlist", method = RequestMethod.GET)
    JSONObject queryCustomerList(@RequestParam(value = "pageNum") final int pageNum, @RequestParam(value = "pageSize") final int pageSize);


}

  1. Hystrix 服务降级处理

说明:

Hystrix配合OpenFeign进行降级,可以对应接口中定义的远程调用单独进行降级操作。

直白的说: 远程调用失败,我们添加一个替代方案,我们知道OpenFegin是以接口的形式来声明远程调用,只要是远程调用失效超时,就执行替代方案。创建一个实现类,对原有的接口方法进行替代方案实现。

  1. 使用@Component注解 交给String管理 注入IOC容器

  1. 对Api接口进行进行实现、重写调用的Api 远程接口 代码如下:

  1. @Component 注解是 Spring 框架中的注解,用于将一个类标记为 Spring 容器中的一个组件,让 Spring 自动扫描并管理这个组件的生命周期和依赖注入。

  1. 实现implements FallbackFactory原因:用于在使用 Feign 进行服务调用时,定义一个 FallbackFactory<> 来处理服务调用失败的情况的<ConsumerService>。[ import feign.hystrix.FallbackFactory; ]

  1. 以下码为例:服务降级内容我只是返回一段话,根据自己业务定性, 可判断参数是否为空等 返回具体错误信息。

import com.alibaba.fastjson.JSONObject;
import com.lt.crm.service.api.ConsumerService;
import feign.hystrix.FallbackFactory;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;


@Component
public class TransactionFallBackFactory implements FallbackFactory<ConsumerService>  {

    private Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    public ConsumerService create(Throwable throwable) {

        return new ConsumerService() {
            @Override
            public JSONObject queryCustomerList(int pageNum, int pageSize) {
                logger.error("TransactionMsgWaitingConfirm Error : {}" , ExceptionUtils.getFullStackTrace(throwable));
                String  fallbackMessage = "被Hystrix熔断,已作降级处理!";
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("fallbackMessage",fallbackMessage);
                return jsonObject;
            }
        };
    }



}

  1. 消费端Controller层

@Autowired

private ConsumerService consumerService;

在消费端Controller注入Api调用 ConsumerService


import com.alibaba.fastjson.JSONObject;
import com.lt.crm.common.PmpResult;
import com.lt.crm.service.api.ConsumerService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;


@Controller
@RequestMapping("/customer/information")
@Api(tags = "跨服务调用数据")
public class ConsumerController {
    /**
     * 日志
     */
    private final static Logger logger = (Logger) LoggerFactory.getLogger(ConsumerController.class);

    @Autowired
    private ConsumerService consumerService;

    @ResponseBody
    @ApiOperation(value = "公共开放的端口")
    @RequestMapping(value = "customerlist", method = RequestMethod.GET)
    public PmpResult queryCustomerList(@RequestParam(value = "pageNum", defaultValue = "1") int pageNum, @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) {

        try {
             // String类型 转换成json 接收
            JSONObject GetJsonObject = consumerService.queryCustomerList(pageNum, pageSize);   
            if (null != GetJsonObject) {
                logger.info("调用数据成功");
                return PmpResult.success("数据服务", GetJsonObject);
            }

        } catch (Exception e) {
            e.printStackTrace();
            return PmpResult.serviceError("服务异常");
        }
        return null;
    }


}

  1. application.properties配置超时时间

说明: OpenFegin 默认1ms 未响应会抛出异常 , 往往在调用远程接口的时候 、业务逻辑复杂、 没等业务处理完毕就抛出异常 、我们可以针对这类问题 可以配置延长时长。

  1. 配置 Feign 超时问题

  1. 配置hystrix 超时时间

######################################openFegin######################################
# 默认开启
feign.httpclient.enabled=true
# 默认关闭
feign.okhttp.enabled=false
# 默认关闭
feign.hystrix.enabled=true
# 默认关闭
feign.sentinel.enabled=false

# default context 连接超时时间 5000ms = 5秒
feign.client.config.default.connectTimeout = 5000
# default context 读超时时间
feign.client.config.default.readTimeout = 5000

######################################Hystrix Config######################################
#开启hystrix超时管理
hystrix.command.default.execution.timeout.enabled=true
#hystrix超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000

#开启ribbon超时管理
ribbon.http.client.enabled=false
#请求超时时间
ribbon.ReadTimeout=20000
#连接超时时间
ribbon.ConnectTimeout=20000
ribbon.MaxAutoRetries=0
ribbon.MaxAutoRetriesNextServer=1
ribbon.OkToRetryOnAllOperations=false

  1. 启动类:

启动类上使用注解 @EnableFeignClients 开启 openFeign 功能。

/**
 * 微服务应用服务启动类
 * 1、(@EnableDiscoveryClient)注解为链接微服务注册中心用,如实际环境中使用注册中心,请取消注释部分,
 *     与配置文件中相关注册中心配置信息结合使用。
 * @author xj
 *
 */
@EnableDiscoveryClient
@EnableFeignClients(clients= {ConsumerService.class} )//加入@EnableFeignClients注解 开启Feign远程服务调用,使Feign的bean可以被注入
@ServletComponentScan
@SpringBootApplication(scanBasePackages = {"com.lt.crm"})
@EnableCaching
public class OpenFeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(OpenFeignApplication.class, args);}
}

  1. 测试 熔断降级

启动消费端Consumer,并没有启动服务端Provider ,过了超时时间,此时执行了服务降级,这样做避免了在微服务中服务雪崩的灾难。

后台也捕获了error :

连接被拒绝:连接执行GET: Connection refused: connect executing GET http://10.1.8.22:9001/customer/information/customerlist?pageNum=1&pageSize=10

2023-03-17 16:56:52.399 ERROR 20928 --- [ervice-client-1] c.l.c.s.f.TransactionFallBackFactory     : TransactionMsgWaitingConfirm Error : feign.RetryableException: Connection refused: connect executing GET http://10.1.8.22:9001/customer/information/customerlist?pageNum=1&pageSize=10
    at feign.FeignException.errorExecuting(FeignException.java:84)
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:113)
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78)
    at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:106)
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
    at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
    at rx.Observable.unsafeSubscribe(Observable.java:10327)
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51)
    at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)
    at rx.Observable.unsafeSubscribe(Observable.java:10327)
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41)
    at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
    at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
    at rx.Observable.unsafeSubscribe(Observable.java:10327)
    at rx.internal.operators.OperatorSubscribeOn$SubscribeOnSubscriber.call(OperatorSubscribeOn.java:100)
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56)
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47)
    at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69)
    at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

现在启动服务端再次调用,已经通过消费者9004端口 拿到了服务端9001提供的数据。

三、openFeign传参方式

简介:

在接口当中传参的方式有很多,但是在 openFeig中的 传参有一定规则的,详细如下。

  1. 传递JSON数据

服务端Provider接口中JSON传参方法如下:

@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
    @PostMapping("/order1")
    public Order createOrder1(@RequestBody Order order){
        return order;
    }
}

注意:在Spring Boot 中通过@RequestBody标识入参。

消费端Consumer在openFeign接口传参如下:

@FeignClient(value = "openFeign-provider")//服务名
public interface OpenFeignService {
    /**
     * 参数默认是@RequestBody标注的,这里的@RequestBody可以不填
     * 方法名称任意
     */
    @RequestMapping("/openfeign/provider/order1", method = RequestMethod.GET)
    Order createOrder1(@RequestBody Order Order);
}

注意: 我用的是@RequestMapping注解 这是一个组合注解,里面包含@RequestBody 可以不填。

  1. POJO表单传参

服务端Provider接口传参方法如下:

@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
    @PostMapping("/order2")
    public Order createOrder2(Order order){
        return order;
    }
}

注意: 参数使用POJO对象接收。

消费端Consumer在openFeign接口传参如下:

@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
    /**
     * 参数默认是@RequestBody标注的,如果通过POJO表单传参的,使用@SpringQueryMap标注
     */
    @RequestMapping("/customer/information/order2", method = RequestMethod.GET)
    Order createOrder2(@SpringQueryMap Order order);
}

注意: openFeign提供了一个注解@SpringQueryMap解决POJO表单传参,这也是官方文档明确给出了解决方案。

  1. URL中携带参数

服务端Provider接口传参方法如下:

@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
 
    @GetMapping("/test/{id}")
    public String test(@PathVariable("id")Integer id){
        return "accept one msg id="+id;
}

注意: 此种方式针对restful方式中的GET请求方式。

消费端Consumer在openFeign接口传参如下:

@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
 
    @GetMapping("/openfeign/provider/test/{id}")
    String get(@PathVariable("id")Integer id);
}

注意: 使用注解@PathVariable接收url中的占位符。

四、开启日志增强

说明: openFeign 虽提供了日志增强功能,但默认是不显示任何日志的,开发者在调试阶段可以自己配置日志的级别。

级别如下:

  • NONE:默认的,不显示任何日志;

  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;

  • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;

  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

  1. 配置日志级别

自定义一个配置类,在其中设置日志级别如下:


import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenFeginConfig {
    /**
     * 日志级别定义
     * @return
     */
    @Bean
    Logger.Level OpenFeignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

注意细节:Logger导包是feign包。

  1. YAML配置

logging:
  level:
    com.lt.crm.service.api: debug

注意此处 com.lt.crm.service.api 是 openFeign 接口所在的包名,可以根据自己项目路径配置一个特定的openFeign接口。

  1. 注解@FeignClient中配置 configuration

说明:@FeignClient配置里 configuration = OpenFeginConfig.class

@Repository
@FeignClient(name = "service-client", url = "http://10.1.8.22:9001", contextId = "ConsumerService", fallbackFactory = TransactionFallBackFactory.class, configuration = OpenFeginConfig.class)
public interface ConsumerService {

    @RequestMapping(value = "/customer/information/customerlist", method = RequestMethod.GET)
    JSONObject queryCustomerList(@RequestParam(value = "pageNum") final int pageNum, @RequestParam(value = "pageSize") final int pageSize);


}

  1. 查看打印日志示意图

五、Gzip压缩数据(全局压缩~接口与浏览器响应压缩)

  1. 全局压缩相关配置

######################################接口与浏览器响应压缩######################################
#全局压缩(接口与浏览器响应压缩)
server:
  compression:
    #开启Gzip压缩
    enabled: true
    #配置min-response-size 压缩数据大小的最小阈值,默认2048
    min-response-size: 1 #设置1kb以上开始压缩
    #配置mime-types 压缩支持的MIME  TYPE
    mime-types:
    - image/png
    - image/jpeg
    - image/jpg
    - text/html
    - text/xml
    - application/xml
    - application/json

min-response-sizemime-types 含默认值,可以不手动指定,如有其他需要可按需指定

  1. 测试压缩

未开启压缩前 请求数据大小为526KB

开启压缩 请求数据大小为82KB

点击查看请求头信息

  1. Gzip压缩算法

Gzip是一种数据格式,采用deflate算法压缩数据;当GZIP算法压缩到一个纯文本数据时,效果会非常显著。

数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页加载的速度。网页加载速度加快,除了节省流量,改善用户的浏览体验外。

Gzip压缩传输原理图如下:

1、客户端向服务器请求头中带有:Accept-Encoding:gzip,deflate 字段,向服务器表示,客户端支持的压缩格式(gzip或者deflate),如果不发送该消息头,服务器是不会压缩的。

2、服务端在收到请求之后,如果发现请求头中含有 Accept-Encoding 字段,并且支持该类型的压缩,就对响应报文压缩之后返回给客户端,并且携带 Content-Encoding:gzip 消息头,表示响应报文是根据该格式压缩过的。

3、客户端接收到响应之后,先判断是否有 Content-Encoding 消息头,如果有,按该格式解压报文。否则按正常报文处理。

六、OpenFeign开启Gzip压缩数据(局部压缩~微服务之间利用Feign请求及响应压缩)

  1. 全局压缩相关配置

######################################openFegin Gzip######################################
#局部压缩(微服务之间利用feign请求及响应压缩)
feign:
  compression:
    request:
      enabled: true    # 开启压缩
      min-request-size: 1  # 开启压缩的阈值,单位字节,默认2048,即是2k,此处为演示效果设置成1字节
      mime-types: text/xml,application/xml,application/json
    response:
      enabled: true #响应压缩

注意:openFeign支持的Gzip仅仅是在openFeign接口的请求和响应,即openFeign消费者调用服务提供者的接口。

  1. 开启Gzip压缩后日志信息

查看Gzip压缩开启后的打印日志

日志中包含 content-encoding: gzip 表示压缩成功

  1. 开启Gzip压缩后日志信息

查看Gzip压缩关闭后的打印日志

日志中未出现 content-encoding: gzip 表示未被压缩 并且可以清楚看到时间为 1231ms

七、如何OpenFegin 替换默认的httpclient ?

前言:

1、OpenFeign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection。
2、默认的http客户端是 javax.net.ssl.HttpsURLConnection,详细信息查看feign-core:feign.Client,该http客户端不支持添加拦截器和连接池。所以我们需要添加第三方http客户端。

在生产环境中,通常也不会使用默认的httpclient,两种选择如下:

  • 使用ApacheHttpClient

  • 使用OkHttp

声明: 我使用的是 OkHttp 如果使用 ApacheHttpClient 思路是基本一致 。

  1. pom.xml依赖如下

        <!-- 使用 Apache HttpClient 替换 Feign原生httpclient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <!-- 使用 Okhttp 替换 Feign原生httpclient-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
            <version>10.1.0</version>
        </dependency>

  1. application.properties 启用 OkHttp 配置

# 默认开启
feign.httpclient.enabled=false
# 默认关闭
feign.okhttp.enabled=true

关闭默认的httpclient 开启okhttp

  1. 配置OkHttp响应拦截器

前言:

微服务之间使用OpenFeign,由于业务得需求有时候我们需要在请求或者响应的时候做一些额外的操作。比如请求的时候添加请求头,响应时候判断token是否过期等等。这时候拦截器就派上用场了!

import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;

@Slf4j
@Configuration
public class FeignOkHttpClientConfig {


    @Bean
    public OkHttpClient.Builder okHttpClientBuilder() {
        return new OkHttpClient.Builder().addInterceptor(new FeignOkHttpClientResponseInterceptor());
    }


    /**
     * okHttp响应拦截器
     */
    public static class FeignOkHttpClientResponseInterceptor implements Interceptor {


        @Override
        public Response intercept(Chain chain) throws IOException {

            Request originalRequest = chain.request();
            Response response = chain.proceed(originalRequest);

            MediaType mediaType = response.body().contentType();
            String content = response.body().string();           
            //解析content,做你想做的事情!!!
            System.out.print("拦截器"+content+"往哪跑?");

            //生成新的response返回,网络请求的response如果取出之后,直接返回将会抛出异常
            return response.newBuilder()
                    .body(ResponseBody.create(mediaType, content))
                    .build();
        }
    }



}

  1. Feign使用okhttpclient时报错:java.lang.IllegalStateException: original request is required

问题复现:

    2023-03-27 16:43:23.754 ERROR 21120 --- [ervice-client-1] c.l.c.s.f.TransactionFallBackFactory     : TransactionMsgWaitingConfirm Error : java.lang.IllegalStateException: original request is required
    at feign.Util.checkState(Util.java:127)
    at feign.Response.<init>(Response.java:48)
    at feign.Response.<init>(Response.java:38)
    at feign.Response$Builder.build(Response.java:133)
    at feign.okhttp.OkHttpClient.toFeignResponse(OkHttpClient.java:99)
    at feign.okhttp.OkHttpClient.execute(OkHttpClient.java:161)

具体描述:

可以看到状态200 请求成功 ,并且我设置的超时是5秒,而我查看后台日志请求只用了130ms说明没有超时,为什么一直走熔断? 为什么会一直报: java.lang.IllegalStateException: original request is required

解决方案:

最初我用的okhttp 版本是9.7.0 导致,feign-core 的版本和 feign-okhttp版本不一致问题引起,或者可以理解版本为不兼容导致,将 feign-okhttp版本换成 10.1.0 成方可解决。

  • 26
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你才是臭弟弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值