对于不熟悉的人来说,源代码一般是庞大而“杂乱”的,要阅读源代码需要先找到入口点。

而openfeign的入口点在哪呢?当在项目中使用openfeig的时候是通过@EnableFeignClients注解来开启Openfeign,所以这个入口点就是@EnableFeignClients注解:

OpenFeign引入maven openfeign源码分析_spring

可以看到其和普通的注解没啥大的不同,除了@Import(FeignClientsRegistrar.class)注解,很显然,@EnableFeignClients注解的核心便是@Import注解。当在某个spring配置类或豆子上标记@EnableFeignClients注解时,相当于通过@import注解同时注册了FeignClientsRegistrar这个豆子----实际上,FeignClientsRegistrar并不是豆子,因为其实现了ImportBeanDefinitionRegistrar这个接口,然后会在registerBeanDefinitions()方法中手动注册豆子来代替FeignClientsRegistrar。总之现在关注点来到了FeignClientsRegistrar。

OpenFeign引入maven openfeign源码分析_OpenFeign引入maven_02

可以看到, FeignClientsRegistrar类实现了3个接口第一个用来手动注册豆子的,后两个接口是典型的Aware系列接口,可以注入对应的豆子,但这里有个疑问是,FeignClientsRegistrar并不是豆子为何还能注入?而且经过我的试验,这里如果使用ApplicationContextAware接口便不能成功,有待后面研究。于是现在关注点来到了ImportBeanDefinitionRegistrar接口的registerBeanDefinitions()方法

通过方法名能够猜测这里是先注册默认配置,再注册FeignClients,先看看怎么注册默认配置的

OpenFeign引入maven openfeign源码分析_初始化_03

 这里遇到了AnnotationMetadata类,顾名思义,注解元数据,即被@import注解了的类。这里便是通过该类获取到了被@import注解的类的另外一个注解---@EnableFeignClients的属性(@import注解是放在@EnableFeignClients注解上,然后@EnableFeignClients注解再放到某个配置类上的,所以@import注解便间接地放到了该配置类上)。接下来实际上是设置要注册地豆子地名称,跳过,直接进入registerClientConfiguration方法

OpenFeign引入maven openfeign源码分析_spring_04

 没啥说的,通过BeanDefinitionBuilder创建FeignClientSpecification的BeanDefinition,然后直接注册该豆子,名字为default.全类名.FeignClientSpecification,到这里注册默认配置类结束。回到

进入下一个方法

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

这个方法很长,很显然从这里便进入了OpenFeign初始化时的核心代码,即为每个FeignClient注册豆子。要注册豆子就得先找到该类的包路径,于是

先获取@EnableFeignClients注解的所有属性,然后优先检验clients属性的值,若不为空,则只会扫描clients属性指定的类的包路径,而不会去扫描被@feignClient注解了的类了。也就是说若clients设置了feignclient会使@feignClient注解失效!!!

若clients属性的值为空,才会去根据指定的包路径扫描被@feignClient注解了的类。这里的包路径是一个set集合,包含了@EnableFeignClients注解的value、basePackages、basePackagesClasses属性的值,若都为空,则使用被@EnableFeignClients注解的类的同级路径。代码如下:

protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
   Map<String, Object> attributes = importingClassMetadata
         .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());

   Set<String> basePackages = new HashSet<>();
   for (String pkg : (String[]) attributes.get("value")) {
      if (StringUtils.hasText(pkg)) {
         basePackages.add(pkg);
      }
   }
   for (String pkg : (String[]) attributes.get("basePackages")) {
      if (StringUtils.hasText(pkg)) {
         basePackages.add(pkg);
      }
   }
   for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
   }

   if (basePackages.isEmpty()) {
      basePackages.add(
            ClassUtils.getPackageName(importingClassMetadata.getClassName()));
   }
   return basePackages;
}

 找出包路径后,便开始扫描,这里使用了一个双重循环来扫描每个包路径下的每个类,并进行了一些判断,最终只有被@FeignClient注解了的接口留下来,然后开始注册,要注册就得有名字,所以先决定名字

OpenFeign引入maven openfeign源码分析_初始化_05

  很显然,名字的优先级从高到低为contextId、value、name、serviceId,若都为空则抛出异常。

找到名字后终于开始注册了

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

显然,注册的豆子并不是被@feignclient注解的类,而是FeignClientFactoryBean,这里涉及到了spring中的FactoryBean,顾名思义就是工厂豆子,这里是hi机上是注册了被@feignclient注解的类的工厂豆子,当要再使用该类的地方注入时,spring会通过对应的工厂豆子的getObject()方法得到要使用的对象。总之factorybean就是一个简单工厂,我们定义好该工厂,要注入时spring会自动从该工厂获取豆子。

这里进行了一些琐碎的操作过后终于结束了openfeign的初始化。

总结一下就是:

当spring初始化时,会经过@EnableFeignClients注解上的@Import(FeignClientsRegistrar.class)注解进入FeignClientsRegistrar类的registerBeanDefinitions方法,之后便会根据注解的属性值找到feign clients们,最后为其注册FeignClientFactoryBean工厂豆子。

接下来就是feignclients的依赖注入……