参考
Spring Cloud 版本 <spring-cloud.version>2020.0.1</spring-cloud.version>
案例说明
8951是消费者,服务消费方,调用方
8952-user 是生产者,服务提供方,被调用方
服务提供者,被调用方 8952-user
完整依赖如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</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...";
}
}
作为生产者,和任何其他单体应用的区别是把自己注册到注册中心而已。
服务消费者,调用方 8951
完整依赖如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
完整结构图
UserClient
@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
可以看到已经具有了负载均衡的能力。
loadbalancer 依赖与启动分析
我们删除 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);
}
Timeout Handling (超时处理)
说明: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
更换http客户端
快速上手
使用 httpclient
第一步:添加依赖
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-httpclient -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>11.10</version>
</dependency>
第二步验证:是否生效
使用 okhttp
第一步:添加依赖
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-okhttp -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>11.10</version>
</dependency>
第二步:配置文件修改
feign:
okhttp:
enabled: true
httpclient:
enabled: false
第三步验证:是否生效
Tips:如果同时有两种依赖,记得将 httpclient enabled 设置为 false,否则会先加载。
如何看使用的什么客户端
在 FeignBlockingLoadBalancerClient 构造函数这个地方打一个断点观察 delegate 参数
性能比较
HTTP 连接客户端,选 HttpClient 还是 OkHttp ?
总结: OkHttp和HttpClient在性能和使用上不分伯仲,根据实际业务选择即可。
日志
log level 选择
public enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
}
logging:
level:
com.example.openfeign8951.client.UserClient: debug
新增配置类
@Configuration
public class FeignLogConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
Level主要包含以下级别:NONE,BASIC,HEADERS,FULL。
重试
FeignClient配置
FeignAutoConfiguration中的EnableConfigurationProperties让配置文件生效
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class })
FeignClientProperties
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;
}
decode404
在上面超时控制的时候我们配置了 FeignClientProperties 这个类里面包含一个参数decode404
我们来看一下true和false的区别。
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;
探究 FeignClient中decode404和FeignClientConfiguration中decode404的关联
猜想:注解上的404和配置文件404只要有一个为true则为true。
首先将注解的 decode404 和 配置文件的 decode404 都设为 false,控制台输出如下所示。
查找那里调用了 FeignClient
FeignClientsRegistrar 这个类是由 EnableFeignClients 注解 @Import(FeignClientsRegistrar.class) 导入的
在 getAnnotationAttributes 方法后打一个断点可以看到 Map<String, Object> attributes 存储了注解上定义的相关信息。
我们接下来看一下这个注解获取到的404对象做了什么?
factoryBean对象将注解中的404值拿过来了
接下来的调用过程如图所示
如果是默认的配置文件使用configure进行配置,否则使用配置文件配置。
//configureUsingConfiguration
if (decode404) {
builder.decode404();
}
//configureUsingProperties
if (config.getDecode404() != null) {
if (config.getDecode404()) {
builder.decode404();
}
}
所以我们得出结论,二者有true为true不会覆盖,相当于 或 的关系。
验证猜想
在两个 builder.decode404() 处打断点,然后分别将 注解 和 配置文件 设置为 true,看是否会跑到断点。
注解为true
配置文件为true
FeignHttpClientProperties
降级 (fallback)
1.5. Feign Spring Cloud CircuitBreaker Support
准备工作
1:添加依赖
<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>
两个依赖添加任何一个都可以,openfeign在高版本移出了默认的hystrix。
在openfeign低版本中会默认添加feign-hystrix,2.2.10依赖如下图所示
3.0.0版本如下图所示
快速上手:fallback
1.6. Feign Spring Cloud CircuitBreaker Fallbacks
1:开启熔断配置
feign:
circuitbreaker:
enabled: true
2:在被调用方开发一个错误接口
@GetMapping("bbb")
public void bbb() {
int a = 1 / 0;
}
3:参照官方文档实现接口
新建一个UserClient 实现类并被Spring容器接管。
@Component
public class UserClientFallback implements UserClient {
public static final String DEFAULT = "default:";
@Override
public String bbb() {
return DEFAULT;
}
}
4:在注解上指定UserClientFallback类
@FeignClient(value = "openfeign-8952-user", fallback = UserClientFallback.class)
快速上手:fallbackFactory
官方示例,基本关系如图所示
If one needs access to the cause that made the fallback trigger, one can use the fallbackFactory attribute inside @FeignClient. 如果需要访问产生回退触发器的原因,你可以使用fallbackFactory这个属性。
T create(Throwable cause);
创建工厂类UserClientFactory ,一般统一处理业务异常和超时异常。
@Component
public class UserClientFactory implements FallbackFactory<UserClient> {
private static final Logger logger = LoggerFactory.getLogger(UserClientFactory.class);
@Override
public UserClient create(Throwable cause) {
throw handleThrowable(cause);
}
/**
* 处理feign中异常
*
* @param cause
* @return
*/
public RuntimeException handleThrowable(Throwable cause) {
//TODO自定义异常解析
if (cause instanceof TimeoutException) {
return new RuntimeException("服务繁忙,请稍后重试");
} else {
}
return (RuntimeException) cause;
}
}
快速上手:优化
@FeignClient 和 @EnableFeignClients
@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方法
FeignAutoConfiguration
FeignClientsRegistrar
FeignClientFactoryBean
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);
}
}
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
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;
}
}
扩展
深入Feign源码吃透Spring扩展点「扩展点实战系列」- 第446篇
Feign 中的自动装配
第一步:找到 spring.factories 这个文件
一共有 5 个自动配置类
FeignHalAutoConfiguration
FeignAutoConfiguration
FeignAcceptGzipEncodingAutoConfiguration
FeignContentGzipEncodingAutoConfiguration
FeignLoadBalancerAutoConfiguration
先后加载关系
FeignLoadBalancer 先于 Feign ,FeignContentGzip 和 FeignAcceptGzip 在 Feign 之后加载。
FeignLoadBalancerAutoConfiguration
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
}
核心是 Import 导入的三个配置类
HttpClientFeignLoadBalancerConfiguration
条件注解
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
- ApacheHttpClient 这个类存在为 true 。
- LoadBalancerClient 和 LoadBalancerClientFactory Bean 在容器内才为 true。
- 当这个注解 matchIfMissing = true 的时候,配置文件没有写也为 true。
当所有条件为 true 的时候,执行配置类。
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
}
所以当我们将 feign-httpclient 注入的时候,容器中存在的 Client 类型为 FeignBlockingLoadBalancerClient
Object feignClient = run.getBean("feignClient");
System.out.println(feignClient);
如何验证? 在主启动类获取名为 feignClient 的 Bean 。
org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient@3d3c886f