一、前文描述
前文主要是讲了Feign的扫包,但是扫完包之后要干啥呢,要通过动态代理,来对加上了@FeignClinet注解的接口来创建对应的实现类。
并且,前文提到了一个FeignClientFactoryBean类,那么,我们的FeignClientFactoryBean中到底有啥?
二、FeignClientFactoryBean的分析
FeignClientFactoryBean里面维护了Feign接口的动态代理的逻辑。
并且通过自己的分析,发现FeignClientFactoryBean#getObject 方法,就是我们想看的动态代理方法。
PS: 怎么分析? 我是看了看 FeignClientFactoryBean中存在的方法,然后通过语义进行分析,别的方法都不太像, 而getObject首先是个重载的方法,因为方法上加了一个 @Override的注解。
三、getObject方法的分析
断点直接打好:
OK,这个方法实际上就是Spring容器启动的时候,都会走到这个方法里面去了。
<T> T getTarget() {
// 从上下文中获取FeignContext类
1. FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, 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();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
分析,这个方法的第一行是做了什么事情,从语义来说,就是从Spring容器上,去获取FeignContext这个对象。代码如下:
applicationContext.getBean(FeignContext.class);
问题来了,那么 FeignContext这个对象是什么?
这个对象从语义上来说,其实就是某个服务的Feign上下文。里面维护了Feign相关的各种对象,比如说Logger组件,独立了Encoder组件,独立的Decoder组件等等。
并且Feign把上述说的独立组件,会把它封装到一个builder中
Feign.Builder builder = feign(context);
接着是进入Feign.Builder的构造代码
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;
}
首先是 encoder,decoder , contract,设置上述的对象
下面的 configureFeign(context, builder); 方法, 进去看看
protected void configureFeign(FeignContext context, Feign.Builder builder) {
// 获得FeignClientProperties对象
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.contextId), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.contextId), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
这里其实是个优先级的问题~, 看方法名,优先使用服务专属的配置,是怎么做到的呢?
先configureUsingConfiguration -> configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
-> configureUsingProperties(properties.getConfig().get(this.contextId), builder);
OK, 这一步就是构建了属于这个服务的专属属性~,并且最后会返回 Feign.Builder。这里就能够成功获得真正的Feign.Builder, 对象中维护所有Feign相关的属性
四、构造Feign.Client
上述分析到,Feign.Builder 这个对象里面,放了的都是Feign一些配置的属性。
接着,继续看回 FeignClientFactoryBean#getTarget 代码:
<T> T getTarget() {
// 从上下文中获取FeignContext类
1. FeignContext context = applicationContext.getBean(FeignContext.class);
//获得Feign.Builder的对象
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
// 上面进行一个URL 字符串的拼接,拼接的形式
// 如果没配置url的话, 访问的路径就是 url = http://ServiceA(服务名)
// 重点关注 **loadBalance**
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, 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();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
这个 loadBalance方法成功吸引了我的眼球,一看就感觉是feign和ribbon的整合部分。
Ok, 进入loadBalance 方法。
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
// 获取到对应的client
Client client = getOptional(context, Client.class);
// 获取到client的情况下,生成动态代理!!!很关键
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
// 如果拿不到client, 会直接抛异常
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
方法很简单,通过一个 getOptional(context, Client.class); 方法,目的是为了获取client对象。
然后,进入这个getOptional(context, Client.class); 方法
protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.contextId, type);
}
因为这个 context 对象实际上就是 FeignContext,
而 FeignContext 类是继承了Spring Cloud 的 NamedContextFactory, 底层的代码有兴趣可以进行了解。
但是这里不属于我们的重点。
现在问题来了,context.getInstance(this.contextId, type); 目的是要返回一个Client的对象,那么这个对象是在哪里注入的呢?
源码分析方法,从XXXAutoConfiguration 或者 XXXConfiguration 类进入,找一下~~~
果然被我找到 DefaultFeignLoadBalancedConfiguration
@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}
果然,找到LoadBalancerFeignClient 的注入, 那么我们就知道了,Ribbon是通过 LoadBalancerFeignClient这个实现类,注入到Feign中去,让Feign可以使用Ribbon的功能。
OK. 到这里我们清楚了,在Feign初始化调用FeignClientFactoryBean#getObject 的时候,最终这个LoadBalancerFeignClient 会代表ribbon和feign进行一次整合!!
最后还剩下,feign的核心机制,来生成动态代理的Targeter, 重点!!