SpringCloud原理 OpenFeign之FeignClient动态代理生成原理一

pivoteic
发布于 2022-6-13 17:51
浏览
0收藏

 

SpringCloud原理 OpenFeign之FeignClient动态代理生成原理一-鸿蒙开发者社区

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

 

一、@EnableFeignClinets作用源码剖析

 

我们都知道,要使用feign,必须要使用@EnableFeignClients来激活,这个注解其实就是整个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方法。  

 

文章转自公众号:三友的java日记

已于2022-6-13 17:51:15修改
收藏
回复
举报
回复
    相关推荐