feign【0.1】feign对象以及调用

前言

之前提到了:

  • feign的实例bean的注册流程,大致如下:
graph TB
EnableFeignClients-->import:FeignClientsRegistrar-->registerBeanDefinitions
registerBeanDefinitions-->注册configuration的beanDefinition到context中
registerBeanDefinitions-->扫描feignClient-->在context中注册为FeignClientFactoryBean的beanDefinition

这里我们继续看看,这里的feignClientFactoryBean是如何生成bean的,并通过实际情况来看看调用链路。

装配bean

首先,factoryBean是通过getObject来获取bean的,因此来看看这里的代码。

入口

<T> T getTarget() {
    //这里看到,feign的context是从容器中获取的
	FeignContext context = this.applicationContext.getBean(FeignContext.class);
	Feign.Builder builder = feign(context);

	if (!StringUtils.hasText(this.url)) {
		if (!this.name.startsWith("http")) {
			this.url = "http://" + this.name;
		}
		else {
			this.url = this.name;
		}
		this.url += cleanPath();
		return (T) loadBalance(builder, context,
				new HardCodedTarget<>(this.type, this.name, this.url));
	}
	if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
		this.url = "http://" + this.url;
	}
	String url = this.url + cleanPath();
	Client client = getOptional(context, Client.class);
	if (client != null) {
		if (client instanceof LoadBalancerFeignClient) {
			// not load balancing because we have a url,
			// but ribbon is on the classpath, so unwrap
			client = ((LoadBalancerFeignClient) client).getDelegate();
		}
		builder.client(client);
	}
	Targeter targeter = get(context, Targeter.class);
	return (T) targeter.target(this, builder, context,
			new HardCodedTarget<>(this.type, this.name, url));
}
复制代码

这里的feign(context,对应如下:)

protected Feign.Builder feign(FeignContext context) {
   FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
   Logger logger = loggerFactory.create(this.type);

   // @formatter:off
   Feign.Builder builder = get(context, Feign.Builder.class)
         // required values
         .logger(logger)
         .encoder(get(context, Encoder.class))
         .decoder(get(context, Decoder.class))
         .contract(get(context, Contract.class));
   // @formatter:on

   configureFeign(context, builder);

   return builder;
}
复制代码
graph LR
getObject-->getTarget-->从Spring容器中获取feignContext-->id{feign中配置的是否指定了URL}
从Spring容器中获取feignContext-->从context中获取Feign.Builder
id{feign中配置的是否指定了URL}-->负载均衡方式构造
id{feign中配置的是否指定了URL}-->非负载均衡方式构造

负载均衡方式

这里对应的就是loadBalance了:

graph LR
从context中获取client-->从context中获取target-->调用targter.target
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
      HardCodedTarget<T> target) {
   Client client = getOptional(context, Client.class);
   if (client != null) {
      builder.client(client);
      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-netflix-ribbon?");
}
复制代码

这里的client,是从context中获取的,这个是通过自动配置的地方注入的。

同理,这里的target,也是通过相同的方式注入的。

总的来说,这部分和前面分支上的指定URL的模式是类似的,都是获取client和target,最后调用target.target()构造最后返回的实例。

非负载均衡方式

其实这里的流程都是一样的。

graph LR
从context中获取client-->从context中获取target-->调用targter.target

这里对应如下:

//feignClientFactoryBean中
//.....
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
}
复制代码

可以观察到核心代码都差不多,只是这里获取的client方式有所不同。

这两个target.target的入参都有一个new HardCodedTarget<>(this.type, this.name, url),这里暂时没看出来作用,后面再说。

核心1:target.target

graph LR
id1{是否HystrixFeign}-->N-->直接调用feign.target构造返回实例
id1{是否是HystrixFeign}-->Y:配置失败的对应请求逻辑-->直接调用feign.target构造返回实例

根据下面自动配置上对应的bean,可知这里我们对应的target是HystrixTargeter,这里的Builder,是原生Feign.builder的。直接进到对应代码:

public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
      FeignContext context, Target.HardCodedTarget<T> target) {
   if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
      return feign.target(target);
   }
    //【1】
   feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
   String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
         : factory.getContextId();
   SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
   if (setterFactory != null) {
      builder.setterFactory(setterFactory);
   }
   Class<?> fallback = factory.getFallback();
   if (fallback != void.class) {
      return targetWithFallback(name, context, target, builder, fallback);
   }
   Class<?> fallbackFactory = factory.getFallbackFactory();
   if (fallbackFactory != void.class) {
      return targetWithFallbackFactory(name, context, target, builder,
            fallbackFactory);
   }

   return feign.target(target);
}
复制代码

这里我们直接看feign.target()。

feign.target

其实这里就是一些简单的构造方法式的值注入,最终返回的是ReflectiveFeign

public <T> T target(Target<T> target) {
  return build().newInstance(target);
}

//实例是Feign,因此对应的build如下
    public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }

复制代码

如果这里是HystrixFeign,那么build对应的如下:

public Feign build() {
  return build(null);
}

/** Configures components needed for hystrix integration. */
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
  super.invocationHandlerFactory(new InvocationHandlerFactory() {
    @Override
    public InvocationHandler create(Target target,
                                    Map<Method, MethodHandler> dispatch) {
      return new HystrixInvocationHandler(target, dispatch, setterFactory,
          nullableFallbackFactory);
    }
  });
  super.contract(new HystrixDelegatingContract(contract));
  return super.build();
}
复制代码

也就是说会额外设置一个invocationHandlerFactorycontract

ReflectiveFeign.newInstance

到这里就算最后返回的值了。

  • 这里算是返回了一个hardCodeTarget,其中的方法受defaultMethodHandlers代理,类受InvocationHandler代理(这里一般是FeignInvocationHandler)。
  • 这里的targetToHandlersByName是前面的ParseHandlersByName,apply是封装方法并塞一个Template进去,实际返回的实例类是SynchronousMethodHandler
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;
}
复制代码

feign调用链路

前面搞清楚了,如果要trace一个feign请求,那么首先会进入FeignInvocationHandler,因此从这里开始看调用链路。

首先方法调用都会到invoke这里:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if ("equals".equals(method.getName())) {
    try {
      Object otherHandler =
          args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
      return equals(otherHandler);
    } catch (IllegalArgumentException e) {
      return false;
    }
  } else if ("hashCode".equals(method.getName())) {
    return hashCode();
  } else if ("toString".equals(method.getName())) {
    return toString();
  }

  return dispatch.get(method).invoke(args);
}
复制代码

这里可以看到其实具体的feign调用是这里dispatch.get(method).invoke(args),而这里的dispatch就是上面我们提到的MethodHandlerSynchronousMethodHandler):

public Object invoke(Object[] argv) throws Throwable {
    //这里的template,就是前面ParseHandlersByName的apply中设置进去的,这里就是通过设置argv来设置参数,并返回对应的template
  RequestTemplate template = buildTemplateFromArgs.create(argv);
    //这里的options,是在前面feign.Builder().Option中写进去的,这里的都是一些连接时间限制的数值配置
    //这里会取外部传进来的参数,如果没有options的话,就用这个方法代理类上的
  Options options = findOptions(argv);
  Retryer retryer = this.retryer.clone();
  while (true) {
    try {
      return executeAndDecode(template, options);
    } catch (RetryableException e) {
      try {
        retryer.continueOrPropagate(e);
      } catch (RetryableException th) {
        Throwable cause = th.getCause();
        if (propagationPolicy == UNWRAP && cause != null) {
          throw cause;
        } else {
          throw th;
        }
      }
      if (logLevel != Logger.Level.NONE) {
        logger.logRetry(metadata.configKey(), logLevel);
      }
      continue;
    }
  }
}
复制代码

可以看到这里的核心就是这个executeAndDecode,重试的核心就是这里的循环以及retryer,我们先看executeAndDecode,这个方法直接做了请求:

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    //这里会去获取请求具体的信息,此时要注意:如果我们前面的是负载均衡方式构建的,那么这里的url还是负载均衡方式的url
  Request request = targetRequest(template);

  if (logLevel != Logger.Level.NONE) {
    logger.logRequest(metadata.configKey(), logLevel, request);
  }

  Response response;
  long start = System.nanoTime();
  try {
      //这里就是核心了,使用上面注入的client
    response = client.execute(request, options);
  } catch (IOException e) {
    if (logLevel != Logger.Level.NONE) {
      logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
    }
    throw errorExecuting(request, e);
  }
  long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

  boolean shouldClose = true;
  try {
    if (logLevel != Logger.Level.NONE) {
      response =
          logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
    }
    if (Response.class == metadata.returnType()) {
      if (response.body() == null) {
        return response;
      }
      if (response.body().length() == null ||
          response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
        shouldClose = false;
        return response;
      }
      // Ensure the response body is disconnected
      byte[] bodyData = Util.toByteArray(response.body().asInputStream());
      return response.toBuilder().body(bodyData).build();
    }
    if (response.status() >= 200 && response.status() < 300) {
      if (void.class == metadata.returnType()) {
        return null;
      } else {
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      }
    } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
      Object result = decode(response);
      shouldClose = closeAfterDecode;
      return result;
    } else {
      throw errorDecoder.decode(metadata.configKey(), response);
    }
  } catch (IOException e) {
    if (logLevel != Logger.Level.NONE) {
      logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
    }
    throw errorReading(request, response, e);
  } finally {
    if (shouldClose) {
      ensureClosed(response.body());
    }
  }
}
复制代码

那么我们就看看这个client.execute(对应类为LoadBalancerFeignClient【2】):

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);
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);

			IClientConfig requestConfig = getClientConfig(options, clientName);
			return lbClient(clientName)
					.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
		}
		catch (ClientException e) {
			IOException io = findIOException(e);
			if (io != null) {
				throw io;
			}
			throw new RuntimeException(e);
		}
	}

//这里的是自动注入的地方就带上的
private CachingSpringLoadBalancerFactory lbClientFactory;
	private FeignLoadBalancer lbClient(String clientName) {
		return this.lbClientFactory.create(clientName);
	}
复制代码

然后就到了这个executeWithLoadBalancer,这里就到Spring中的loadBalancer部分中了。

  • 在这里就会最终决定,选择哪个URL进行请求。
//AbstractLoadBalancerAwareClient
    public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

        try {
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            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);
            }
        }
        
    }

复制代码

AutoConfiguration中导入的bean

从context中获取的:

  • Feign.builder
  • client
  • target

feign.builder

这里的builder有两个,在TraceFeignClientAutoConfiguration中进行配置的(包):

@Bean
@Scope("prototype")
@ConditionalOnClass(
      name = { "com.netflix.hystrix.HystrixCommand", "feign.hystrix.HystrixFeign" })
@ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "true")
Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) {
   return SleuthHystrixFeignBuilder.builder(beanFactory);
}

@Bean
@ConditionalOnMissingBean
@Scope("prototype")
@ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "false",
      matchIfMissing = true)
Feign.Builder feignBuilder(BeanFactory beanFactory) {
   return SleuthFeignBuilder.builder(beanFactory);
}
复制代码

之所以指定为原型bean,理由也很简单: 大部分feign构造最终实例并注入都会调用这里,而这里要做不同bean之间的隔离。

这里的sleuth是Spring自带的链路追踪;这里的builder的区别其实是:上面这个是一个代理类HystrixFeign,而下面的是直接调用Feign.builder构建的实例。

在上面的案例中,我们没有配对应的参数,因此获取到的是第二个,也就是说是原生的而非是代理的。

client

这里的client对应的bean有三个,在FeignLoadBalancerAutoConfiguration中import:

@Import({ HttpClientFeignLoadBalancerConfiguration.class,
      OkHttpFeignLoadBalancerConfiguration.class,
      DefaultFeignLoadBalancerConfiguration.class })
class FeignLoadBalancerAutoConfiguration {

}
复制代码

看名字也知道:

  • 第一个对应的是如果导入包中带了ApacheHttpClient,使用之;
  • 第二个对应的是OKHTTP
  • 第三个是默认兜底的

具体到实现上,这几个类最终返回的实例都是FeignBlockingLoadBalancerClient

target

这里的target有两个,分别对应包路径下是否有feign.hystrix.HystrixFeign:

//feignAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public Targeter feignTargeter() {
      return new HystrixTargeter();
   }

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public Targeter feignTargeter() {
      return new DefaultTargeter();
   }

}
复制代码

集成的对应类

  • sleuth
  • 【1】失败处理

@TODO

  • 【2】哪里判断的?

猜你喜欢

转载自juejin.im/post/7037090107597258759