Feign

Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端。我们只需要声明接口和一些简单的注解,就能像使用普通的Bean一样调用远程服务。它具备可插拔的注解支持,包括Feign注解、JAX-RS注解。它也支持可插拔的编码器和解码器。Spring Cloud Feign还扩展了对Spring MVC注解的支持,同时还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。
使用Feign需要增加的maven依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

主应用类增加@EnableFeignClients开启扫描Spring Cloud Feign客户端功能:

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaConsumerFeignApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaConsumerFeignApplication.class, args);
	}

}

创建Feign Client定义, 使用@FeignClient注解来定义Feign Client并定义该Feign客户端需要调用的微服务名,之后定义各个方法是调用微服务的哪个接口:

@FeignClient("eureka-client")
public interface DcClient {

    @GetMapping("/ls")
    String consumer();

}

调用:

@RestController
public class FeignController {

    @Autowired
    DcClient dcClient;
    
    @GetMapping("/feign/consumer")
    public String ls() {
        return dcClient.consumer();
    }
}

通过@FeignClient定义的接口来统一的声明需要依赖的微服务接口。而在具体使用的时候就跟调用本地方法一样地进行调用即可。由于Feign是基于Ribbon实现的,所以它自带了客户端负载均衡功能,也可以通过Ribbon的IRule接口进行策略扩展。另外,Feign还整合的Hystrix来实现服务的容错保护,在Dalston版本中,Feign的Hystrix默认是关闭的。

@EnableFeignClients开启feign

@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 {};
}

这个注解导入了一个类FeignClientsRegistrar,这个类实现了ImportBeanDefinitionRegistrar接口,该接口用于向Bean容器中注册添加BeanDefinition。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
public interface ImportBeanDefinitionRegistrar {
    void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}

跟进FeignClientsRegistrar的registerBeanDefinitions方法,看看它注册了哪些东西。

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 注册默认配置
    this.registerDefaultConfiguration(metadata, registry);
    // 注册FeignClient接口的Bean
    this.registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    Set<String> basePackages;

    // 获取扫描路径

    // 扫描所有路径,默认情况下扫描启动类下的路径
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
        // 遍历扫描到的FeignClient的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());

                String name = getClientName(attributes);
                // 注册FeignClient的配置
                registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 注册FeignClient
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

方法中的核心逻辑就是扫描类路径,获取BeanDefinition(@FeignClient),然后遍历进行注册。

跟进registerFeignClient注册方法

private void registerFeignClient(BeanDefinitionRegistry registry,
      AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
   String className = annotationMetadata.getClassName();
   // 生成FeignClientFactoryBean这个BeanDefinition构造器
   // FeignClientFactoryBean.class
   BeanDefinitionBuilder definition = BeanDefinitionBuilder
         .genericBeanDefinition(FeignClientFactoryBean.class);
   validate(attributes);
   // 将@FeignClient的注解属性添加到builder
   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.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
   definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

   String alias = name + "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);
}

这里值得注意的是genericBeanDefinition方法最终生成的其实是FeignClientFactoryBean,registerBeanDefinition方法注册进容器的也是FeignClientFactoryBean。

从名字就可以看出FeignClientFactoryBean是个Factorybean,实现了FactoryBean接口

FactoryBean接口是spring开放出来的,用于自定义Bean的生成过程。也就是说,spring将会通过调用FeignClientFactoryBean的getObject来获取@FeignClient注解的接口对应的Bean对象。

总结

openfeign的自动配置过程逻辑相对比较简单,就是扫描了一下@FeignClient注解的接口,然后生成FeignClientFactoryBean的BeanDefinition给注册到容器当中。而具体的Bean对象,将会通过调用FeignClientFactoryBean的getObject方法来获取。

生成proxy对象

从FeignClientFactoryBean的getObject方法开始,看看代理对象的生成。跟进getObject方法

@Override
public Object getObject() throws Exception {
   // 获取一个上下文
   FeignContext context = applicationContext.getBean(FeignContext.class);
   // feign用于构造代理对象,builder将会构建feign
   Feign.Builder builder = feign(context);

   if (!StringUtils.hasText(this.url)) {
      String url;
      // 拼接URL地址,如:http://service-provider/
      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));
   }
   // ....... 省略 
}  
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
      HardCodedTarget<T> target) {
   // 获取执行http请求的客户端
   Client client = getOptional(context, Client.class);
   if (client != null) {
      builder.client(client);
      // 选择获取代理对象的实现类,默认是HystrixTargeter
      Targeter targeter = get(context, Targeter.class);
      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的实现类处理

interface Targeter {
   <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
             Target.HardCodedTarget<T> target);
}

默认是HystrixTargeter

class HystrixTargeter implements Targeter {

   @Override
   public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
                  Target.HardCodedTarget<T> target) {
      
      if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
         // Feign实现了构造代理对象的过程,所以这里将会回调feign的构造过程方法
         return feign.target(target);
      }
      
      // ...... 省略

      return feign.target(target);
   }
   // ...... 省略
}  
public <T> T target(Target<T> target) {
    return this.build().newInstance(target);
}

public Feign build() {
    // ......
    return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory);
}

跟进newInstance方法,看看代理对象是如何被构建的

/**
 * creates an api binding to the {@code target}. As this invokes reflection, care should be taken
 * to cache the result.
 */
@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>();
  // 构建Method到MethodHandler的映射关系
  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)));
    }
  }
  // factory.create() 实现 invocationHandler
  InvocationHandler handler = factory.create(target, methodToHandler);
  // jdk的动态代理获取的代理对象
  T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);

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

代理对象的构建主要由三块内容

1、构建Method到MethodHandler的映射关系,后面调用代理的对象的时候将会根据Method找到MethodHandler然后调用MethodHandler的invoke方法,而MethodHandler将包含发起http请求的实现。

2、jdk动态代理需要提供InvocationHandler,这个大家比较熟悉了。而InvocationHandler将由InvocationHandlerFactory的create方法实现

3、通过Proxy.newProxyInstance方法,生成proxy对象。

这里我们看看factory.create方法生成InvocationHandler的实现吧

static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      // 这是一个内部类的实现 FeignInvocationHandler
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }

调用proxy对象发起http请求

我们知道,jdk的动态代理将会调用FeignInvocationHandler的invoke方法。所以,我们看看FeignInvocationHandler是怎么调用Method的

private final Map<Method, MethodHandler> dispatch;

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // ...

  return dispatch.get(method).invoke(args);
}

前面我们说到,构建proxy对象的时候会构建Method和MethodHandler的映射关系。而这里invoke代理对象的时候又会根据method来获取到MethodHandler,再调用其invoke方法。

MethodHandler的默认实现类是SynchronousMethodHandler,我们跟进它的invoke方法

// feign.SynchronousMethodHandler
public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        // executeAndDecode
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        // ...
      }
    }
  }

熟悉的代码来了,executeAndDecode将会负责发起http请求

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);

    Response response;

    try {
      // 执行http请求
      response = client.execute(request, options);
    } catch (IOException e) {
      
    }

    try {
      //...

      // http请求成功
      if (response.status() >= 200 && response.status() < 300) {
          // 无需返回值
        if (void.class == metadata.returnType()) {
          return null;
        } else {
          // 解码结果
          Object result = decode(response);

          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        // ...
      } else {
        // ...
      }
    } catch (IOException e) {
      // ...
    } finally {
      // ...
    }
  }

总结

openFeign生成@FeignClient注解的接口的代理对象是从FeignClientFactoryBean的getObject方法开始的,生成proxy对象主要由ReflectiveFeign对象来实现。动态代理方法由jdk原生的动态代理支持。

调用proxy对象,其实就是发起http请求,请求结果将被解码并返回。

所以,正如Feign本身的意义一样,http远程调用被伪装成了本地调用一样简单的代理对象,对于使用者来说就是调用本地接口一样简单。

ribbon如何集成在openfeign中使用

ribbon是springcloud封装的一个基于http客户端负载均衡的组件。springcloud的openfeign集成使用了ribbon。所以如果你使用openfeign,那么也会很轻易得使用到ribbon。ribbon是负载均衡的组件,所以使用在http的请求之中。

参考之前的executeAndDecode方法, client提交了一个http的request,然后获得了response响应对象,处理后并返回。ribbon的接入将从这里开始,我们看看client接口

public interface Client {
  
  Response execute(Request request, Options options) throws IOException;  
  
}

LoadBalancerFeignClient作为Client在负载均衡方面的实现类

@Override
public Response execute(Request request, Request.Options options) throws IOException {
   try {
      URI asUri = URI.create(request.url());
      String clientName = asUri.getHost();
      URI uriWithoutHost = cleanUrl(request.url(), clientName);
      // 构造ribbon的request对象
      FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
            this.delegate, request, uriWithoutHost);

      IClientConfig requestConfig = getClientConfig(options, clientName);
      // 执行ribbon的request对象
      return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
            requestConfig).toResponse();
   }
   catch (ClientException e) {
      // ......
   }
}

可以看到,关于负载均衡方面openfeign直接构造了ribbon的请求,并执行。

构造ribbon的IClient

lbClient(clientName)构造了一个ribbon的http客户端实现,打开该方法

private FeignLoadBalancer lbClient(String clientName) {
   return this.lbClientFactory.create(clientName);
}

一个简单工厂模式,跟进create方法

public FeignLoadBalancer create(String clientName) {
   if (this.cache.containsKey(clientName)) {
      return this.cache.get(clientName);
   }
   IClientConfig config = this.factory.getClientConfig(clientName);
   ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
   ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
   // 默认返回FeignLoadBalancer
   FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
         loadBalancedRetryPolicyFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
   this.cache.put(clientName, client);
   return client;
}

看一下FeignLoadBalancer的继承结构

FeignLoadBalancer实现了IClient接口,所以它会负责提交并执行Ribbon的request请求

lbClient方法构建了FeignLoadBalancer,下面该调用它的executeWithLoadBalancer方法了,跟进方法(方法在父类AbstractLoadBalancerAwareClient中)

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
    LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
            .withLoadBalancerContext(this)
            .withRetryHandler(handler)
            .withLoadBalancerURI(request.getUri())
            .build();

    try {
        return command.submit(
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    // 回调返回选择好的Server对象,并重新构造uri地址
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        // 执行ribbon的request请求
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    } 
                    catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            })
            .toBlocking()
            .single();
    } catch (Exception e) {
        Throwable t = e.getCause();
        if (t instanceof ClientException) {
            throw (ClientException) t;
        } else {
            throw new ClientException(e);
        }
    }
    
}

如果不关心负载均衡的情况,这里其实就是直接执行ribbon的request请求了。也就是IClient这个接口类定义的内容。不过FeignLoadBalancer需要进行一次负载选择Server,然后才回调这里的call来发起请求。

跟进submit方法看看,submit方法先是进行了一次选择获得了一个Server对象,然后回调了上面说的ribbon的request执行

public Observable<T> submit(final ServerOperation<T> operation) {
        // ...

        // Use the load balancer
        Observable<T> o = 
                // 选择Server
                (server == null ? selectServer() : Observable.just(server))
                .concatMap(new Func1<Server, Observable<T>>() {
                    @Override
                    // Called for each server being selected
                    public Observable<T> call(Server server) {
                        context.setServer(server);
                        //
                        
                        // Called for each attempt and retry
                        Observable<T> o = Observable
                                .just(server)
                                .concatMap(new Func1<Server, Observable<T>>() {
                                    @Override
                                    public Observable<T> call(final Server server) {
                                        // ...
                                        // 回调ribbon的request请求
                                        return operation.call(server).doOnEach(
                                            // ...
                                        );
                                    }
                                });
                        
                        if (maxRetrysSame > 0) 
                            o = o.retry(retryPolicy(maxRetrysSame, true));
                        return o;
                    }
                });
            
        // ...
    }

ILoadBalancer负载均衡器

继续跟进selectServer,看看如何选择服务的

private Observable<Server> selectServer() {
    return Observable.create(new OnSubscribe<Server>() {
        @Override
        public void call(Subscriber<? super Server> next) {
            try {
                // 从上下文中获取
                Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                next.onNext(server);
                next.onCompleted();
            } catch (Exception e) {
                next.onError(e);
            }
        }
    });
}

托付给了getServerFromLoadBalancer来实现,继续跟进

public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
    // ...

    ILoadBalancer lb = getLoadBalancer();
    if (host == null) {
        if (lb != null){
            Server svc = lb.chooseServer(loadBalancerKey);
            // ...

            return svc;
        } else {
            // ...
        }
    } else {
        // ...
    }
    
    // ...
}

getLoadBalancer方法先是获取了一个ILoadBalancer接口的实现,然后调用了chooseServer来选择一个Server。

先跟进getLoadBalancer方法,直接返回了上下文中的设置的ILoadBalancer负载均衡器

private ILoadBalancer lb;

public ILoadBalancer getLoadBalancer() {
    return lb;    
}

我们看一下ILoadBalancer的的类图,RibbonClientConfiguration配置ILoadBalancer的时候配置的是ZoneAwareLoadBalancer的Bean

IRule负载均衡算法

有了ILoadBalancer负载均衡器,再看看chooseServer方法。这里忽略一些细节,直接看BaseLoadBalancer的chooseServer这个核心的实现

// com.netflix.loadbalancer.BaseLoadBalancer

protected IRule rule = DEFAULT_RULE;

public Server chooseServer(Object key) {
    if (counter == null) {
        counter = createCounter();
    }
    counter.increment();
    if (rule == null) {
        return null;
    } else {
        try {
            return rule.choose(key);
        } catch (Exception e) {
            logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
            return null;
        }
    }
}

可以看看,直接调用了IRule接口的choose方法。IRule接口则负责相关的负载均衡算法实现,我们看看IRule接口有哪些实现吧

常见的随机算法、轮询算法...等

如何定制给Ribbon配置算法,或者定制算法呢??

到这里,本文就结束了。我们再回顾一下文章的接口和流程

1、先是openfeign开放了一个Client接口用于http请求,并且LoadBalancerFeignClient作为负载均衡的实现类

2、LoadBalancerFeignClient则直接构造了一个ribbon的IClient接口的实现FeignLoadBalancer

3、执行ribbon的request之前,先委托ILoadBalancer负载均衡器选择一个Server,然后回调执行request请求

4、ILoadBalancer会选择IRule实现的负载均衡算法来获取一个Server,并返回。

总体逻辑比较简单,本文忽略了一些细节内容,比如一些自动配置的东西、如果从Eureka中获取服务列表等,有兴趣可以自己看看。

hystrix如何集成在openfeign中使用

posted @ 2020-06-02 20:40  江舟  阅读(312)  评论(0)    收藏  举报