Feign源码分析(2)

推荐文章

深入理解Feign的Method has too many Body parameters异常
Feign 实现多文件上传
spring cloud feign (包含上传文件和下载文件)
Feign实现多文件上传,Open Feign多文件上传解决方案
工具篇–Spring-Cloud–feign 通过feign 接口完成文件的下载
Feign上传/接收文件实现代码

前面,我们已经把Feign的2个注解:@FeignClient、@EnableFeignClients,以及Feign客户端接口扫描注册过程,包括Feign创建代理的过程、代理执行请求的流程都分析了一遍。其实,还有2部分没有提到:OpenFeign是如何完成自动配置的OpenFeign的负载均衡实现

FeignAutoConfiguration

在我们引入的spring-cloud-openfeign-core的jar包中,里面存在spring.factories文件,配置了如下内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
	org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
	org.springframework.cloud.openfeign.FeignAutoConfiguration,\
	org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
	org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

我们不妨先看下FeignAutoConfiguration

@Configuration
@ConditionalOnClass(Feign.class) // 必须存在Fegin这个类,该自动配置类才生效
@EnableConfigurationProperties({FeignClientProperties.class,      // 前缀为: feign.client.xxx配置
								FeignHttpClientProperties.class}) // 前缀为: feign.httpclient.xxx配置
public class FeignAutoConfiguration {

	// 这里会默认注入容器中所有配置的 FeignClientSpecification 的bean
	// 		那么,在哪里有配置这个bean呢?
	//			1. @EnableFeignClients会默认配置
	//			  		beanName-> default.@EnableFeignClients所标注的类的类名+FeignClientSpecification
	//            		beanClass-> FeignClientSpecification(并且我们指定了2个参数,一个是: default, 另一个参数是从@EnableFeignClients注解上获取的配置类)
	//			2. 在@EnableFeignClients扫描包下的@FeignClient标注的客户端接口, 会配置
	//					beanName->客户端名字(contextId->value->name->serviceId) .FeignClientSpecification
	//  		 		beanClass-> FeignClientSpecification(并且我们指定了2个参数,一个是: name(contextId->value->name->serviceId) ,
	//																			 另外一个参数是从@FeignClient注解上获取的配置类)
	@Autowired(required = false)
	private List<FeignClientSpecification> configurations = new ArrayList<>();


	// springcloud提供了featuresEndpoint,可以方便我们查看系统启动的一些features,进而了解系统特征
	@Bean
	public HasFeatures feignFeature() {
		return HasFeatures.namedFeature("Feign", Feign.class);
	}

	// 这个就比较关键了
	// FeignContext继承自NamedContextFactory<FeignClientSpecification> 
	// 它持有了全部注入的FeignClientSpecification, 这些配置将会被用来创建spring子容器(每个子容器都有自己的名字)
	@Bean
	public FeignContext feignContext() {
		FeignContext context = new FeignContext();
		context.setConfigurations(this.configurations);
		return context;
	}

	// 当存在feign.hystrix.HystrixFeign这个类时,
	// 并且我们没有自定义Targeter的bean时,将会使用HystrixTargeter
	@Configuration
	@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
	protected static class HystrixFeignTargeterConfiguration {
		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new HystrixTargeter();
		}
	}

	// 当不存在feign.hystrix.HystrixFeign这个类时,使用 DefaultTargeter
	@Configuration
	@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
	protected static class DefaultFeignTargeterConfiguration {
		@Bean
		@ConditionalOnMissingBean
		public Targeter feignTargeter() {
			return new DefaultTargeter();
		}
	}

	// 以下2个内部配置类, 都是当不存在ILoadBalancer负载均衡器时, 并且引入对应的依赖才会生效
	
	/* 需要引入feign-httpclient的依赖
		<dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>9.4.0</version>
        </dependency>
	*/
	@Configuration
	@ConditionalOnClass(ApacheHttpClient.class) // 必须导入ApacheHttpClient
	@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")  // 没有引入ILoadBalancer负载均衡器依赖
	@ConditionalOnMissingBean(CloseableHttpClient.class) // 允许用户自定义CloseableHttpClient
	@ConditionalOnProperty(value = "feign.httpclient.enabled",  // feign.httpclient.enabled为true, 或未配置
						   matchIfMissing = true)
	protected static class HttpClientFeignConfiguration {
		private final Timer connectionManagerTimer = new Timer(
				"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);

		@Autowired(required = false)
		private RegistryBuilder registryBuilder;

		private CloseableHttpClient httpClient;

		// 配置 HttpClientConnectionManager 
		@Bean
		@ConditionalOnMissingBean(HttpClientConnectionManager.class)
		public HttpClientConnectionManager connectionManager(
				ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
				FeignHttpClientProperties httpClientProperties)  // 可以通过feign.httpclient.xxx配置
		{
			final HttpClientConnectionManager connectionManager = connectionManagerFactory
					.newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
							httpClientProperties.getMaxConnectionsPerRoute(),
							httpClientProperties.getTimeToLive(),
							httpClientProperties.getTimeToLiveUnit(), registryBuilder);
			this.connectionManagerTimer.schedule(new TimerTask() {
				@Override
				public void run() {
					connectionManager.closeExpiredConnections();
				}
			}, 30000, httpClientProperties.getConnectionTimerRepeat());
			return connectionManager;
		}

		// 配置 CloseableHttpClient
		@Bean
		public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
				HttpClientConnectionManager httpClientConnectionManager,
				FeignHttpClientProperties httpClientProperties) {
			RequestConfig defaultRequestConfig = RequestConfig.custom()
					.setConnectTimeout(httpClientProperties.getConnectionTimeout())
					.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
					.build();
			this.httpClient = httpClientFactory.createBuilder().
					setConnectionManager(httpClientConnectionManager).
					setDefaultRequestConfig(defaultRequestConfig).build();
			return this.httpClient;
		}

		// 将apache的httpClient适配成Feign需要Client接口实例
		@Bean
		@ConditionalOnMissingBean(Client.class) // 当已经配置了Client的bean时,就不会配置这个了
		public Client feignClient(HttpClient httpClient) {
			return new ApacheHttpClient(httpClient);
		}

		@PreDestroy
		public void destroy() throws Exception {
			connectionManagerTimer.cancel();
			if(httpClient != null) {
				httpClient.close();
			}
		}
	}

	/*
		引入依赖
		<dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
            <version>11.8</version>
        </dependency>
	
	*/
	@Configuration
	@ConditionalOnClass(OkHttpClient.class) // 必须导入okhttp的依赖
	@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer") // 没有引入ILoadBalancer负载均衡器依赖
	@ConditionalOnMissingBean(okhttp3.OkHttpClient.class) // 允许用户自定义OkHttpClient实例的bean
	@ConditionalOnProperty(value = "feign.okhttp.enabled") // 必须用户主动开启okhttp才会生效
	protected static class OkHttpFeignConfiguration {

		private okhttp3.OkHttpClient okHttpClient;

		// 连接池的 配置
		@Bean
		@ConditionalOnMissingBean(ConnectionPool.class)
		public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
													   OkHttpClientConnectionPoolFactory connectionPoolFactory) {
			Integer maxTotalConnections = httpClientProperties.getMaxConnections();
			Long timeToLive = httpClientProperties.getTimeToLive();
			TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
			return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
		}

		// okhttp3的配置
		@Bean
		public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
										   ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
			Boolean followRedirects = httpClientProperties.isFollowRedirects();
			Integer connectTimeout = httpClientProperties.getConnectionTimeout();
			Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
			this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation).
					connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
					followRedirects(followRedirects).
					connectionPool(connectionPool).build();
			return this.okHttpClient;
		}

		@PreDestroy
		public void destroy() {
			if(okHttpClient != null) {
				okHttpClient.dispatcher().executorService().shutdown();
				okHttpClient.connectionPool().evictAll();
			}
		}

		// 将okhttp3适配成feign的Client接口实例
		@Bean
		@ConditionalOnMissingBean(Client.class) // 当已经配置了Client的bean时,就不会配置这个了
		public Client feignClient(okhttp3.OkHttpClient client) {
			return new OkHttpClient(client);
		}
	}

}

FeignContext

在FeignAutoConfiguration中,配置FeignContext这个bean,而从上一节分析中,我们发现在FeignClientFactoryBean中在拿组件的时候,几乎都会从FeignContext中去拿的,所以,有必要看下FeignContext。

可以把FeignContext理解为一个spring子容器的集合,每一个spring子容器都会有自己的名字,同时创建一个spring(子)容器,需要指定一个或多个配置类(就好比我们之前的spring.xml配置文件),而当我们需要组件的时候,首先,要根据名字,从FeignContext中取出对应的spring子容器,然后根据类型,从spring子容器中取出对应的bean,这样就可以使用不同名字就可以拿到不同的组件,也就是配置隔离

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

	public FeignContext() {
	
		super(FeignClientsConfiguration.class,  // 默认使用的配置类
			  "feign", // 属性源的名字
			  "feign.client.name" // 属性名
	    );
	}

}

NamedContextFactory< C extends NamedContextFactory.Specification>

FeignContext 继承自NamedContextFactory,见名知义,带名字的容器工厂。

// NamedContextFactory的泛型是 内部的 Specification接口
// 也就是说: NamedContextFactory的实现类, 必须指定一个泛型, 
//			并且这个泛型要实现NamedContextFactory.Specification接口,
// 那么,指定这个泛型有什么用呢 ?
//		就相当于说, 子类可以自定义自己的Specification实现, 
//		比如: FeignContext有自己的Specification实现(FeignClientSpecification)
//		     Ribbon也有自己的Specification实现(RibbonClientSpecification)
//		但是,不管它们怎么实现,每个Specification实现都会由一个name和一个配置类数组组成
//		     并且,不同的Specification实现,不能混在一起,由子类自己去定义实现,就不会搞混了,
//			 你注入你的Specification实现(比如feign),我注入我的Specification实现(比如ribbon)
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, 
				  ApplicationContextAware { // 外部spring父容器回调设置

	// 一个Specification由一个name和一个配置类数组组成(由配置类, 我们就可以根据配置类创建spring容器了呀)
	public interface Specification {
	
		String getName();

		Class<?>[] getConfiguration();
	}

	// 名字 -> spring子容器
	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

	// 名字 -> NamedContext.Spcification接口实例
	private Map<String, C> configurations = new ConcurrentHashMap<>();

	// 外部spring父容器
	private ApplicationContext parent;

	// 默认配置类
	private Class<?> defaultConfigType;
	// 属性源名字(比如: 子类FeignContext是:"feign")
	private final String propertySourceName;
	// 属性名(比如: 子类FeignContext是:"feing.client.name")
	private final String propertyName;

	// 唯一构造方法,传入默认配置类、属性源名字、属性名
	public NamedContextFactory(Class<?> defaultConfigType, 
							   String propertySourceName,
							   String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

	// 回调设置外部spring父容器
	@Override
	public void setApplicationContext(ApplicationContext parent) throws BeansException {
		this.parent = parent;
	}

	// 由子类设置 Specification接口实例 集合, 放入configurations中
	//			(比如: FeignAutoConfiguration中就把自动注入的List<FeignClientSpecification>集合设置进来了)
	public void setConfigurations(List<C> configurations) {
		for (C client : configurations) {
			// 名字 -> NamedContext.Specification接口实例
			this.configurations.put(client.getName(), client);
		}
	}

	// 获取所有子容器的名字
	public Set<String> getContextNames() {
		return new HashSet<>(contexts.keySet());
	}

	// 销毁时, 关闭所有子容器
	@Override
	public void destroy() {
		Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
		for (AnnotationConfigApplicationContext context : values) {
			// This can fail, but it never throws an exception (you see stack traces
			// logged as WARN).
			context.close();
		}
		this.contexts.clear();
	}

	// 根据名字,从NamedContext中,找出名字对应的spring子容器(如果此时还没有,则会去创建该容器)
	protected AnnotationConfigApplicationContext getContext(String name) {
		// 先从contexts属性中查找
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					// 没有,则走创建的逻辑
					this.contexts.put(name, createContext(name));
				}
			}
		}
		
		// 如果有, 则直接取出
		return this.contexts.get(name);
	}

	// 创建对应名字的spring子容器
	protected AnnotationConfigApplicationContext createContext(String name) {
	
		// 直接new(只要来拿, 我就new一个出来, 不管有没有)
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

		// 前面设置进来的NamedContext.Specification实例的getName()方法返回值就是name
		// 也就是: 这里先查看是否设置了对应名字的NamedContext.Specification实例
		if (this.configurations.containsKey(name)) {
			// 如果有设置, 则取出NamedContext.Specification实例的所有配置类,注册到上面创建的容器中
			for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
				context.register(configuration);
			}
		}

		// 看完了有没有指定name的Specification,
		// 然后继续遍历configurations,找出其中以"default."开头的Specification实例
		// (比如: 在上一篇中, 在feign客户端的扫描过程中, 
		//		 是有添加default.@EnableFeignClients所标注的类的类名+FeignClientSpecification的FeignClientSpecification实例的, 
		//       并且该实例的配置类, 就是@EnableFeignClients注解的defaultConfiguration属性)
		// 同样,也把它们作为配置类注册到容器中,
		//      这样就相当于用户主动设置的保底配置(注意注册的顺序就是解析的顺序)
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {				
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		
		// 同时注册2个类: PropertyPlaceholderAutoConfiguration 和 默认配置类
		// (比如: FeignContext默认的配置类是FeignClientsConfiguration, 它是框架指定的兜底配置, 并且里面生效条件基本都以用户设置的优先)
		context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);
		
		// 注册了一个属性源
		// (比如: FeignContext设置的是 feign:feign.client.name->{name})
		context.getEnvironment().getPropertySources()
			   .addFirst(new MapPropertySource(this.propertySourceName,
			   								   Collections.singletonMap(this.propertyName, name)));

		// 将外部spring容器设置为当前子容器的父容器(所以子容器自然能去父容器里去拿组件了)
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
		}

		// 设置displayName为 “NamedContextFactory-{name}”
		context.setDisplayName(generateDisplayName(name));
		
		// 所有的配置类、属性源、父容器设置好后,刷新该springg子容器(解析配置类,注册配置的组件)
		context.refresh();
		
		return context;
	}
	
	// 从对应名字的容器中获取对应类型的bean(包括父容器, 优先子容器)
	public <T> T getInstance(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {
			return context.getBean(type);
		}
		return null;
	}

	// 延迟获取(ClientFactoryObjectProvider)
	public <T> ObjectProvider<T> getLazyProvider(String name, Class<T> type) {
		return new ClientFactoryObjectProvider<>(this, name, type);
	}

	// 延迟获取(BeanObjectProvider)
	public <T> ObjectProvider<T> getProvider(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		return context.getBeanProvider(type);
	}

	// 从对应名字的子容器中,根据类和泛型获取组件
	public <T> T getInstance(String name, Class<?> clazz, Class<?>... generics) {
		ResolvableType type = ResolvableType.forClassWithGenerics(clazz, generics);
		return getInstance(name, type);
	}

	// 从spring子容器中(包括父容器, 优先子容器), 获取对应类型(及泛型)的组件
	public <T> T getInstance(String name, ResolvableType type) {
		AnnotationConfigApplicationContext context = getContext(name);
		String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type);
		if (beanNames.length > 0) {
			for (String beanName : beanNames) {
				if (context.isTypeMatch(beanName, type)) {
					return (T) context.getBean(beanName);
				}
			}
		}
		return null;
	}

	// 从spring子容器中(包括父容器, 优先子容器), 获取“所有”对应类型的组件
	public <T> Map<String, T> getInstances(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,type).length > 0) {
			return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
		}
		return null;
	}

}

FeignClientsConfiguration

FeignClientsConfiguration是在FeignContext构造方法中指定的默认配置类,同时从FeignContext的父类NamedContext中,我们知道FeignClientsConfiguration是最后注册到spring子容器中的配置类,它是框架提供给我们的默认兜底的配置类,我们可以覆盖其中配置的组件,达到配置feign客户端的目的。

@Configuration
public class FeignClientsConfiguration {

	// 注入消息转化器(包含许多的消息转换器, 见: HttpMessageConvertersAutoConfiguration)
	@Autowired
	private ObjectFactory<HttpMessageConverters> messageConverters;

	// 注入注解参数处理器
	//      PathVariableParameterProcessor、
	//		QueryMapParameterProcessor、
	//      RequestHeader、
	//		RequestParamParameterProcessor
	@Autowired(required = false)
	private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();

	// 注入Feign格式化注册器
	@Autowired(required = false)
	private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

	// 注入feign的logger
	@Autowired(required = false)
	private Logger logger;

	// 定义feign的解码器
	@Bean
	@ConditionalOnMissingBean
	public Decoder feignDecoder() {
		return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
	}

	// 定义feign的编码器
	@Bean
	@ConditionalOnMissingBean
	public Encoder feignEncoder() {
		return new SpringEncoder(this.messageConverters);
	}

	// 定义feign的契约
	@Bean
	@ConditionalOnMissingBean
	public Contract feignContract(ConversionService feignConversionService) {
		return new SpringMvcContract(this.parameterProcessors, feignConversionService);
	}

	// 格式化类型转换器
	@Bean
	public FormattingConversionService feignConversionService() {
		FormattingConversionService conversionService = new DefaultFormattingConversionService();
		for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
			feignFormatterRegistrar.registerFormatters(conversionService);
		}
		return conversionService;
	}

	// 如果引入了feign-hystrix, 则定义HystrixFeign.Builder(它实现了Feign.Builder)
	@Configuration
	@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();
		}
	}

	// feign的重试器,默认不重试,遇到异常,直接将异常抛给调用者
	@Bean
	@ConditionalOnMissingBean
	public Retryer feignRetryer() {
		return Retryer.NEVER_RETRY;
	}

	// 上面静态内部类中也定义了一个Feign.Builder, 那么谁生效呢?
	// 在解析一个配置类时, 先解析里面的成员类 (ConfigurationClassParser#doProcessConfigurationClass)
	// 所以,上面的定义先生效,如果上面没满足生效条件,这个才生效
	@Bean
	@Scope("prototype")
	@ConditionalOnMissingBean
	public Feign.Builder feignBuilder(Retryer retryer) {
		return Feign.builder().retryer(retryer);
	}

	// Feign的日志工厂
	// (如果logger不为null,则直接使用该logger;如果为null,则使用new Slf4jLogger(Class<?>))
	@Bean
	@ConditionalOnMissingBean(FeignLoggerFactory.class)
	public FeignLoggerFactory feignLoggerFactory() {
		return new DefaultFeignLoggerFactory(logger);
	}

}

FeignRibbonClientAutoConfiguration

回顾FeignClientFactoryBean#getTarget()

在分析feign的负载均衡的过程前,先回顾下FeignClientFactoryBean#getTarget(),这个方法会返回Feign客户端接口的代理对象。当我们的Feign客户端没有设置url时,就会从FeignContext中获取Client实例,然后Feign会使用这个Client实例发送请求。(而当我们设置了url时,并且也没有配置Client实例时,则会使用默认的Client.Default(见:Feign.Builder#client属性))

<T> T getTarget() {

	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();
		
		// 如果没有设置url,将会使用负载均衡
		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) {
			
			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)
						      );
}

protected <T> T loadBalance(Feign.Builder builder, 
							FeignContext context,
							HardCodedTarget<T> target) {
							
	// 从FeignContext中获取Client(这一步,就是负载均衡的扩展点入口)
	Client client = getOptional(context, Client.class);
	
	if (client != null) {
		// 将获取到的Client,设置给了Feign.Builder
		builder.client(client);
		Targeter targeter = get(context, Targeter.class);
		return targeter.target(this, builder, context, target);
	}
	
	// 负载均衡需要引入spring-cloud-starter-netflix-ribbon
	throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

从上面,我们得出了结论:要实现负载均衡,容器必须提供Feign的Client实现,这个Client实现将会用于负载均衡,这个类就是LoadBalancerFeignClient,我们先看它是怎么配置到容器中的。

// 必须存在com.netflix.ribbon:ribbon-loadbalancer的负载均衡依赖 和 Feign的依赖
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
// 在Feign的自动配置类之前执行
@AutoConfigureBefore(FeignAutoConfiguration.class)
// 开启feign.httpclient的属性配置
@EnableConfigurationProperties({ FeignHttpClientProperties.class })

@Import({ HttpClientFeignLoadBalancedConfiguration.class,  // 使用ApacheClient的实现,包装在LoadBalancerFeignClient中
		  OkHttpFeignLoadBalancedConfiguration.class,      // 使用OkHttpClient的实现,包装在LoadBalancerFeignClient中
		  DefaultFeignLoadBalancedConfiguration.class      // 使用Feign自己默认的Client.Default的实现,包装在LoadBalancerFeignClient中
	   })
public class FeignRibbonClientAutoConfiguration {

	// SpringClientFactory也继承自:NamedContextFactory<RibbonClientSpecification>
	// 只不过它提供的NamedContextFactory.Specification是RibbonClientSpecification
	// 所以说Feign的负载均衡就是借用了ribbon的实现

	// 当没有引入spring-retry的依赖时, 这个配置生效(可被用户覆盖)
	@Bean
	@Primary
	@ConditionalOnMissingBean
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") 
	public CachingSpringLoadBalancerFactory cachingLBClientFactory(SpringClientFactory factory) {
		// 可缓存Feign客户端的对应的FeignLoadBalancer(同一个clientName,只创建一个FeignLoadBalancer)
		return new CachingSpringLoadBalancerFactory(factory); 
	}

	// 当引入了spring-retry的依赖时, 这个配置生效(可被用户覆盖)
	@Bean
	@Primary
	@ConditionalOnMissingBean
	@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
	public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(SpringClientFactory factory,
																		   LoadBalancedRetryFactory retryFactory) {
		return new CachingSpringLoadBalancerFactory(factory, retryFactory);
	}

	// Request.Options可被覆盖
	@Bean
	@ConditionalOnMissingBean
	public Request.Options feignRequestOptions() {
		return LoadBalancerFeignClient.DEFAULT_OPTIONS;
	}
}

CachingSpringLoadBalancerFactory

public class CachingSpringLoadBalancerFactory {

	// ribbon的NamedContextFactory
	protected final SpringClientFactory factory;
	
	// 负载均衡重试工厂
	protected LoadBalancedRetryFactory loadBalancedRetryFactory = null;

	private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();

	public CachingSpringLoadBalancerFactory(SpringClientFactory factory) {
		this.factory = factory;
	}

	// 根据客户端名称 获取 FeignLoadBalaner
	public CachingSpringLoadBalancerFactory(SpringClientFactory factory, LoadBalancedRetryFactory loadBalancedRetryPolicyFactory) {
		this.factory = factory;
		this.loadBalancedRetryFactory = loadBalancedRetryPolicyFactory;
	}

	public FeignLoadBalancer create(String clientName) {
	
		// 先从缓存中拿,缓存中有,则直接返回;没有,则须创建
		FeignLoadBalancer client = this.cache.get(clientName);
		if(client != null) {
			return client;
		}
		
		// 从ribbon的NamedContextFactory,根据客户端名字获取IClientConfig (客户端配置隔离的体现)
		IClientConfig config = this.factory.getClientConfig(clientName);
		// 从ribbon的NamedContextFactory,根据客户端名字获取ILoadBalancer(客户端配置隔离的体现)
		ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
		// 从ribbon的ServerIntrospector ,根据客户端名字获取ILoadBalancer(客户端配置隔离的体现)
		ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
		
		// 创建FeignLoadBlancer
		client = loadBalancedRetryFactory != null ? 
									new RetryableFeignLoadBalancer(lb, config, serverIntrospector,loadBalancedRetryFactory) 
								  : new FeignLoadBalancer(lb, config, serverIntrospector);
								  
	    // 放入缓存
		this.cache.put(clientName, client);
		return client;
	}

}

HttpClientFeignLoadBalancedConfiguration

@Configuration
@ConditionalOnClass(ApacheHttpClient.class) // 必须引入feign-httpclient的依赖
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
class HttpClientFeignLoadBalancedConfiguration {

	@Configuration
	@ConditionalOnMissingBean(CloseableHttpClient.class)
	protected static class HttpClientFeignConfiguration {
	
		private final Timer connectionManagerTimer = new Timer("FeignApacheHttpClientConfiguration.connectionManagerTimer", true);

		private CloseableHttpClient httpClient;

		@Autowired(required = false)
		private RegistryBuilder registryBuilder;

		@Bean
		@ConditionalOnMissingBean(HttpClientConnectionManager.class)
		public HttpClientConnectionManager connectionManager(
				ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
				FeignHttpClientProperties httpClientProperties) {
			final HttpClientConnectionManager connectionManager = connectionManagerFactory
					.newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
							httpClientProperties.getMaxConnectionsPerRoute(),
							httpClientProperties.getTimeToLive(),
							httpClientProperties.getTimeToLiveUnit(), registryBuilder);
			this.connectionManagerTimer.schedule(new TimerTask() {
				@Override
				public void run() {
					connectionManager.closeExpiredConnections();
				}
			}, 30000, httpClientProperties.getConnectionTimerRepeat());
			return connectionManager;
		}

		@Bean
		@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "true")
		public CloseableHttpClient customHttpClient(HttpClientConnectionManager httpClientConnectionManager,
											  FeignHttpClientProperties httpClientProperties) {
			HttpClientBuilder builder = HttpClientBuilder.create().disableCookieManagement().useSystemProperties();
			this.httpClient = createClient(builder, httpClientConnectionManager, httpClientProperties);
			return this.httpClient;
		}

		@Bean
		@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "false", matchIfMissing = true)
		public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager,
											  FeignHttpClientProperties httpClientProperties) {
			this.httpClient = createClient(httpClientFactory.createBuilder(), httpClientConnectionManager, httpClientProperties);
			return this.httpClient;
		}

		private CloseableHttpClient createClient(HttpClientBuilder builder, HttpClientConnectionManager httpClientConnectionManager,
												 FeignHttpClientProperties httpClientProperties) {
			RequestConfig defaultRequestConfig = RequestConfig.custom()
					.setConnectTimeout(httpClientProperties.getConnectionTimeout())
					.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
					.build();
			CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig).
					setConnectionManager(httpClientConnectionManager).build();
			return httpClient;
		}

		@PreDestroy
		public void destroy() throws Exception {
			connectionManagerTimer.cancel();
			if(httpClient != null) {
				httpClient.close();
			}
		}
	}

	// 直接使用LoadBalancerFeignClient包装了apache请求客户端
	@Bean
	@ConditionalOnMissingBean(Client.class)
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
							  SpringClientFactory clientFactory, HttpClient httpClient) {
		ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
		return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
	}


}

OkHttpFeignLoadBalancedConfiguration

@Configuration
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty(value = "feign.okhttp.enabled")
class OkHttpFeignLoadBalancedConfiguration {

	@Configuration
	@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
	protected static class OkHttpFeignConfiguration {
		private okhttp3.OkHttpClient okHttpClient;

		@Bean
		@ConditionalOnMissingBean(ConnectionPool.class)
		public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
													   OkHttpClientConnectionPoolFactory connectionPoolFactory) {
			Integer maxTotalConnections = httpClientProperties.getMaxConnections();
			Long timeToLive = httpClientProperties.getTimeToLive();
			TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
			return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
		}

		@Bean
		public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
										   ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
			Boolean followRedirects = httpClientProperties.isFollowRedirects();
			Integer connectTimeout = httpClientProperties.getConnectionTimeout();
			this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).
					connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
					followRedirects(followRedirects).
					connectionPool(connectionPool).build();
			return this.okHttpClient;
		}

		@PreDestroy
		public void destroy() {
			if(okHttpClient != null) {
				okHttpClient.dispatcher().executorService().shutdown();
				okHttpClient.connectionPool().evictAll();
			}
		}
	}

	// 直接使用LoadBalancerFeignClient包装了okhttp请求客户端
	@Bean
	@ConditionalOnMissingBean(Client.class)
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
							  SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
		OkHttpClient delegate = new OkHttpClient(okHttpClient);
		return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
	}
}

DefaultFeignLoadBalancedConfiguration

@Configuration
class DefaultFeignLoadBalancedConfiguration {

	// 直接使用LoadBalancerFeignClient包装了Feign原始的请求客户端
	@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
							  SpringClientFactory clientFactory) {
							  
		return new LoadBalancerFeignClient(new Client.Default(null, null),
										   cachingFactory, 
										   clientFactory);
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值