赞
踩
Spring Cloud 版本 <spring-cloud.version>2020.0.1</spring-cloud.version>
案例说明
8951是消费者,服务消费方,调用方
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..."; } }
作为生产者,和任何其他单体应用的区别是把自己注册到注册中心而已。
完整依赖如下
<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
可以看到已经具有了负载均衡的能力。
我们删除 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
第一步:添加依赖
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-httpclient -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>11.10</version>
</dependency>
第二步验证:是否生效
第一步:添加依赖
<!-- 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。
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
我们来看一下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;
猜想:注解上的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
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版本如下图所示
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)
官方示例,基本关系如图所示
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; } }
@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); } }
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; } }
深入Feign源码吃透Spring扩展点「扩展点实战系列」- 第446篇
第一步:找到 spring.factories 这个文件
一共有 5 个自动配置类
FeignHalAutoConfiguration
FeignAutoConfiguration
FeignAcceptGzipEncodingAutoConfiguration
FeignContentGzipEncodingAutoConfiguration
FeignLoadBalancerAutoConfiguration
FeignLoadBalancer 先于 Feign ,FeignContentGzip 和 FeignAcceptGzip 在 Feign 之后加载。
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
}
核心是 Import 导入的三个配置类
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。