SpringCloud原理 OpenFeign之FeignClient动态代理生成原理四
(3)FeignClientsConfiguration源码剖析
说完NamedContextFactory,接下来我们说一下FeignClientsConfiguration的作用。
这是一个默认的配置类,里面配置了很多bean,这些bean都是生成Feign客户端动态代理的需要的,我说几个重要的。
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
这个的主要作用是用来解析@FeignClient接口中每个方法使用的springmvc的注解的,这也就是为什么FeignClient可以识别springmvc注解的原因。
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
用来构建动态代理的类,通过这个类的target方法,就能生成Feign动态代理
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
这个是FeignClientsConfiguration的内部类,是用来整合hystrix的,@ConditionalOnProperty(name = "feign.hystrix.enabled"),当在配置文件配置了feign.hystrix.enabled=true的时候,就开启了hystrix整合了Feign,然后调用Feign的接口就有了限流、降级的功能。其实hystrix整合Feign很简单,就是在构造动态代理的时候加了点东西而已。其实不光是hystrix,spring cloud alibaba中的sentinel在整合Feign的适合也是按照这个套路来的。
(4)构建动态代理的过程源码剖析
说完了前置的内容,接下来我们就来看一看动态代理是如何生成的。从上面我们已经知道了,@EnableFeignClinets会扫描出每个加了@FeignClient注解的接口,然后生成对应的BeanDefinition,最后重新生成一个bean class为FeignClientFactoryBean的BeanDefinition,注册到spring容器。
接下来就会根据BeanDefinition来生成feign客户端的代理对象了。上面我提到,是通过FeignClientFactoryBean的getObject方法来获取到代理对象,接下来,我们就来着重分析一下getObject方法的实现。
@Override
public Object getObject() throws Exception {
return getTarget();
}
getObject是调用getTarget()来获取代理对象的。
getTarget方法
<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));
}
先从ioc容器中获取到FeignContext,FeignContext里面封装了每个Feign的配置,起到配置隔离的作用。
然后获取到一个Feign.Builder,默认是在FeignClientsConfiguration中配置的。然后调用feign方法。
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
//这个是从配置文件中读取feign的配置
configureFeign(context, builder);
return builder;
}
这个方法很简单,就是从每个FeignClient对应的ioc容器中获取到对应的组件,填充到 Feign.Builder中,默认都是FeignClientsConfiguration配置的。configureFeign这个方法不用去care它,它是默认从配置文件读取feign的配置,然后对Feign.Builder进行配置,有可能会覆盖从每个FeignClient对应的ioc容器中获取到对应的组件,所以配置文件的优先级是最高的,但是一般没人这么玩,所以就不用care。
上面获取到的各种组件都是默认的,如果你有需要替换什么组件,都可以实现,然后通过@FeignClient的configuration配置,就可以替换这些组件。
文章转自公众号:三友的java日记