Feign源码学习(3)—Feign构造Feign.Client

446 阅读4分钟

一、前文描述

前文主要是讲了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, 重点!!