Spring Cloud Alibaba快速入门03-OpenFeign进阶用法

文章目录

  • 前言
  • [进阶用法 - 日志](#进阶用法 - 日志)
  • [进阶用法 - 超时控制](#进阶用法 - 超时控制)
  • [进阶用法 - 配置文件](#进阶用法 - 配置文件)
  • [进阶用法 - 重试机制](#进阶用法 - 重试机制)
  • 进阶用法-拦截器
  • [进阶用法 - Fallback](#进阶用法 - Fallback)

前言

前文介绍了OpenFeign的基本用法,而本文重点内容为OpenFeign的进阶用法,包括日志配置、超时控制、重试机制、拦截器、兜底回调等。

进阶用法 - 日志

配置文件

java 复制代码
logging:
  level:
    com.qf.feign: debug

配置类

java 复制代码
@Configuration
public class BeanConfig {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

测试类

java 复制代码
@SpringBootTest
public class LoadBalancerTest {
    @Autowired
    private TestFeignClient testFeignClient;

    @Test
    void Test(){
        String details = testFeignClient.getDetails();
        System.out.println("details = " + details);
    }
}

打印openfeign相关日志

进阶用法 - 超时控制

连接超时默认10秒,读取超时默认60秒

通过让商品服务接口休眠(也可以在商品服务中打断点)超过60秒,来查看订单服务的报错信息

js 复制代码
2025-09-11T22:57:59.629+08:00 DEBUG 38868 --- [qf-service-order] [nio-8080-exec-3] com.qf.feign.ProductFeignClient          : [ProductFeignClient#getProductById] ---> GET http://qf-service-product/product/11 HTTP/1.1
2025-09-11T22:57:59.629+08:00 DEBUG 38868 --- [qf-service-order] [nio-8080-exec-3] com.qf.feign.ProductFeignClient          : [ProductFeignClient#getProductById] ---> END HTTP (0-byte body)
2025-09-11T22:58:59.650+08:00 DEBUG 38868 --- [qf-service-order] [nio-8080-exec-3] com.qf.feign.ProductFeignClient          : [ProductFeignClient#getProductById] <--- ERROR SocketTimeoutException: Read timed out (60020ms)
2025-09-11T22:58:59.650+08:00 DEBUG 38868 --- [qf-service-order] [nio-8080-exec-3] com.qf.feign.ProductFeignClient          : [ProductFeignClient#getProductById] java.net.SocketTimeoutException: Read timed out
	at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283)
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966)
	at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:244)
	at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
	at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:343)
	at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:827)
	at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:762)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1688)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
	at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:529)
	at feign.Client$Default.convertResponse(Client.java:111)
	at feign.Client$Default.execute(Client.java:107)
	at org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(LoadBalancerUtils.java:56)
	at org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(LoadBalancerUtils.java:91)
	at org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient.execute(FeignBlockingLoadBalancerClient.java:134)
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:100)
	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:70)
	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:99)
	at jdk.proxy2/jdk.proxy2.$Proxy71.getProductById(Unknown Source)
	at com.qf.service.impl.OrderServiceImpl.createOrder(OrderServiceImpl.java:31)
	at com.qf.controller.OrderController.createOrder(OrderController.java:23)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:281)
	at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:482)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:768)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:720)
	at com.qf.controller.OrderController$$SpringCGLIB$$0.createOrder(<generated>)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:384)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
	at java.base/java.lang.Thread.run(Thread.java:833)

以上日志中可以看到feign调用60秒后报错java.net.SocketTimeoutException: Read timed out

也可以在配置文件中配置

yml 复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          # 默认配置
          default:
            logger-level: full
            connect-timeout: 1000
            read-timeout: 2000

进阶用法 - 配置文件

单个配置文件中配置太多属性会很乱,因此可以写多个配置文件,在主配置文件中引入

添加application-feign.yml配置文件,专门用于配置openfeign相关属性

yml 复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          # 默认配置
          default:
            logger-level: full
            connect-timeout: 1000
            read-timeout: 2000
          # 指定调用商品服务配置
          qf-service-product:
            logger-level: full
            connect-timeout: 3000
            read-timeout: 5000

在主配置文件中加入

yml 复制代码
spring:
  profiles:
    active: prod
    include: feign

openfeign客户端的名称主要可以用属性contextId来配置,不写的话会取value值配置。

java 复制代码
@FeignClient(value = "qf-service-product",contextId = "qf-service-product") // feign客户端
public interface ProductFeignClient {
    ...
}

进阶用法 - 重试机制

配置类中加入

java 复制代码
@Configuration
public class BeanConfig {
    ...
    //超时重试
    @Bean
    Retryer retryer(){
        //进入
        return new Retryer.Default();
    }
}

↓源码

java 复制代码
public static class Default implements Retryer {
    ...
    public Default() {
        this(100L, TimeUnit.SECONDS.toMillis(1L), 5);
   }
   ...
}

参数说明:

当第一次超时后,间隔100毫秒进行第二次请求,如果没有返回则在之前基础上time1.5发送第三次请求,如果没有返回则在time 1.5*1.5毫秒发送第四次请求,总共发送五次,最长间隔时间为1秒

商品服务中让接口进行Thread.sleep(10000),在日志中可以看到商品服务接口被调用了5次

js 复制代码
2025-02-23 18:36:10 id:22
2025-02-23 18:36:16 id:22
2025-02-23 18:36:21 id:22
2025-02-23 18:36:26 id:22
2025-02-23 18:36:32 id:22

进阶用法-拦截器

请求拦截器

在配置文件中设置拦截器(对指定feign客户端生效,如订单服务)

java 复制代码
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;

@Component
public class XTokenRequestInterceptor implements RequestInterceptor {

    /**
     * 请求拦截器
     * @param template 请求模板
     */
    @Override
    public void apply(RequestTemplate template) {
        System.out.println("XTokenRequestInterceptor ....... ");
        template.header("X-Token", UUID.randomUUID().toString());
    }
}

配置文件

yml 复制代码
spring:
  cloud:
    openfeign:
      client:
        config:
          # 默认配置
          default:
            logger-level: full
            connect-timeout: 1000
            read-timeout: 2000
          # 指定调用商品服务配置
          qf-service-product:
            logger-level: full
            connect-timeout: 3000
            read-timeout: 5000
            request-interceptors:
              - com.qf.interceptor.XTokenRequestInterceptor

进阶用法 - Fallback

注意:此功能需要整合 Sentinel 才能实现

如果没有设置兜底返回,如当关闭商品服务会直接报错。

相关代码

兜底回调

java 复制代码
import com.qf.feign.ProductFeignClient;
import com.qf.entity.Product;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;

@Component
public class ProductFeignClientFallback implements ProductFeignClient {
    @Override
    public Product getProductById(Long id) {
        System.out.println("兜底回调....");
        Product product = new Product();
        product.setId(id);
        product.setPrice(new BigDecimal("0"));
        product.setProductName("未知商品");
        product.setNum(0);
        return product;
    }
}
java 复制代码
import com.qf.entity.Product;
import com.qf.feign.fallback.ProductFeignClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

//填写需要远程调用的服务
@FeignClient(value = "qf-service-product", fallback = ProductFeignClientFallback.class) // feign客户端
public interface ProductFeignClient {
    //mvc注解的两套使用逻辑
    //1、标注在Controller上,是接受这样的请求
    //2、标注在FeignClient上,是发送这样的请求
    @GetMapping("/product/{id}")
    Product getProductById(@PathVariable("id") Long id);
}

此时因为没有加入sentinel依赖,所以发生报错时兜底回调并没有发生。

加入依赖

xml 复制代码
<!--        熔断器-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

配置文件开启熔断

yml 复制代码
feign:
  sentinel:
    enabled: true

此时关闭商品服务,调用订单服务接口


相关推荐
程序员清风2 小时前
快手二面:Redisson公平锁用用过吗?他的实现原理是什么样子的?
java·后端·面试
SimonKing2 小时前
Java序列化陷阱揭秘:这5个错误80%的开发者都犯过
java·后端·程序员
深圳蔓延科技2 小时前
Kafka + Spring Boot 终极整合指南
后端·kafka
风象南2 小时前
SpringBoot 方法级耗时监控器
后端
Sui_Network2 小时前
GraphQL RPC 与通用索引器公测介绍:为 Sui 带来更强大的数据层
javascript·人工智能·后端·rpc·去中心化·区块链·graphql
BingoGo3 小时前
PHP serialize 序列化完全指南
后端·php
枫叶V3 小时前
Go 实现大文件分片上传与断点续传
后端·go
Cache技术分享3 小时前
186. Java 模式匹配 - Java 21 新特性:Record Pattern(记录模式匹配)
前端·javascript·后端