前言
Spring Cloud OpenFeign 3.0.4
Spring Cloud 版本
案例说明
8951是消费者,服务消费方,调用方
8952-user 是生产者,服务提供方,被调用方
完整依赖如下
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
dependencies>
作为被调用方需要将自己注册到注册中心,这里以nacos为例。项目结构图如下所示。
bootstrap.yml
spring:
application:
name: openfeign-8952-user
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
server:
port: 8952
UserController 随便写几个测试方法
@RestController
public class UserController {
private final static Logger logger = LoggerFactory.getLogger(UserController.class);
@Value(value = "${server.port}")
private String port;
@GetMapping("user")
public String getUser() {
return "user" + port;
}
@GetMapping("user2")
public String getUser2(Integer id) {
return "this is user2:" + id + port;
}
@PostMapping("user3")
public String getUser3(@RequestBody Integer id) {
return "this is user3:" + id + port;
}
@PostMapping("user4")
public String getUser4(@RequestBody Integer id, HttpServletRequest request) {
logger.info("header {}", request.getHeader("token"));
return "this is user3:" + id + port;
}
@PostMapping("user5")
public String getUser5() {
try {
TimeUnit.SECONDS.sleep(11);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "ok...";
}
}
作为生产者,和任何其他单体应用的区别是把自己注册到注册中心而已。
完整依赖如下
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-loadbalancerartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<scope>providedscope>
dependency>
dependencies>
@FeignClient("openfeign-8952-user")
public interface UserClient {
@GetMapping("user")
String getUser();
@GetMapping("user2")
String getUser2(@RequestParam(value = "id") Integer id);
@PostMapping(value = "user3", headers = "{}")
String getUser3(@RequestBody Integer id);
@PostMapping("user4")
String getUser4(@RequestBody Integer id, @RequestHeader String token);
@PostMapping("user5")
String getUser5();
}
UserController
@RestController
public class UserController {
@Autowired
UserClient userClient;
@GetMapping("user")
public String getUser() {
return userClient.getUser();
}
@GetMapping("user2")
public String getUser2(Integer id) {
return userClient.getUser2(id);
}
@GetMapping("user3")
public String getUser3(Integer id) {
return userClient.getUser3(id);
}
@GetMapping("user4")
public String getUser4(Integer id) {
String token = UUID.randomUUID().toString();
return userClient.getUser4(id, token);
}
@GetMapping("user5")
public String getUser5() {
return userClient.getUser5();
}
}
nacos服务列表如下
访问 http://localhost:8951/user 成功返回 user8952
注意事项
在比较新的版本(如3.0.4)中如果没有添加 spring-cloud-starter-loadbalancer 会报错,如下图所示。
快速启动另一台服务,右键选择复制配置
在程序参数指定另一个端口
--server.port=8953
访问 8951/user
可以看到已经具有了负载均衡的能力。
我们删除 spring-cloud-starter-loadbalancer 依赖根据报错信息找到对应的方法。
从上面截图第三行这个loadBalance方法点进去,源码如下所示
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
//如果 client == null
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
}
可以看到 throw 后面的内容就是报错信息里面的提示。也就是说client == null的时候会抛出这个异常。
那么 Client client = getOptional(context, Client.class); 这一行就是重点。点进去看一下实现。
protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(contextId, type);
}
调用了context.getInstance继续点进去。
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
try {
return context.getBean(type);
}
catch (NoSuchBeanDefinitionException e) {
// ignore
}
return null;
}
这里是从容器AnnotationConfigApplicationContext中获取到bean,那么谁创建的这个bean呢?
一般来说都是由AutoConfiguration默认配置,所以我们找到loadbalancer包下的AutoConfiguration类。
FeignLoadBalancerAutoConfiguration 部分源码如下所示。
@ConditionalOnClass(Feign.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter({ BlockingLoadBalancerClientAutoConfiguration.class, LoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {}
通过import将三个客户端的配置类加入容器,由于我们还未做客户端切换所以找到Default。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(LoadBalancerProperties.class)
class DefaultFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerProperties properties,
LoadBalancerClientFactory loadBalancerClientFactory) {
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, properties,
loadBalancerClientFactory);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
@ConditionalOnBean(LoadBalancedRetryFactory.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
LoadBalancerClientFactory loadBalancerClientFactory) {
return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
loadBalancedRetryFactory, properties, loadBalancerClientFactory);
}
}
所以容器中获取到的Client类型的bean就是FeignBlockingLoadBalancerClient。如何验证?
我们回到loadBalance这个方法打一个断点看一下获取到的client类型就可以了。
删掉依赖后查看 FeignLoadBalancerAutoConfiguration 类情况
在ConditionalOnBean的作用下,这个AutoConf 不会执行就会导致容器中没有对应的 Client 。
到此为止,依赖不添加为什么报错已经说清楚了,那么获取到客户端负载均衡的过程呢?
在上面我们已经获取到了这么一个客户端 FeignBlockingLoadBalancerClient。里面有一个execute方法。
@Override
public Response execute(Request request, Request.Options options) throws IOException {}
猜想:在请求接口的时候会进到这里面。
验证猜想:在 loadBalancerClient.choose(serviceId, lbRequest); 这一行打一个断点观察输出
方法调用栈过程如下图
经过了动态代理的invoke,最终进到上面刚刚获取的FeignBlockingLoadBalancerClient的execute里面。
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
最终通过这一行来筛选出负载均衡后的服务,我们重点看一下里面的实现。
点进去来到 BlockingLoadBalancerClient类中的choose方法。简称为阻塞的负载均衡客户端
@Override
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
这个类是由 BlockingLoadBalancerClientAutoConfiguration 这个类装配而来。
@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
@AutoConfigureBefore({ org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@ConditionalOnClass(RestTemplate.class)
public class BlockingLoadBalancerClientAutoConfiguration {
@Bean
@ConditionalOnBean(LoadBalancerClientFactory.class)
@ConditionalOnMissingBean
public LoadBalancerClient blockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory,
LoadBalancerProperties properties) {
return new BlockingLoadBalancerClient(loadBalancerClientFactory, properties);
}
... }
choose方法主要分为两段,第一段获得 ReactiveLoadBalancer 这么一个类型的变量。
第二段获得 Response 这么一个类型的返回值,里面包含了服务器相关信息。
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
loadBalancer.choose(request)
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {
private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
RoundRobinLoadBalancer 中核心方法
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// TODO: enforce order?
int pos = Math.abs(this.position.incrementAndGet());
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
说明:2020版本之前,超时控制由 ribbon 完成 。
spring-cloud-starter-openfeign 查看 pom 有无 ribbon来选择不同的配置方式。
旧版 ribbon 配置代码如下所示。
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
新版 FeignClientProperties
FeignClientConfiguration 类关键参数如下图
配置文件写入下面这段内容
feign:
client:
config:
openfeign-8952-user:
connectTimeout: 1000
readTimeout: 1000
打一个断点来看是否写成功
调用 user5 方法,可以看到成功相应超时设置的 1s
查看两个客户端的配置文件来比较区别
OkHttpFeignLoadBalancerConfiguration HttpClientFeignLoadBalancerConfiguration
区别
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@ConditionalOnProperty("feign.okhttp.enabled")
这两个类的区别是多了一个 matchIfMissing 那么它有什么用呢 ?
FeignLoadBalancerAutoConfiguration
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
OkHttpFeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
猜想:同时满足条件只会加载 HttpClientFeignLoadBalancerConfiguration
配置文件同时开启enabled,发现先进入 HttpClientFeignLoadBalancerConfiguration,根据 @Import 机制来确定猜想。
java.net 包下面的http链接工具,由于有缺陷例如不支持 patch、连接池等问题最好更换,当然不换也可以用。
添加依赖
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
<version>11.10version>
dependency>
进行配置
feign:
httpclient:
enabled: true
enabled 点进去看到如下配置文件
{
"name": "feign.httpclient.enabled",
"type": "java.lang.Boolean",
"description": "Enables the use of the Apache HTTP Client by Feign.",
"defaultValue": "true"
},
在 HttpClientFeignLoadBalancerConfiguration 中 return new FeignBlockingLoadBalancerClient 这里打一个断点。
添加依赖后成功执行到这里说明匹配成功。
只添加依赖不开启 enable 会走到 DefaultFeignLoadBalancerConfiguration 这里面
添加依赖
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-okhttpartifactId>
<version>11.10version>
dependency>
OkHttpFeignLoadBalancerConfiguration
{
"name": "feign.okhttp.enabled",
"type": "java.lang.Boolean",
"description": "Enables the use of the OK HTTP Client by Feign.",
"defaultValue": "false"
}
在 return new FeignBlockingLoadBalancerClient 这里打一个断点。
添加依赖后成功执行到这里说明匹配成功。
只添加依赖不开启 enable 会走到 DefaultFeignLoadBalancerConfiguration 这里面
HTTP 连接客户端,选 HttpClient 还是 OkHttp ?
总结: OkHttp和HttpClient在性能和使用上不分伯仲,根据实际业务选择即可。
1.10. Feign logging
log level 选择
public enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
}
logging:
level:
com.example.openfeign8951.client.UserClient: debug
新增配置类
@Configuration
public class FeignLogConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
/**
* Controls the level of logging.
*/
public enum Level {
/**
* No logging.
*/
NONE,
/**
* Log only the request method and URL and the response status code and execution time.
*/
BASIC,
/**
* Log the basic information along with request and response headers.
*/
HEADERS,
/**
* Log the headers, body, and metadata for both requests and responses.
*/
FULL
}
FeignAutoConfiguration中的EnableConfigurationProperties让配置文件生效
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class })
FeignClientProperties中的FeignClientConfiguration控制
public static class FeignClientConfiguration {
//日志级别
private Logger.Level loggerLevel;
//连接超时
private Integer connectTimeout;
//读超时
private Integer readTimeout;
//重试
private Class<Retryer> retryer;
//错误码
private Class<ErrorDecoder> errorDecoder;
//过滤器
private List<Class<RequestInterceptor>> requestInterceptors;
//默认请求头
private Map<String, Collection<String>> defaultRequestHeaders;
//默认查询参数
private Map<String, Collection<String>> defaultQueryParameters;
//是否开启404
private Boolean decode404;
//decoder
private Class<Decoder> decoder;
//encoder
private Class<Encoder> encoder;
//Contract
private Class<Contract> contract;
//异常传播策略
private ExceptionPropagationPolicy exceptionPropagationPolicy;
}
在上面超时控制的时候我们配置了 FeignClientProperties 这个类里面包含一个参数decode404
feign:
client:
config:
openfeign-8952-user:
connectTimeout: 1000
readTimeout: 1000
decode404: false
decode404: true
{"timestamp":"2022-11-10T09:33:25.917+00:00","status":404,"error":"Not Found","message":"","path":"/aaa"}
decode404: false
feign.FeignException$NotFound: [404 ] during [GET] to [http://openfeign-8952-user/aaa] [UserClient#aaa()]: [{"timestamp":"***","status":404,"error":"Not Found","message":"","path":"/aaa"}]
不开启会得到一个错误页面,开启会得到一个相对友好的提示。
当我们学习到FeignClient这个注解的时候发现里面也有这样一个字段 boolean decode404() default false;
猜想:注解上的404和配置文件404只要有一个为true则为true。
首先将注解的 decode404 和 配置文件的 decode404 都设为 false,控制台输出如下所示。
查找那里调用了 FeignClient
FeignClientsRegistrar 这个类是由 EnableFeignClients 注解 @Import(FeignClientsRegistrar.class) 导入的
在 getAnnotationAttributes 方法后打一个断点可以看到 Map
我们接下来看一下这个注解获取到的404对象做了什么?
factoryBean对象将注解中的404值拿过来了
接下来的调用过程如图所示
如果是默认的配置文件使用configure进行配置,否则使用配置文件配置。
//configureUsingConfiguration
if (decode404) {
builder.decode404();
}
//configureUsingProperties
if (config.getDecode404() != null) {
if (config.getDecode404()) {
builder.decode404();
}
}
所以我们得出结论,二者有true为true不会覆盖,相当于 或 的关系。
验证猜想
在两个 builder.decode404() 处打断点,然后分别将 注解 和 配置文件 设置为 true,看是否会跑到断点。
1.6. Feign Spring Cloud CircuitBreaker Fallbacks
开启熔断配置
feign:
circuitbreaker:
enabled: true
在注解上指定一个降级处理类
@FeignClient(value = "openfeign-8952-user", fallback = UserClientFallback.class)
首先我们在服务提供方写一个错误的接口,
@GetMapping("bbb")
public void bbb() {
int a = 1 / 0;
}
调用方后台结果如下所示
@Component
static class Fallback implements TestClient {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
模仿给出一个实现
@Component
public class UserClientFallback implements UserClient {
@Override
public String getUser() {
return "这是一个兜底方法....";
}
@Override
public String aaa() {
return "这是一个兜底方法....";
}
@Override
public String bbb() {
return "这是一个兜底方法....";
}
@Override
public String getUser2(Integer id) {
return "这是一个兜底方法....";
}
@Override
public String getUser3(Integer id) {
return "这是一个兜底方法....";
}
@Override
public String getUser4(Integer id, String token) {
return "这是一个兜底方法....";
}
@Override
public String getUser5() {
return "这是一个兜底方法....";
}
}
访问 http://localhost:8951/bbb 发现没有效果。
查看官方猜想缺失部分依赖,引入依赖 resilience4j 或者 netflix-hystrix
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-circuitbreaker-resilience4j -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
经过测试,上面两个依赖添加任意一个都可以实现正确的降级。
2.2.10依赖如下图所示
对比两个依赖项可以得出结论3.0.x版本移出了降级相关依赖。
@EnableFeignClients 的作用是什么?
可以看到这个注解的核心就是 @Import(FeignClientsRegistrar.class) 这一行,那么他有什么作用呢?
定义一个 User 对象
public class User {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
this.name = "default";
}
public User(String name) {
this.name = name;
}
}
主启动类@Import(User.class)
注入对象
@Autowired
User user;
@GetMapping("user6")
public String getUser6() {
return user.getName();
}
发现输出了 default,代表走的无参构造函数。
图解+源码讲解代理对象 ReflectiveFeign 分析
这里可以看到userClient是一个代理对象。
我们注入的接口 UserClient 是一个代理对象,触发了动态代理的invoke方法
FeignClientsRegistrar
FeignClientsRegistrar中有一个registerFeignClient方法调用了 return factoryBean.getObject();
@Override
public Object getObject() {
return getTarget();
}
getTarget方法源码
<T> T getTarget() {
//根据beanFactory是否为null从上下文或者beanFactory中获取FeignContext
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
//配置
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
//从上下文中拿Targeter
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
Targeter 有两个子类
其中DefaultTargeter是FeignAutoConfiguration这两个地方配置的。
当我们FeignAutoConfiguration中将feign.circuitbreaker.enabled设为true的时候,会配置下面两个bean
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CircuitBreaker.class)
@ConditionalOnProperty("feign.circuitbreaker.enabled")
protected static class CircuitBreakerPresentFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean(CircuitBreakerFactory.class)
public Targeter defaultFeignTargeter() {
return new DefaultTargeter();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(CircuitBreakerFactory.class)
public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) {
return new FeignCircuitBreakerTargeter(circuitBreakerFactory);
}
}
Feign类中的newInstance
我们知道代理类执行方法的时候会调用InvocationHandler,所以我们分别看一下两个实现。
FeignCircuitBreakerInvocationHandler
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
//省略部分代码
String circuitName = this.feignClientName + "_" + method.getName();
CircuitBreaker circuitBreaker = this.factory.create(circuitName);
Supplier<Object> supplier = asSupplier(method, args);
if (this.nullableFallbackFactory != null) {
Function<Throwable, Object> fallbackFunction = throwable -> {
Object fallback = this.nullableFallbackFactory.create(throwable);
try {
return this.fallbackMethodMap.get(method).invoke(fallback, args);
}
catch (Exception e) {
throw new IllegalStateException(e);
}
};
return circuitBreaker.run(supplier, fallbackFunction);
}
return circuitBreaker.run(supplier);
}
FeignInvocationHandler
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//省略部分代码
return dispatch.get(method).invoke(args);
}
到这里应该就明白为什么不生效了,当创建defaultFeignTargeter的时候没有对方法进行任何增强处理,自然不具备降级的能力。
Resilience4JAutoConfiguration中部分代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = { "spring.cloud.circuitbreaker.resilience4j.enabled",
"spring.cloud.circuitbreaker.resilience4j.blocking.enabled" }, matchIfMissing = true)
public class Resilience4JAutoConfiguration {
@Autowired(required = false)
private List<Customizer<Resilience4JCircuitBreakerFactory>> customizers = new ArrayList<>();
@Bean
@ConditionalOnMissingBean(CircuitBreakerFactory.class)
public Resilience4JCircuitBreakerFactory resilience4jCircuitBreakerFactory() {
Resilience4JCircuitBreakerFactory factory = new Resilience4JCircuitBreakerFactory();
customizers.forEach(customizer -> customizer.customize(factory));
return factory;
}
}
参考1
参考2
参考链接
深入Feign源码吃透Spring扩展点「扩展点实战系列」- 第446篇