Feign的核心加载流程

上篇文章完成了Feign整体流程的的一个大图,本节主要来研究下@FeignClient,是如何被加载到Spring容器中的

实现Feign涉及到两个核心注解@EnableFeignClients@FeignClient就从这两个类入口开始

目录

  • 入口类
  • 核心类的扫描

1.寻找加载的入口类

该注解标注在服务启动入口,会将标注了@FeignClient相关的接口扫描到容器中

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Documented
  4. @Import(FeignClientsRegistrar.class)
  5. public @interface EnableFeignClients {
  6. String[] value() default {};
  7. String[] basePackages() default {};
  8. Class<?>[] basePackageClasses() default {};
  9. Class<?>[] defaultConfiguration() default {};
  10. Class<?>[] clients() default {};
  11. }
  • 这里可以看到核心类是FeignClientsRegistrar
  • 其他的几个参数,可以在@EnableFeignClients中指定相关参数

这里我们可以看到@EnableFeignClients注解包含了几个参数,这里其实我们可以实现自定义的一些功能。那么接下来去看下FeignClientsRegistrar中的实现

2.核心注解的启动入口FeignClientsRegistrar类

  1. class FeignClientsRegistrar
  2. implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
  3. @Override
  4. public void registerBeanDefinitions(AnnotationMetadata metadata,
  5. BeanDefinitionRegistry registry) {
  6. registerDefaultConfiguration(metadata, registry);
  7. registerFeignClients(metadata, registry);
  8. }
  9. }

该类通过实现Spring的相关组件,使得在服务启动时会执行registerBeanDefinitions()的方法,该方法内部通过两个私有方法实现具体功能。这里有个小技巧,一般是去寻找@Override的方法

2.1.registerDefaultConfiguration()

  1. private void registerDefaultConfiguration(AnnotationMetadata metadata,
  2. BeanDefinitionRegistry registry) {
  3. //获取到注解@EnableFeignClients中的所有属性
  4. Map<String, Object> defaultAttrs = metadata
  5. .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
  6. if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
  7. String name;
  8. if (metadata.hasEnclosingClass()) {
  9. name = "default." + metadata.getEnclosingClassName();
  10. }
  11. else {
  12. name = "default." + metadata.getClassName();
  13. }
  14. //这里的name就是`default.包名`
  15. //将name和defaultAttrs注册到BeanDefinitionRegistry中去
  16. registerClientConfiguration(registry, name,
  17. defaultAttrs.get("defaultConfiguration"));
  18. }
  19. }
  • 1.获取到服务启动类的全限定名:default.xxx.EurekaClientApplication
  • 2.解析@EnableFeignClients注解的属性
  • 3.将上面获取的信息注册到BeanDefinitionRegistry中

2.2.registerFeignClients()

该方法完成了@FeignClient相关接口的实例化以及注册功能

扫描@FeignClient的

  1. public void registerFeignClients(AnnotationMetadata metadata,
  2. BeanDefinitionRegistry registry) {
  3. //获取sanner,该scanner只扫描@FeignClient注解的类
  4. ClassPathScanningCandidateComponentProvider scanner = getScanner();
  5. scanner.setResourceLoader(this.resourceLoader);
  6. Set<String> basePackages;
  7. //扫描@FeignClient标注的所有接口的全限定名
  8. ....
  9. for (String basePackage : basePackages) {
  10. Set<BeanDefinition> candidateComponents = scanner
  11. .findCandidateComponents(basePackage);
  12. for (BeanDefinition candidateComponent : candidateComponents) {
  13. if (candidateComponent instanceof AnnotatedBeanDefinition) {
  14. //核心注册到spring容器的类
  15. registerFeignClient(registry, annotationMetadata, attributes);
  16. }
  17. }
  18. }
  19. }
  • 1.获取到scaner,只扫描@FeignClient标注的
  • 2.用scanner扫描@FeignClient,并将其路径名放入set中
  • 3.将上面获取的信息注册到BeanDefinitionRegistry中

注册到容器 registerFeignClient()

  1. private void registerFeignClient(BeanDefinitionRegistry registry,
  2. AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
  3. String className = annotationMetadata.getClassName();
  4. BeanDefinitionBuilder definition = BeanDefinitionBuilder
  5. //这里是核心代理类,FeignClientFactoryBean是后续生成@FeignClient代理类的工厂
  6. .genericBeanDefinition(FeignClientFactoryBean.class);
  7. //基于@FeignClient的一些属性和OrderClient的一些信息构建`BeanDefinitionBuilder`
  8. .....
  9. //构造holder,完成BeanDefinition的注册
  10. BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
  11. new String[] { alias });
  12. BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
  13. }
  • 基于@FeignClient的一些属性和OrderClient的一些信息构建BeanDefinitionBuilder
  • 构造BeanDefinitionHolder,完成BeanDefinition的注册

3.总结

一张图来总结整个Feign加载的流程

2.Feign的核心加载流程 - 图1

我们从入口类@EnableFeignClients中找到了核心的类FeignClientsRegistrar,然后从中找到了对应的入口方法。这里其实核心的操作分为两个流程

  • 处理@EanbleFeignClients,这里找到核心入口类FeignClientsRegistrar
    • 解析@EanbleFeignClients入口,若有指定的参数basePackage等,则获取到待扫描的路径名,若无,则取springboot启动类路径
    • 解析@EnableFeignClients对应的相关参数

5.关于