微服务通讯之feign的注册、发现过程

前言

feign 是目前微服务间通讯的主流方式,是springCloud中一个很是重要的组件。他涉及到了负载均衡、限流等组件。真正意义上掌握了feign能够说就掌握了微服务。spring

1、feign的使用

feign 的使用和dubbo的使用本质上很是类似。dubbo的理念是:像调用本地方法同样调用远程方法。那么套在feign上一样适用:像调用本地接口同样调用远程接口。
使用feign只须要2步:定义一个接口并用FeignClient注解说明接口所在服务和路径,服务启动类上添加@EnableFeignClients。以下所示app

1.1,定义一个feign接口

@FeignClient(contextId = "order", name = "order", path = "/app")
public interface OrderApiFeignClient {

   /**
    * 获取订单列表
    * @return
    */
   @RequestMapping("order/list")
   BaseResponse<List<OrderVO>> obtaining(@PathVariable("userId") Long userId);
}

1.2,再启动类上添加注解

@EnableSwagger2
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("com.xxx.*")
@ComponentScan(value={"com.xxx"})
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication .class, args);
    }
}

2、feign 接口如何被实例化到spring容器的?

首先按照通常的思路,咱们会猜想基于接口生成代理类,而后对接口的调用实际上调的是代理对象,那真的是这样么? 咱们带着猜测往下看。负载均衡

2.1 @EnableFeignClients 注解都作了些什么?

能够看到注解自己主要定义了要扫描的feign接口包路径以及配置,可是注解自己又有注解Import ,能够看到他引入了FeignClientsRegistrar到容器。从名字看这个类就应该是在将feign接口注册到容器中,接下来咱们具体看一下这个类干了些什么。框架

/**
 * @author Spencer Gibb
 * @author Jakub Narloch
 * @author Venil Noronha
 * @author Gang Li
 */
class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

能够看到FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,但凡是实现了这个接口的类被注入到容器后,spring容器在启用过程当中都会去调用它的void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2)方法,能够肯定的是FeignClientsRegistrar确定重写了此方法,咱们接下来看一下该方法的实现。ide

能够看到在这个方法中作了两件事: 1)注册feign配置, 2)注册feign接口。咱们这里抓一下重点,看一下feign接口是怎么注册的?微服务

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
                        // 限定只扫描FeingClient注解
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
                                        // 这里生成bean而且注册到容器
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

上面这段代码归纳起来就是: 先找了包路径basePackages , 而后在从这些包路径中查找带有FeignClient注解的接口,最后将注解的信息解析出来做为属性手动构建beanDefine注入到容器中。(这里有一个类ClassPathScanningCandidateComponentProvider,它能够根据filter扫描指定包下面的class对象,十分好用,建议收藏)。包路径的获取以及扫描feign相对简单,这里不作阐述,咱们看一下它生成bean的过程,关注上面代码中的registerFeignClient方法。ui

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
		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"));
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
                // 这里省略部分代码
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

代码中经过BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class)生成的BeanDefine(请记住这里设置得FeignClientFactoryBean.type就是feign接口对应得class对象)。那么全部的feign接口最终注册到容器中的都是FeignClientFactoryBean对应的一个实例(注意实际上注册到容器中压根就不是FeignClientFactoryBean对应的实例化对象,具体缘由看下文),到此feign接口对应的实例注册过程已经完成。那么回到一开始的问题为何咱们调用接口的方法最终发起了请求? 是否有代理类的生成呢? 咱们接下来看看FeignClientFactoryBean类的特殊之处this

2.2 FeignClientFactoryBean 类,feign接口代理生成类

由上文知,每个feign接口实际上最终都会生成FeignClientFactoryBean ,最终由FeignClientFactoryBean生成具体的bean实例注册到容器中。url

/**
 * @author Spencer Gibb
 * @author Venil Noronha
 * @author Eko Kurniawan Khannedy
 * @author Gregor Zurowski
 */
class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware

能够看到该类实现了FactoryBean接口,这意味着当Spring注册该bean实例到容器中时,实际是调用其getObject方法,那么FeignClientFactoryBean必定是重写了getObject()方法,接下来咱们看一下getObject()干了什么事情:代理

public Object getObject() throws Exception {
		return getTarget();
	}

咱们继续追踪getTarget()方法:

<T> T getTarget() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
  
                // 省略部分代码...
		 
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}

显然最终的bean是经过target.target()方法生成,咱们继续往下看:

@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}

显然最终的bean是经过feign.target(target)生成。咱们继续往下看:

public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }

显然最终得bean是经过build().newInstance(target)生成。咱们继续往下看:

public <T> T newInstance(Target<T> target) {
    // 省略部分代码
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

能够看到Proxy.newProxyInstance这个熟悉得身影了,没错他就是基于JDK原生得动态代理生成了FeignClientFactoryBean.type属性对应得class对应得代理类。从前文咱们知道FeignClientFactoryBean.type就是feign接口得class对象。因此最终咱们调用feign接口得方法实际上调用得是InvocationHandler方法。

3、小结

总结起来,就是经常咱们挂在口头的东西就是将feign接口生成代理类,而后调用代理接口方法其实调用的代理类得方法,具体是为何?不知道你们是否清楚。但愿经过本文的阅读能让你们阅读源码的能力获得提高,也不在对feign有一种黑盒子的感受。可能篇幅看起来较少,其实feign的注册过程牵涉到框架层面的知识仍是蛮多的,包括springIoc、BeanDefine、动态代理等等,仔细看明白的话收获应该仍是有蛮多的。哈哈,懂得都懂。顺手提一句:读源码必定要对SPI等等特别熟悉,要否则你会无从下手,没有方向,抓不到重点。后续会更新文章讲feign怎么实现负载均衡、熔断等。

(本文原创、转载请注明出处)