Spring Cloud Alibaba快速入门03-OpenFeign进阶用法
文章目录
- 前言
- 进阶用法 - 日志
- 进阶用法 - 超时控制
- 进阶用法 - 配置文件
- 进阶用法 - 重试机制
- 进阶用法-拦截器
- 请求拦截器
- 进阶用法 - Fallback
前言
前文介绍了OpenFeign的基本用法,而本文重点内容为OpenFeign的进阶用法,包括日志配置、超时控制、重试机制、拦截器、兜底回调等。
进阶用法 - 日志
配置文件
logging:level:com.qf.feign: debug
配置类
@Configuration
public class BeanConfig {@BeanLogger.Level feignLoggerLevel() {return Logger.Level.FULL;}
}
测试类
@SpringBootTest
public class LoadBalancerTest {@Autowiredprivate TestFeignClient testFeignClient;@Testvoid Test(){String details = testFeignClient.getDetails();System.out.println("details = " + details);}
}
打印openfeign相关日志
进阶用法 - 超时控制
连接超时默认10秒,读取超时默认60秒
通过让商品服务接口休眠(也可以在商品服务中打断点)超过60秒,来查看订单服务的报错信息
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 outat 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
也可以在配置文件中配置
spring:cloud:openfeign:client:config:# 默认配置default:logger-level: fullconnect-timeout: 1000read-timeout: 2000
进阶用法 - 配置文件
单个配置文件中配置太多属性会很乱,因此可以写多个配置文件,在主配置文件中引入
添加application-feign.yml配置文件,专门用于配置openfeign相关属性
spring:cloud:openfeign:client:config:# 默认配置default:logger-level: fullconnect-timeout: 1000read-timeout: 2000# 指定调用商品服务配置qf-service-product:logger-level: fullconnect-timeout: 3000read-timeout: 5000
在主配置文件中加入
spring:profiles:active: prodinclude: feign
openfeign客户端的名称主要可以用属性contextId来配置,不写的话会取value值配置。
@FeignClient(value = "qf-service-product",contextId = "qf-service-product") // feign客户端
public interface ProductFeignClient {...
}
进阶用法 - 重试机制
配置类中加入
@Configuration
public class BeanConfig {...//超时重试@BeanRetryer retryer(){//进入return new Retryer.Default();}
}
↓源码
public static class Default implements Retryer {...public Default() {this(100L, TimeUnit.SECONDS.toMillis(1L), 5);}...
}
参数说明:
当第一次超时后,间隔100毫秒进行第二次请求,如果没有返回则在之前基础上time1.5发送第三次请求,如果没有返回则在time1.5*1.5毫秒发送第四次请求,总共发送五次,最长间隔时间为1秒
商品服务中让接口进行Thread.sleep(10000),在日志中可以看到商品服务接口被调用了5次
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客户端生效,如订单服务)
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;@Component
public class XTokenRequestInterceptor implements RequestInterceptor {/*** 请求拦截器* @param template 请求模板*/@Overridepublic void apply(RequestTemplate template) {System.out.println("XTokenRequestInterceptor ....... ");template.header("X-Token", UUID.randomUUID().toString());}
}
配置文件
spring:cloud:openfeign:client:config:# 默认配置default:logger-level: fullconnect-timeout: 1000read-timeout: 2000# 指定调用商品服务配置qf-service-product:logger-level: fullconnect-timeout: 3000read-timeout: 5000request-interceptors:- com.qf.interceptor.XTokenRequestInterceptor
进阶用法 - Fallback
注意:此功能需要整合 Sentinel 才能实现
如果没有设置兜底返回,如当关闭商品服务会直接报错。
相关代码
兜底回调
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 {@Overridepublic 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;}
}
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依赖,所以发生报错时兜底回调并没有发生。
加入依赖
<!-- 熔断器--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
配置文件开启熔断
feign:sentinel:enabled: true
此时关闭商品服务,调用订单服务接口