preface

Mentioned earlier:

  • Feign instance bean registration process, roughly as follows:
graph TB EnableFeignClients-->import:FeignClientsRegistrar-->registerBeanDefinitions RegisterBeanDefinitions --> Register the beanDefinition of the Configuration into the context RegisterBeanDefinitions --> Scan feignClient--> beanDefinition registered as FeignClientFactoryBean in context

Here we continue to look at how the feignClientFactoryBean here generates the bean and see the call link in action.

Assembly bean

First, factoryBeans get beans through getObject, so take a look at the code here.

The entrance

<T> T getTarget(a) {
    // As you can see here, the feign context is fetched from the container
	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));
}
Copy the code

Here feign(context, corresponding to 🙂

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;
}
Copy the code
Graph LR getObject-->getTarget--> Get feignContext from Spring -->id Feign.Builder id{Feign = URL}--> load balancing constructor id{Feign = URL}--> Load balancing constructor id{Feign = URL}--> Load balancing constructor

Load balancing mode

LoadBalance = loadBalance

The graph LR gets the client from the context --> gets the target from the context --> calls targter. Target
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget
       
         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?");
}
Copy the code

The client here, we get it from the context, and this is injected through the auto-configuration place.

Similarly, target here is injected in the same way.

In general, this part is similar to the pattern of the specified URL on the previous branch, fetching the client and target, and then calling target.target() to construct the returned instance.

Non-load balancing mode

In fact, the process is the same here.

The graph LR gets the client from the context --> gets the target from the context --> calls targter. Target

Here is the corresponding:

/ / 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));
}
Copy the code

You can see that the core code is pretty much the same, but the way you get the client here is different.

Both target.target inputs have a new HardCodedTarget<>(this.type, this.name, url).

Core 1: target.target

Graph LR id1{HystrixFeign}-->N--> call feign.target directly to return instance id1{HystrixFeign -->Y: request logic for configuration failure --> call feign.target directly to return instance

HystrixTargeter = HystrixTargeter; Builder = HystrixTargeter; Builder = HystrixTargeter; Go straight to the corresponding code:

public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget
       
         target)
        {
   if(! (feigninstanceof 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) {
      returntargetWithFallback(name, context, target, builder, fallback); } Class<? > fallbackFactory = factory.getFallbackFactory();if(fallbackFactory ! =void.class) {
      return targetWithFallbackFactory(name, context, target, builder,
            fallbackFactory);
   }

   return feign.target(target);
}
Copy the code

Here we go straight to feign.target().

feign.target

It’s just some simple construction-style value injection that returns ReflectiveFeign.

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

// The instance is Feign, so the corresponding build is as follows
    public Feign build(a) {
      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);
    }

Copy the code

If this is HystrixFeign, then build would look like this:

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

/** Configures components needed for hystrix integration. */
Feign build(finalFallbackFactory<? > nullableFallbackFactory) {
  super.invocationHandlerFactory(new InvocationHandlerFactory() {
    @Override
    public InvocationHandler create(Target target, Map
        
          dispatch)
        ,> {
      return newHystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); }});super.contract(new HystrixDelegatingContract(contract));
  return super.build();
}
Copy the code

That is, an additional invocationHandlerFactory and contract will be set.

ReflectiveFeign.newInstance

That’s what I’m going to return.

  • This returns a hardCodeTarget, where methods are represented by defaultMethodHandlers and classes by InvocationHandler (typically FeignInvocationHandler).
  • Where targetToHandlersByName is the previous ParseHandlersByName, apply encapsulates the method and inserts a Template into it, and the actual instance class returned is 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(),
      newClass<? >[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}
Copy the code

Feign invokes the link

We made it clear that if we were to trace a Feign request, we would first go to the FeignInvocationHandler, so we would look at the call link from there.

First method calls go to 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);
}
Copy the code

Get (method).invoke(args) and the dispatch is the MethodHandler (SynchronousMethodHandler) we mentioned above:

public Object invoke(Object[] argv) throws Throwable {
    // Set argv to template by applying ParseHandlersByName
  RequestTemplate template = buildTemplateFromArgs.create(argv);
    Feign.builder ().option (); // The options are set in feign.builder ().option
    // This method takes the parameters passed in from the class. If there are no options, this method is used to proxy the class
  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 {
          throwth; }}if(logLevel ! = Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); }continue; }}}Copy the code

You can see that the core here is this executeAndDecode, and the core of retries is this loop and retryer, so let’s look at executeAndDecode, and this method directly asks:

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Note: if the previous url is load-balanced, then this url is load-balanced
  Request request = targetRequest(template);

  if(logLevel ! = Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response;long start = System.nanoTime();
  try {
      // Here is the core, using the client injected above
    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;
        returnresult; }}else if (decode404 && response.status() == 404 && void.class ! = metadata.returnType()) { Object result = decode(response); shouldClose = closeAfterDecode;return result;
    } else {
      throwerrorDecoder.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()); }}}Copy the code

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 newRuntimeException(e); }}// Here is the automatic injection place with the belt
private CachingSpringLoadBalancerFactory lbClientFactory;
	private FeignLoadBalancer lbClient(String clientName) {
		return this.lbClientFactory.create(clientName);
	}
Copy the code

And then we have this executeWithLoadBalancer, which is now in the loadBalancer section of Spring.

  • This is where the final decision will be made, which URL to choose for the request.
//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 newClientException(e); }}}Copy the code

Beans imported in AutoConfiguration

Obtained from the context:

  • Feign.builder
  • client
  • target

feign.builder

The builder here has two, configured in TraceFeignClientAutoConfiguration (package) :

@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);
}
Copy the code

The reason for the designation of a prototype bean is simple: this is where most feign constructs the final instance and injection is called, and where the isolation between different beans is done.

Sleuth here is Spring’s own link tracing; The difference here is that the top one is a proxy class HystrixFeign, while the bottom one is an instance built by calling feign.Builder directly.

In the above case, we don’t have the matching argument, so we get the second one, which is native rather than proxy.

client

Here the client of the corresponding bean has three, in FeignLoadBalancerAutoConfiguration import:

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

See the name also know:

  • If ApacheHttpClient is included in the import package, use it.
  • The second counterpart is OKHTTP
  • The third is the default bottom pocket

Specific to the implementation, these a few class instances are FeignBlockingLoadBalancerClient eventually return.

target

The target here has two, respectively corresponding to the package path if there were any feign. Hystrix. HystrixFeign:

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

   @Bean
   @ConditionalOnMissingBean
   public Targeter feignTargeter(a) {
      return newHystrixTargeter(); }}@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {

   @Bean
   @ConditionalOnMissingBean
   public Targeter feignTargeter(a) {
      return newDefaultTargeter(); }}Copy the code

Corresponding classes for integration

  • sleuth
  • [1] Failure handling

@TODO

  • [2] Where do you judge?