Netflix Feign - Spring Cloud 整合 Feign 源码(一)

121 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


FeignClientsRegistrar#registerBeanDefinitions()

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   // 1. 注册FeignClientSpecification
   registerDefaultConfiguration(metadata, registry);
   // 2. 扫描@FeignClient注解
   // 3. 注册FeignClient BeanDefinition
   // 4. 通过FactoryBean 实例化Bean
   registerFeignClients(metadata, registry);
}
  1. registerDefaultConfiguration()方法,注册FeignClientSpecification到注册表
  2. registerFeignClients()方法,对所有的包进行扫描,专门扫描@FeignClient注解修饰的接口
  3. registerFeignClient()方法,完成@FeignClient修饰的接口的注册

1. 注册FeignClientSpecification

启动OrderService服务,Feign扫描@FeignClient注解。

registerDefaultConfiguration()

AnnotationMetadata metadata是注解相关的一些元数据,BeanDefinitionRegistry registryBean实例注册器。

Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

获取@EnableFeignClients注解的所有属性的值。

String name;
if (metadata.hasEnclosingClass()) {
    name = "default." + metadata.getEnclosingClassName();
}
else {
    name = "default." + metadata.getClassName();
}

获取Aplication启动类的全限定名:default.center.leon.eurekaconsumerribbonfeignapi.EurekaConsumerRibbonFeignApiApplication

registerClientConfiguration()

注册 FeignClient 配置信息

BeanDefinitionBuilder,Bean定义的构造器,

启动类的全限定名,以及defaultConfiguration(空的),调用addConstructorArgValue,将name和defaultConfiguration作为入参构造BeanDefinition对象。

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
                                         Object configuration) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientSpecification.class);
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(configuration);
    registry.registerBeanDefinition(
        name + "." + FeignClientSpecification.class.getSimpleName(),
        builder.getBeanDefinition());
}

基于default.center.leon.eurekaconsumerribbonfeignapi.EurekaConsumerRibbonFeignApiApplication.FeignClientSpecification以及BeanDefinition,注册到BeeanDefinictionRegistry里面去。

  1. FeignClientSpecification:default.center.leon.eurekaconsumerribbonfeignapi.EurekaConsumerRibbonFeignApiApplication
  2. 解析@EnableFeignClients注解的attrs属性值defaultConfiguration
  3. 注册到BeanDefinitionRegistry中。

2. 扫描@FeignClient注解

获取scanner(ClassPathScanningCandidateComponentProvider),在classpath中扫描候选的组件的provider类。

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) {
    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)));
}

处理要扫描的包路径,确定要扫描哪些包里面的@FeignClient注解标注的接口。

先是去获取了@EnableFeignClients里面的属性,因为这个属性里是可以配置basePackages,就是可以自己指定要扫描的包路径的

AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);

AnnotationTypeFilter,根据注解的类型进行过滤,用于过滤@FeignClient注解类型的过滤器

final Class<?>[] clients = attrs == null ? null
    : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
    scanner.addIncludeFilter(annotationTypeFilter);
    basePackages = getBasePackages(metadata);
}

clients,在正常情况下,都是空的,因为我们一般不会在@EnableFeignClients注解中来配置clients属性,所以可以默认一般都是空的。

如果clients是空的,就会给组件扫描器添加一个注解过滤器,专门过滤@FeignClient注解的注解过滤器,就会加入组件扫描器中。

basePackages = getBasePackages(metadata),尝试从@EnableFeingClients注解中去获取要扫描的basePackages包路径,结果没有发现。在@EnableFeignClients注解没有配置basePackages属性的时候,就会去自动生成basePackages。

如果@EnableFefignClients中没有配置要扫描的包路径,默认是Application启动类所在的包。

Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);

使用组件扫描器,在启动类所在包中扫描包含@FeignClient注解的接口,封装成Set<BeanDefinition>返回。

protected ClassPathScanningCandidateComponentProvider getScanner() {
		return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
			@Override
			protected boolean isCandidateComponent(
					AnnotatedBeanDefinition beanDefinition) {
				boolean isCandidate = false;
				if (beanDefinition.getMetadata().isIndependent()) {
					if (!beanDefinition.getMetadata().isAnnotation()) {
						isCandidate = true;
					}
				}
				return isCandidate;
			}
		};
	}

创建组件扫描器时候重写isCandidateComponent()方法,在x@EnableFeignClients注解修饰类所在包下去扫描所有的类,每次扫到一个类,就交给这个isCandidateComponent()方法,判断是否是需要的类。

BeanDefinition candidateComponent是标注@FeignClient的接口,即center.leon.eurekaconsumerribbonfeignapi.product.server.ProductFacadeServiceFeign接口。

Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

@FeignClient注解必须标注在接口上。

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

获取@FeignClient注解的属性,比如value、configuration属性。获取@FeignClient指定的要访问的服务名称。

registerClientConfiguration(registry, name, attributes.get("configuration"));

将name(服务名称) 及 configuration(配置类)注册到BeanDefinitionRegistry中。

registerFeignClient(registry, annotationMetadata, attributes);