学科分类
目录
Spring Cloud

Feign服务调用的工作原理

开启Spring Cloud Feign功能是通过@EnableFeignClients注解实现的。程序启动时,首先会检测是否有@EnableFeignClients注解,如果有,则会开启扫描功能,并扫描被@FeignClient注解修饰的接口。接下来,我们从@EnableFeignClients注解入手,分析Feign远程调用服务的工作原理。

查看@EnableFeignClients注解的源码,具体代码如下所示。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
    String[] value() default {};
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<?>[] defaultConfiguration() default {};
    Class<?>[] clients() default {};
}

从上述源码可以看出,@EnableFeignClients注解引入了FeignClientsRegistrar类,继续跟踪该类源码,发现其内部定义了一个registerBeanDefinitions方法,具体代码如下所示。

public void registerBeanDefinitions(AnnotationMetadata metadata, 
BeanDefinitionRegistry registry) {
        this.registerDefaultConfiguration(metadata, registry);
        this.registerFeignClients(metadata, registry);
}

上述源码中,registerBeanDefinitions方法内部调用了两个方法,分别是registerDefaultConfiguration()和registerFeignClients(),其中registerDefaultConfiguration()用于加载相关配置,registerFeignClients()用于扫描所有@FeignClient注解的接口。这里,我们重点查看registerFeignClients()方法内部实现细节,registerFeignClients()的源码如下所示。

 1     public void registerFeignClients(AnnotationMetadata metadata, 
 2                                            BeanDefinitionRegistry registry) {
 3       // 创建类路径的扫描组件
 4        ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
 5        scanner.setResourceLoader(this.resourceLoader);
 6      // 获取EnableFeignClients注解的信息
 7        Map<String, Object> attrs = 
 8        metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
 9        // 创建FeignClient注解的过滤器
 10      AnnotationTypeFilter annotationTypeFilter = new 
 11                    AnnotationTypeFilter(FeignClient.class);
 12     Class<?>[] clients = attrs == null ? null : 
 13     (Class[])((Class[])attrs.get("clients"));
 14        Object basePackages;
 15        if (clients != null && clients.length != 0) {
 16            final Set<String> clientClasses = new HashSet();
 17            basePackages = new HashSet();
 18            Class[] var9 = clients;
 19            int var10 = clients.length;
 20            for(int var11 = 0; var11 < var10; ++var11) {
 21                Class<?> clazz = var9[var11];
 22                ((Set)basePackages).add(ClassUtils.getPackageName(clazz));
 23                 clientClasses.add(clazz.getCanonicalName());
 24            }
 25           AbstractClassTestingTypeFilter filter = 
 26                              new AbstractClassTestingTypeFilter() {
 27           protected boolean match(ClassMetadata metadata) {
 28           String cleaned = metadata.getClassName().replaceAll("\\$", ".");
 29                   return clientClasses.contains(cleaned);
 30                }
 31          };
 32          scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(
 33          Arrays.asList(filter, annotationTypeFilter)));
 34          } else {
 35              scanner.addIncludeFilter(annotationTypeFilter);
 36              basePackages = this.getBasePackages(metadata);
 37       }
 38             Iterator var17 = ((Set)basePackages).iterator();
 39             while(var17.hasNext()) {
 40                 String basePackage = (String)var17.next();
 41                 Set<BeanDefinition> candidateComponents = 
 42                 scanner.findCandidateComponents(basePackage);
 43                 Iterator var21 = candidateComponents.iterator();
 44                while(var21.hasNext()) {
 45                BeanDefinition candidateComponent = 
 46                              (BeanDefinition)var21.next();
 47                if (candidateComponent instanceof AnnotatedBeanDefinition) {
 48                      AnnotatedBeanDefinition beanDefinition = 
 49                           (AnnotatedBeanDefinition)candidateComponent;
 50                      AnnotationMetadata annotationMetadata = 
 51                            beanDefinition.getMetadata();
 52                      Assert.isTrue(annotationMetadata.isInterface(), 
 53                     "@FeignClient can only be specified on an interface");
 54                      Map<String, Object> attributes = annotationMetadata.
 55           getAnnotationAttributes(FeignClient.class.getCanonicalName());
 56                      String name = this.getClientName(attributes);
 57                      this.registerClientConfiguration(registry, name, 
 58                                  attributes.get("configuration"));
 59                         this.registerFeignClient(registry, 
 60                                  annotationMetadata, attributes);
 61                     }
 62                 }
 63             }
 64         }

上述代码中,首先定义了一个基于classpath的组件扫描器,然后组件扫描器会根据指定的扫描位置和@EnableFeignClients注解属性找到开发人员定义的所有Feign客户端,也就是所有添加了@FeignClient注解的所有接口,最后将注册Feign客户端的动作交给registerFeignClient()方法完成。registerFeignClient()方法的源码如下所示。

private void registerFeignClient(BeanDefinitionRegistry registry, 
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
   // 获取使用@FeignClient注解修饰的类名
String className = annotationMetadata.getClassName();
//创建一个BeanDefinitionBuilder,指定待创建的Bean是FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder.
genericBeanDefinition(FeignClientFactoryBean.class);
this.validate(attributes);
definition.addPropertyValue("url", this.getUrl(attributes));
definition.addPropertyValue("path", this.getPath(attributes));
String name = this.getName(attributes);
definition.addPropertyValue("name", name);
String contextId = this.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(2);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = this.getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, 
className, new String[]{alias});
  // 动态注册
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

上述代码中,首先会将使用@FeignClient注解修饰的类名及其注解信息获取出来,赋值给BeanDefinitionBuilder,然后根据BeanDefinitionBuilder得到BeanDefinition,最后将BeanDefinition注入IoC容器。

当调用被@FeignClient修饰接口中的方法时,该方法会被SynchronousMethodHandler拦截处理,并生成一个RequestTemplate对象。SynchronousMethodHandler类中生成RequestTemplate对象的源码具体如下。

@Override
public Object invoke(Object[] argv) throws Throwable {
  RequestTemplate template= buildTemplateFromArgs.create(argv) ;
  Retryer retryer = this.retryer.clone();
  while(true) {
    try {
      return executeAndDecode(template) ;
    } catch(RetryableException e) {
      retryer.continueOrPropagate(e);
      if (logLevel!=Logger.Level.NONE) {
        logger.logRetry(metadata.configKey(),logLevel);
      }
      continue;
      }
     }
    }

上述代码中,executeAndDecode()方法会通过RequestTemplate 生成 Request对象。Request对象将交给Client处理,具体源码如下。

Object executeAndDecode(RequestTemplate template) throws Throwable {
  Request request= targetRequest(template) ;
    ...//省略代码
  response = client.execute(request,options);
    ...//省略代码

综上所述,Feign服务调用的工作原理可以总结为以下几个步骤:

(1)首先通过@EnableFeignClients注解开启FeignClient功能。程序启动时,会通过该注解开启对@FeignClient注解的包扫描。

(2)根据Feign规则实现接口,并在接口上面添加@FeignClient注解。

(3)程序启动后,会进行包扫描,扫描所有的@FeignClient注解类,并将这些信息注入IoC容器。

(4)当接口方法被调用时,通过JDK的代理生成具体的RequestTemplate模板对象。根据RequestTemplate再生成HTTP请求的Request对象,Request对象交给Client处理。

点击此处
隐藏目录