SpringCloud OpenFeign源码详细解析

简介

OpenFeign 是Spring Cloud中的一个组件,经常用来在项目中进行Http调用其他服务的接口,在于对服务端接口的绑定。
使用方式是比较简单的,让我们来探究一下使用原理吧!
阅读条件:

  • 要有Spring源码的基础
  • 使用过原生的Feign
  • SpringBoot源码基础

源码解析入口:@EnableFeignClients

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};

	Class<?>[] defaultConfiguration() default {};

	Class<?>[] clients() default {};
}

注解里面的参数配置,都可以在官网找到说明,这里就不介绍了。
点进去我们可以在注解上看到这样 一段代码:
@Import(FeignClientsRegistrar.class)
这个是往Spring容器里面注入一个类。
SpringCloud OpenFeign源码详细解析_第1张图片核心在于 FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar是Spring中的一个扩展点,实现该接口的方法,我们可以动态的往容器里面注入其他类的信息,然后走Spring的生命周期,创建出Bean。
要说明的一点实现了ImportBeanDefinitionRegistrar的类,该实现类的执行时机在于Spring对配置类的解析阶段执行的,熟悉Spring源码的同学都知道,Spring是先扫描合格的配置类,从配置类中获取要创建Bean的类信息,注册成BeanDefinition简称bd,最后在统一进行实例化。
Spring在解析配置类的时候,会判断该类是否实现了ImportBeanDefinitionRegistrar,是的话就创建对象,执行接口方法,这样也就会执行我们写的自定义逻辑,往容器里面注入bd。
我们直接找到 FeignClientsRegistrar 实现的注入bd方法:

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		// 对 @EnableFeignClients 解析
		registerDefaultConfiguration(metadata, registry);
		// 对 @FeignClient 解析
		registerFeignClients(metadata, registry);
	}

我们直接看对 @FeignClient 的解析。

1.@FeignClient

只看核心部分:

	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
			// 创建一个扫描器
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);
		basePackages = getBasePackages(metadata);
		for (String basePackage : basePackages) {
		  // 扫描指定的包下面的 @FeignClient
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
			     // 核心方法:向容器中注入 FeignClientSpecification
			     registerClientConfiguration(registry, name,
							attributes.get("configuration"));
			     // 核心方法:注册接口,注册到容器
					registerFeignClient(registry, annotationMetadata, attributes);
			}
		}
	}
	
	/**
	* 这个地方每一个 @FeignClient 都会向容器中注入一个 FeignClientSpecification 的用意是什么呢?
	* 在 FeignAutoConfiguration 自动配置里面我们会进行分析,就是为了实现 容器配置隔离。
	* 每一个 @FeignClient 如果指定了 configuration 属性的值,都会对应一个Spring容器,达到隔离的效果。
	* 官网中为啥会说 如果@FeignClient中 指定了 configuration 对应的Class不需要加@Configuration注解呢?在下面我们会进行分析的
	**/
	private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
		// configuration 是 @FeignClient 中的 class[] configuration() default {} 值;
		// 向容器中注入 FeignClientSpecification	
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		registry.registerBeanDefinition(
				name + "." + FeignClientSpecification.class.getSimpleName(),
				builder.getBeanDefinition());
	}
private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		// 注入接口的实际bd类型是 FeignClientFactoryBean
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);
		definition.addPropertyValue("type", className);
		definition.addPropertyValue("decode404", attributes.get("decode404"));
		definition.addPropertyValue("fallback", attributes.get("fallback"));
		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
		// AUTOWIRE_BY_TYPE,方法注入
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = contextId + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		// 构造了bd
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

上面就是扫描包,得到加了注解的类接口信息,然后根据每一个FeignClient,修改bd后向容器注入。
这一步比较重要的就是,统一修改了bd的类信息为FeignClientFactoryBean,并设置了各种参数。

2.FeignClientFactoryBean

SpringCloud OpenFeign源码详细解析_第2张图片
重要的就是这个类的实例化,继承体系上我们可以看到FeignClientFactoryBean是一个FactoryBean,也就是说Spring在创建该类实例的时候,会去调FactoryBean的getObject()方法,下面只要重点分析getObject()方法即可。

	@Override
	public Object getObject() throws Exception {
		return getTarget();
	}
	
  <T> T getTarget() {
    // FeignContext 是从 FeignAutoConfiguration 注入进来的(重要)
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		// feign(context);原生api构建Feign Client
		Feign.Builder builder = feign(context);
		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			this.url += cleanPath();
			// 服务名的调用方式,,负载均衡调用
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
		// 指定了 url 方式
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		Targeter targeter = get(context, Targeter.class);
		// 在往下面就是生成动态代理对象等
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}

	/**
	*get(context, xxx.class) 是从容器中获取的对应的实现类,但并不是直接从当前容器中获取,
	*前面说过 配置隔离,每一个@FeignClient 如果指定了 configuration 属性的值 都会生成
	*一个Spring容器,会根据指定的名称,获取指定的Spring容器,然后从Spring容器里面获取这个xxx.class的实现
	**/
	protected Feign.Builder feign(FeignContext context) {
			// get(context, FeignLoggerFactory.class); 是从Spring容器中获取的
			FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
			Logger logger = loggerFactory.create(this.type);
			// 原生Feign的构建
			Feign.Builder builder = get(context, Feign.Builder.class)
					.logger(logger)
					.encoder(get(context, Encoder.class))
					.decoder(get(context, Decoder.class))
					.contract(get(context, Contract.class));
			// @formatter:on
			configureFeign(context, builder);
			return builder;
		}

	protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		// 从容器中获取 Client.class,真正发起http请求的实现,这个地方可以进行负载均衡请求
		// FeignRibbonClientAutoConfiguration: feign调用ribbon 负载均衡的自动配置
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			return targeter.target(this, builder, context, target);
		}
	}

Client的实现:
FeignBlockingLoadBalancerClient
LoadBalancerFeignClient

3.FeignAutoConfiguration

public class FeignAutoConfiguration {

	/**
	* 这个地方注入进来了前面扫描到的每一个@FeignClient 修改bd过后的 FeignClientSpecification
	**/
	@Autowired(required = false)
	private List<FeignClientSpecification> configurations = new ArrayList<>();

	@Bean
	public FeignContext feignContext() {
	  // 比较重要的一个类
		FeignContext context = new FeignContext();
		context.setConfigurations(this.configurations);
		return context;
	}
}	

FeignContext 继承 NamedContextFactory
SpringCloud OpenFeign源码详细解析_第3张图片

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
	public FeignContext() {
	  // 这个 FeignClientsConfiguration 是Spring默认为Feign提供的上下文信息
	  // 上下文信息里面有 feign.Decoder,feign.Encoder,feign.Contract 等等
	  // 我们没有指定的话,就会用这个类提供的
		super(FeignClientsConfiguration.class, "feign", "feign.client.name");
	}
}

NamedContextFactory:命名空间类

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
	private final String propertySourceName;
	private final String propertyName;
	// 拼接的名字,Spring上下文
	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
	// 拼接的名字,FeignClientSpecification
	private Map<String, C> configurations = new ConcurrentHashMap<>();
	// 父Spring上下文
	private ApplicationContext parent;
	// FeignClientsConfiguration
	private Class<?> defaultConfigType;

	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
  }
  // 上面的get(context, FeignLoggerFactory.class); 最终会掉这个方法先获取容器
	protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
				  // map 中不存在,就会重新创建
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}
	protected AnnotationConfigApplicationContext createContext(String name) {
	  // 手动创建 AnnotationConfigApplicationContext 
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				// 注入configuration也就是@FeignClient指定的configuration属性的值
				context.register(configuration);
			}
		}
		// 默认的 FeignClientsConfiguration,也注入到容器
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		if (this.parent != null) {
		  // 设置父上下文,也就是当前项目启动的Spring容器
			context.setParent(this.parent);
			context.setClassLoader(this.parent.getClassLoader());
		}
		// 刷新容器
		context.refresh();
		return context;
	}
}  		

createContext(String name)里解释了官网的这一段说明:
SpringCloud OpenFeign源码详细解析_第4张图片

4. FeignRibbonClientAutoConfiguration

前面说了,真正发起 http 请求的类是 Client client = getOptional(context, Client.class);

public interface Client {
  /**
   * Executes a request against its {@link Request#url() url} and returns a response.
   */
  Response execute(Request request, Options options) throws IOException;
}  

我们来看一下FeignRibbonClientAutoConfiguration:

@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
	@Bean
	@Primary
	@ConditionalOnMissingBean
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	public CachingSpringLoadBalancerFactory cachingLBClientFactory(
			SpringClientFactory factory) {
		return new CachingSpringLoadBalancerFactory(factory);
	}

	@Bean
	@Primary
	@ConditionalOnMissingBean
	@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
	public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
			SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
		return new CachingSpringLoadBalancerFactory(factory, retryFactory);
	}
}

导入了一个 DefaultFeignLoadBalancedConfiguration

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
	
	/**
	* 注入了一个具备负载均衡的Client:LoadBalancerFeignClient
	**/
	@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory) {
		return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
				clientFactory);
	}

}

FeignRibbonClientAutoConfiguration,FeignAutoConfiguration的加载时机:
SpringCloud OpenFeign源码详细解析_第5张图片基于SpringBoot的自动装配原理。

总结图

SpringCloud OpenFeign源码详细解析_第6张图片
自此源码分析结束!

你可能感兴趣的:(spring,cloud,java,spring,spring,boot,微服务)