zzyang

 

 

 年前的时候我发布两篇关于nacos源码的文章,一篇是聊一聊nacos是如何进行服务注册的,另一篇是一文带你看懂nacos是如何整合springcloud -- 注册中心篇。今天就继续接着剖析SpringCloud中OpenFeign组件的源码,来聊一聊OpenFeign是如何工作的。

一、@EnableFeignClinets作用源码剖析

我们都知道,要使用feign,必须要使用@EnableFeignClinets来激活,这个注解其实就是整个feign的入口,接下来我们着重分析一下这个注解干了什么事。

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

 

这个注解通过@Import注解导入一个配置类FeignClientsRegistrar.class,FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,所以Spring Boot在启动的时候,会去调用FeignClientsRegistrar类中的registerBeanDefinitions来动态往spring容器中注入bean。如果有不懂小伙伴可以看一下我以前写过的一篇文章 看Spring源码不得不会的@Enable模块驱动实现原理讲解,这里详细讲解了@Import注解的作用。

接下来看一下registerBeanDefinitions的实现

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry)
    //这个方式是注入一些配置,就是对EnableFeignClients注解属性的解析
    registerDefaultConfiguration(metadata, registry);
    //这个方法是扫秒加了@FeignClient注解
    registerFeignClients(metadata, registry);
}

  

这里我们着重分析registerFeignClients,看一看是如何扫描@FeignClient注解的,然后扫描到之后又做了什么。

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

          registerFeignClient(registry, annotationMetadata, attributes);
        }
      }
    }
  }

  

这段代码我分析一下,先获取到了一个ClassPathScanningCandidateComponentProvider这个对象,这个对象是按照一定的规则来扫描指定目录下的类的,符合这个规则的每个类,会生成一个BeanDefinition,不知道BeanDefinition的小伙伴可以看我之前写的关于bean生命周期的文章 Spring bean到底是如何创建的?(上)和 Spring bean到底是如何创建的?(下),里面有过对BeanDefinition的描述。

获取到ClassPathScanningCandidateComponentProvider对象,配置这个对象,指定这个对象需要扫描出来标有@FeignClient注解的类;随后解析EnableFeignClients注解,获取内部的属性,获取到指定的需要扫描包路径下,如果没有指定的,那么就默认是当前注解所在类的所在目录及子目录。

然后就遍历每个目录,找到每个标有@FeignClient注解的类,对每个类就生成一个BeanDefinition,可以把BeanDefinition看成对每个标有@FeignClient注解的类信息的封装。

拿到一堆BeanDefinition之后,会遍历BeanDefinition,然后调用registerClientConfiguration和registerFeignClient方法。

接下来我分别剖析一下这两个方法的作用

registerClientConfiguration:

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

  

这里的作用就是拿出你再@FeignClient指定的配置类,也就是configuration属性,然后构建一个bean class为FeignClientSpecification,传入配置。这个类的最主要作用就是将每个Feign的客户端的配置类封装成一个FeignClientSpecification的BeanDefinition,注册到spring容器中。记住这个FeignClientSpecification,后面会有用。

registerFeignClient:

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

    String alias = contextId + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

    boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
                                // null

    beanDefinition.setPrimary(primary);

    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
      alias = qualifier;
    }

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
        new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

registerFeignClient这个方法很重要我来说一下大概做了哪些事重新构造了一个BeanDefinition这个BeanDefinition的指定的class类型是FeignClientFactoryBean这个类实现了FactoryBean接口对spring有一定了解的小伙伴应该知道spring在生成bean的时候判断BeanDefinition中bean的class如果是FactoryBean的实现的话会调用这个实现类的getObject来获取对象这里我就不展开讲了

分类:

技术点:

相关文章: