本文已参与「新人创作礼」活动,一起开启掘金创作之路。
4. 实例化FeignClient
-
构造
FeignClientFactoryBean的BeanDefinition注册到beanDefinitionMaps中。Spring IOC AbstractApplicationContext refresh() // 注册 BeanDefinition invokeBeanFactoryPostProcessors(beanFactory)
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
- 回调
FeignClientFactoryBean.getObject()方法获取FeignClient实例
FeignClientFactoryBean 实现了 FactoryBean,在SpringIOC第十一步finishBeanFactoryInitialization(beanFactory)通过FactoryBean.getObject()方法返回Bean实例。
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware
getObject
getObject() 调用 getTraget() 方法。
FeignContext
获取FeignContext实例,通过构造器模式获得Feign.Builder实例。
FeignContext context = this.applicationContext.getBean(FeignContext.class);
FeignContexnt继承NamedContextFactory,通过Map<String, C> configurations提供各个FeignClient的上下文数据互相隔离的能力。
public class FeignContext extends NamedContextFactory<FeignClientSpecification>
FeignContext 构造时,加载FeignClientsConfiguration,注入默认的Decoder、Encoder、FeignLoggerFactory、Contract。
Logger:DefaultFeignLoggerFactory
Encoder:SpringEncoder
Decoder:SpringDecoder(ResponseEntityDecoder)
Contract:SpringMvcContract
Feign.Builder:Feign.builder().retryer(retryer);Retryer.NEVER_RETRY;
FeignContext 内部保存一个 Map<String, C> configurations,由List<FeignClientSpecification> configurations的name和FeignClientSpecification映射而成。
List<FeignClientSpecification> configurations,来自于 FeignClientsRegister构造注册FeignClient的BeanDefinition之前,获取@FeignClient注解的name属性和configuration属性,构造FeignClientSpecification的BeanDefinition的集合。是所有@FeignClient注解的集合。
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
// 设置 configurations 填充 NamedContextFactory的configurations。
context.setConfigurations(this.configurations);
return context;
}
Feign.Builder
Feign.Builder builder = feign(context);
通过feign方法,从FeignContext context上下文中根据当前FeignClientFactoryBean的contextId(就是@FeignClient注解设置测name)获取对应的子容器。
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
通过context获取contextId,从Map<String, AnnotationConfigApplicationContext> contexts中获取对应的子容器,然后通过指定的BeanType从子容器中获取BeanType实例。然后放入Feign.Builder中。
Feign.Builder 默认使用整合了Hystrix的HystrixFeign.Builder。
在fegin方法中先配置基础的默认的Logger、Decoder、Encoder、Contract之后,在configureFeign()方法中根据FeignClientProperties properties 和 FeignContext context 中的配置信息,配置Feign.Builder中的各个组件。
Logger.Level、Retryer、ErrorDecoder、Request.Options、List<RequestInterceptor>
先配置自定义的Configuration,然后是springboot配置文件中默认的客户端配置,最后时springboot配置文件中指定服务名称的客户端配置。依次覆盖。
重构url
如果@FeignClient注解没有执行url属性,重构url(http://eureka-provider-ribbon-feign-api-impl),通过loadBalance构造整合Ribbon的具备负载均衡的FeignClient。
loadBalance()
loadBalance()方法通过Targeter实现类HystrixTargeter.target获取FeignClient实例。
@Override
public Object getObject() throws Exception {
return getTarget();
}
/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
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 = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}