Spring Cloud Feign源码分析

96
数齐
2017.09.09 22:23* 字数 571

今天我们来说一下spring cloud 微服务之间远程调用组件feign。还是先从一个注解开始@EnableFeignClients,老样子引入了一个FeignClientsRegistrar,是一个注册器,看名字就知道,通过扫描某个特性的类注册成spring中的Bean。他的生命周期方法 registerBeanDefinitions

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   registerDefaultConfiguration(metadata, registry);  
   registerFeignClients(metadata, registry);
}

registerDefaultConfiguration注册 EnableFeignClients注解里面定义的defaultConfiguration属性我们着重要看的是 registerFeignClients

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);   //设置要扫描的注解为FeignClient
   final Class<?>[] clients = attrs == null ? null
         : (Class<?>[]) attrs.get("clients");   //查看EnableFeignClients注解有没有配置clients属性
   if (clients == null || clients.length == 0) {  //如果没有设置,那么加入要扫描的注解和扫描的包
      scanner.addIncludeFilter(annotationTypeFilter);
      basePackages = getBasePackages(metadata);
   }
   else {  //如果设置了,还需要加上一个判断条件,最终扫出来的Bean必须是注解中设置的那些
      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);   //查找候选的Bean
      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());   //查找FeignClient注解身上设置的属性

            String name = getClientName(attributes);
            registerClientConfiguration(registry, name,
                  attributes.get("configuration"));    //注册FeignClient的配置文件的信息

            registerFeignClient(registry, annotationMetadata, attributes);   //注册Bean
         }
      }
   }
}

设置扫描的注解为FeignClient。查看EnableFeignClients注解有没有配置clients属性,如果没有设置,那么加入要扫描的注解和扫描的包如果设置了,还需要加上一个判断条件,最终扫出来的Bean必须是注解中设置的那些.查找到候选的Bean后遍历注册FeignClient的配置文件的信息,注册FeignClient的实例。

private void registerFeignClient(BeanDefinitionRegistry registry,
      AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
   String className = annotationMetadata.getClassName();
   BeanDefinitionBuilder definition = BeanDefinitionBuilder
         .genericBeanDefinition(FeignClientFactoryBean.class);
   //logger.info("TEX do some replacement");
        //attributes.put("value", ((String)attributes.get("value")).replace('_','-'));
   validate(attributes);
   definition.addPropertyValue("url", getUrl(attributes));
   definition.addPropertyValue("path", getPath(attributes));
   String name = getName(attributes);
   definition.addPropertyValue("name", name);
   definition.addPropertyValue("type", className);
   definition.addPropertyValue("decode404", attributes.get("decode404"));
   definition.addPropertyValue("fallback", attributes.get("fallback"));
   definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

   String alias = name + "FeignClient";
   AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
   beanDefinition.setPrimary(true);
   BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
         new String[] { alias });
   BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

注册的时候无非是将注解中的信息设置进来。注意一点,生成的Bean是FeignClientFactoryBean的实例。既然是FactoryBean,那么我们就看一下他的getObject方法

@Override
public Object getObject() throws Exception {
   FeignContext context = applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);

   if (!StringUtils.hasText(this.url)) {
      String url;
      if (!this.name.startsWith("http")) {
         url = "http://" + this.name;
      }
      else {
         url = this.name;
      }
      url += cleanPath();
      return loadBalance(builder, context, new HardCodedTarget<>(this.type,
            this.name, url));
   }
   if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
      this.url = "http://" + this.url;
   }
   String url = this.url + cleanPath();
   return targeter.target(this, builder, context, new HardCodedTarget<>(
         this.type, this.name, url));
}

如果指定了URL那么久根据url来调用,相当于直连,那么久不需要负载均衡了。如果没有指定的话,就可以使用负载均衡,区别就在于client,使用ribbon提供的,需要引入spring-cloud-starter-ribbon的包

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
      HardCodedTarget<T> target) {
   Client client = getOptional(context, Client.class);
   if (client != null) {
      builder.client(client);
      return targeter.target(this, builder, context, target);
   }

   throw new IllegalStateException(
         "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?");  
}

至于targeter.target就是利用动态代理,创建出我们需要的对象,也就是factoryBean中getObject得到的Bean.

static class HystrixTargeter implements Targeter {

   @Override
   public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
                  HardCodedTarget<T> target) {
      if (factory.fallback == void.class
            || !(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
         return feign.target(target);
      }

      Object fallbackInstance = context.getInstance(factory.name, factory.fallback);
      if (fallbackInstance == null) {
         throw new IllegalStateException(String.format(
               "No fallback instance of type %s found for feign client %s",
               factory.fallback, factory.name));
      }

      if (!target.type().isAssignableFrom(factory.fallback)) {
         throw new IllegalStateException(
               String.format(
                     "Incompatible fallback instance. Fallback of type %s is not assignable to %s for feign client %s",
                     factory.fallback, target.type(), factory.name));
      }

      feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
      return builder.target(target, (T) fallbackInstance);
   }
}

上面主要描述了,Hystrix对于fallback情况的处理,如果我们没有设置fallback那么我们直接执行 feign.target(target),否则我们设置一下fallback,然后执行

builder.target(target, (T) fallbackInstance),我们先看一下普通的 feign.target(target)
public <T> T target(Target<T> target) {
  return build().newInstance(target);
}

public Feign build() {
  SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
      new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                           logLevel, decode404);
  ParseHandlersByName handlersByName =
      new ParseHandlersByName(contract, options, encoder, decoder,
                              errorDecoder, synchronousMethodHandlerFactory);
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
 
 
@SuppressWarnings("unchecked")
@Override
public <T> T newInstance(Target<T> target) {
  Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
  Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
  List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

  for (Method method : target.type().getMethods()) {
    if (method.getDeclaringClass() == Object.class) {
      continue;
    } else if(Util.isDefault(method)) {
      DefaultMethodHandler handler = new DefaultMethodHandler(method);
      defaultMethodHandlers.add(handler);
      methodToHandler.put(method, handler);
    } else {
      methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    }
  }
  InvocationHandler handler = factory.create(target, methodToHandler);
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

  for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}

当调用
build时我们创建了一个很有用的对象
handlersByName,主要是用来解析Feign接口中方法的。看一下
newInstance的第一行
targetToHandlersByName.apply(target)就是开始执行解析接口中的方法。

  public Map<String, MethodHandler> apply(Target key) {
    List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());  //key的type就是接口,根据接口找到里面的方法,利用注RequestMapping
    Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
    for (MethodMetadata md : metadata) {
      BuildTemplateByResolvingArgs buildTemplate;
      if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
        buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder);
      } else if (md.bodyIndex() != null) {
        buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);
      } else {
        buildTemplate = new BuildTemplateByResolvingArgs(md);
      }
      result.put(md.configKey(),
                 factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
    }
    return result;
  }
}

解析出每个feign接口中的方法放入map中返回,创建InvocationHandler时传入解析出来的方法,利用动态代理创建出代理对象。然后这个代理对象作为此接口所实现的Bean放入容器准备调用。

以上

spring cloud
Web note ad 1