[Enjoy Feign]4. Detailed core API for native Feign: SynchronousMethodHandler, Contract...

Interview questions, written exams, the points of knowledge to be used in the work if you do not know them, then the interview is usually here, you can call the Engineer for the API yourself.

->Return to Column Directory <-
Code download address: https://github.com/f641385712/feign-learning

Catalog

Preface

Next to this article Last article Next, the core API section of Feign is introduced.The revolution has not yet been unified, and comrades still need to work hard.

text

Logger

feign.Logger is a log logger abstracted by Feign itself, positioned as a simple log abstraction for debugging.

public abstract class Logger {
	
  // Formatting subclasses of configKey when logging can override this implementation, but it is generally unnecessary	
  protected static String methodTag(String configKey) {
    return new StringBuilder().append('[').append(configKey.substring(0, configKey.indexOf('(')))
        .append("] ").toString();
  }
	
	// It is an abstract method of protected, and subclasses must be replicated.Three parameters are given to you, and the subclass decides how to write (Write console or Write files?)
	// For example, you can use System.out, Log4j, SLF4j and so on.
	protected abstract void log(String configKey, String format, Object... args);

	// Record request information.You can copy, but it's not usually necessary.Keep it in factory format.
	protected void logRequest(String configKey, Level logLevel, Request request) {
		// Print request URL...
		log(configKey, "---> %s %s HTTP/1.1", request.httpMethod().name(), request.url());
		
		// Next, only the log level is output at HEADERS and above
		// This is Feign's own log level: NONE,BASIC,HEADERS,FULL
		// So only if the log level is HEADERS, will the output be below FULL, and the log level is yours.But the default is NONE
		if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
			... // Divide Request Header, Request Body, Response Code, Response Body, etc.
		}
	}

	// Record response information
	protected Response logAndRebufferResponse(...) { ... }
	// Log exception information
	protected IOException logIOException(...){ ... }
}

To sum up, there is actually only one abstract method left for subclass implementations: the log() method.Feign has three implementation classes built into it:

  • ErrorLogger: Very simple, implements only the log() method and deletes it directly using the error stream System.err.printf (methodTag (configKey) + format +'%n', args)
  • NoOpLogger: implements related methods, all empty implementations.This is Feign's default logger
  • JavaLogger: Log based on JDK's own java.util.logging.Logger logger, but, JDK's own log level must be above FINE for logging, and its default level is INFO, so FINE, DEBUG, and so on will not output
    • If you want this log output from JDK, you have to change the log level of the corresponding instance by placing it in a configuration file, etc. This is relatively cumbersome. The production environment does not actually use it. The integration with SLF4j will be discussed later.

InvocationHandlerFactory

Control reflection method scheduling.It is a factory for creating a java.lang.reflect.InvocationHandler reflection call object for the target target.

public interface InvocationHandlerFactory {
	
	// Dispatcher: MethodHandler -> SynchronousMethodHandler instance for each method
	// A FeignInvocationHandler instance is created that implements the InvocationHandler interface
	InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
}
Default

It has one and only one implementation class: feign.InvocationHandlerFactory.Default.

static final class Default implements InvocationHandlerFactory {

	// Simple: call the FeignInvocationHandler constructor to complete the instance creation
    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }

Creating an instance of the InvocationHandler is simple and accomplishes by calling the FeignInvocationHandler's constructor directly.However, it is necessary to read its invoke method, which is the core of completing the positive scheduling of the method and the entry point for all method calls.

FeignInvocationHandler

It implements the InvocationHandler interface for dispatching all methods.

static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map<Method, MethodHandler> dispatch;
	...

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      ... //Omit processing code for equals, hashCode, toString, and so on
	
	  // The dispath completes the dispatch, so the final call is MethodHandler#invoke
      return dispatch.get(method).invoke(args);
    }
}

You can see that the invoke method is only scheduled for a moment, and ultimately it is delegated to SynchronousMethodHandler#invoke(args) to complete the actual call: send an http request or call the interface local method.

Description: Synchronous MethodHandler is the most important part of the entire Feign core process, and I'll leave it at the end to focus on the analysis

Contract

This interface is important: it determines which annotations can be labeled as valid on the interface/interface method and extracts valid information to assemble into MethodMetadata metadata information.

public interface Contract {

	// This method resolves methods in classes that link to HTTP requests: extracting valid information to the meta-information store
	// MethodMetadata: Method meta-information, including but not limited to
	// Return value type returnType
	// Request parameter, index of request parameter, name
	// url, query parameters, request body, etc.
	List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}

The inheritance structure of the interface is as follows:

BaseContract

Abstract base class.

abstract class BaseContract implements Contract {
    @Override
    public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {
		// These checks are interesting
		// 1. No generic variable can exist on a class
		... targetType.getTypeParameters().length == 0
		// 2. An interface can have at most one parent interface
		... targetType.getInterfaces().length <= 1
			targetType.getInterfaces()[0].getInterfaces().length == 0

		// All methods of this class are parsed: wrapped as a MethodMetadata
		// getMethods represents the public method of this class + parent class
		// Because it's an interface, everything must be public (Java8 of course supports private, default, static, etc.)
		Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
		for (Method method : targetType.getMethods()) {
			... // Methods that exclude Object s, static methods, default methods, and so on
			
			// parseAndValidateMetadata is a protected method of this class
			MethodMetadata metadata = parseAndValidateMetadata(targetType, method);
			// Note that this key is metadata.configKey()
			result.put(metadata.configKey(), metadata);

			// Note: result is not directly returned here
			// Instead, it returns a snapshot version
			return new ArrayList<>(result.values());
		}
    }
}

It is clear from here that although Feign is implemented based on an interface, it requires an interface:

  1. Cannot be a generic interface
  2. An interface can have at most one parent interface (there can be no parent, of course)

He then handles all interface methods, including the parent interface.It does not include default methods, private methods, static methods, etc. in the interface, and excludes methods in Object.
The metadata analysis of the method falls on the parseAndValidateMetadata(), a protected method:

BaseContract: 

	protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
		MethodMetadata data = new MethodMetadata();
		// Method return types are generic
		data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
		// Feign's tool method is used here to generate configKey, not to get too detailed, but simply to be as unique as possible
		data.configKey(Feign.configKey(targetType, method));

	  // This is an important step: handling annotations on interfaces.And handled the parent interface
	  // That's why annotations on your parent interface and subinterfaces work, too. ~~
	  // processAnnotationOnClass() is an abstract method that is left to subclasses to implement (after all, annotations can be extended)
      if (targetType.getInterfaces().length == 1) {
        processAnnotationOnClass(data, targetType.getInterfaces()[0]);
      }
      processAnnotationOnClass(data, targetType);
	
	  // Handle all annotations labeled in methods
	  // If the child interface override s the method of the parent interface, the comment should focus on the child interface, ignoring the parent interface method
      for (Annotation methodAnnotation : method.getAnnotations()) {
        processAnnotationOnMethod(data, methodAnnotation, method);
      }
		// Simply put: Once you've processed the annotations on the method, you must already know whether it's GET or POST or something else
	   checkState(data.template().method() != null,


		// Method parameters that support generic types.Such as List <String>
      Class<?>[] parameterTypes = method.getParameterTypes();
      Type[] genericParameterTypes = method.getGenericParameterTypes();

	   // Annotations are a two-dimensional array...
      Annotation[][] parameterAnnotations = method.getParameterAnnotations();
      int count = parameterAnnotations.length;
      // Processing of One Note One Note
      for (int i = 0; i < count; i++) {
		...
		// processAnnotationsOnParameter is an abstract method that subclasses determine
        if (parameterAnnotations[i] != null) {
          isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
        }

		// If a parameter of type URI exists for a method parameter, then the url is based on it and does not use global
		if (parameterTypes[i] == URI.class) {
			data.urlIndex(i);
		}
		... 
		// Check body:
		// 1. The body parameter cannot be used as a parameter of the form form
		// 2. Body parameters cannot be too many
		
		...
		return data;
      }
		
	}

The annotation information on each method is parsed according to the top-down process, and the abstract method is left to the subclass implementation:

  • processAnnotationOnClass:
  • processAnnotationOnMethod:
  • processAnnotationsOnParameter:

These three abstract methods are very common and determine which annotations to identify and parse, so they are particularly suitable for third-party extensions.
Clearly, Feign's built-in default implementations implement these interfaces, and even Spring MVC's extended SpringMvcContract inherits it to support annotations such as @RequestMapping.

Default

It is the only built-in implementation class for BaseContract and the default implementation for Feign.

class Default extends BaseContract {

	static final Pattern REQUEST_LINE_PATTERN = Pattern.compile("^([A-Z]+)[ ]*(.*)$");

	// Support note: @Headers
    @Override
    protected void processAnnotationOnClass(MethodMetadata data, Class<?> targetType) { ... }

	// Expense note: @RequestLine, @Body, @Headers
    @Override
    protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { ... }

	// Supports annotations: @Param, @QueryMap, @HeaderMap, etc.
    @Override
    protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { ... }
	...
}

By implementing this subclass, you can clearly see which annotation types Feign supports natively, which is exactly what was described in the previous article.

MethodHandler

Functionally similar to the java.lang.reflect.InvocationHandler#invoke() method, the interface definition is simple:

interface MethodHandler {
	Object invoke(Object[] argv) throws Throwable;
}

The interface definition is simple, but it is of paramount importance in completing the Feign send request process.The interface has two built-in implementation classes:

Note: The interface name is MethodHandler, while JDK's is MethodHandle, don't confuse...

DefaultMethodHandler

It is handled by calling the interface default method (because it does not hold a Target proxy object, so the only method available in the interface is the default method). Note that the bindTo method must be called before the invoke method.

It relies on the Java7 method handle MethodHandle, which is more efficient than reflection, with the disadvantage of slightly more complex coding.

Description: How to use the method handle MethodHandle for JDK?Interested students, please do your own research

// Access is Default
final class DefaultMethodHandler implements MethodHandler {

	private final MethodHandle unboundHandle;
	private MethodHandle handle;

	// Get the method handle from Method and assign it to unboundHandle
	public DefaultMethodHandler(Method defaultMethod) { ... }


  // Bind the target object (proxy object) to the method handle
  // So unboundHandle becomes a bound handle, so invoke can be invoked
  public void bindTo(Object proxy) {
    if (handle != null) {
      throw new IllegalStateException("Attempted to rebind a default method handler that was already bound");
    }
    handle = unboundHandle.bindTo(proxy);
  }

  // Call target method
  // Before invoking: Make sure the target object is bound
  @Override
  public Object invoke(Object[] argv) throws Throwable {
    if (handle == null) {
      throw new IllegalStateException("Default method handler invoked before proxy has been bound.");
    }
    return handle.invokeWithArguments(argv);
  }
}

The default implementation is characterized by the method handle MethodHandle used to "reflect" the execution of the target method. It is obvious that it can only execute to the interface default method, so there is usually something like remote communication.

Synchronous MethodHandler

Synchronization method calls the processor, which emphasizes synchronization and remote communication.

final class SynchronousMethodHandler implements MethodHandler {
	
	// Method Meta Information
	private final MethodMetadata metadata;
	// The target, that is, the instance that ultimately actually builds the Http request Request, is generally HardCodedTarget
	private final Target<?> target;
	// Responsible for sending final requests - >The default incoming is based on JDK source, which is inefficient and not recommended for direct use
	private final Client client;
	// Responsible for retry --> Default is passed in by default, there is a retry mechanism oh, production use must be noted
	private final Retryer retryer;

	// Request interceptor, which completes interception before target.apply(template); that is, template->requested transformation
	// Note: It's not the moment before the request is sent, so be careful
	// It only serves to customize the request template, not the Request
	// There is only one implementation built in: BasicAuthRequestInterceptor for authentication
	private final List<RequestInterceptor> requestInterceptors;

	// If you want to see feign's request log in the console, change the log level to info (since info is usually the only output to the log file)
	private final Logger.Level logLevel;
	...
	// Factory Building Request Template
	// For request templates, there are several ways to build them, possibly multiple encoders are used internally, as detailed below
	private final RequestTemplate.Factory buildTemplateFromArgs;
	// Request parameters: such as link timeout, request timeout, etc.
	private final Options options;

	// Decoder: Used to decode Response
	private final Decoder decoder;
	// Decoder in case of error/exception
	private final ErrorDecoder errorDecoder;

	// Decode 404 status code?Default is not decoded
	private final boolean decode404;
	
	// The only constructor, and also private (so it must only be built within this class.)
	// Completed assignment of all the above attributes
	private SynchronousMethodHandler( ... ) { ... }

  	@Override
  	public Object invoke(Object[] argv) throws Throwable {
  		// Build a request template based on the method and the factory
		RequestTemplate template = buildTemplateFromArgs.create(argv);
		// findOptions(): If your method includes Options type in the entry, it will be found here
		// Note: If there are more than one, only the first one will take effect (no error will be reported)
		Options options = findOptions(argv);
		// Retry mechanism: Note that here is a clone to use
		Retryer retryer = this.retryer.clone();
		while (true) {
		     try {
		        return executeAndDecode(template, options);
		      } catch (RetryableException e) {
				
				// If an exception is thrown, the retry logic is triggered
		       try {
		       	  // The logic is that if you don't retry, the exception will continue to be thrown
		       	  // To recharge, continue below
		          retryer.continueOrPropagate(e);
		        } catch (RetryableException th) {
		        	...
		        }
		        continue;
		      }
		}
  	}
}

The MethodHandler implementation is relatively complex, described in one sentence as sending an Http request with all the parameters ready and parsing the results.The steps I try to summarize are as follows:

  1. Build a RequestTemplate request template using the factory with method parameters
    1. Annotations such as @RequestLine/@Param are resolved here
  2. Get the request option from the method parameter: Options (of course, there may not be this type in the parameter, which is null).If null, the default option is executed eventually)
  3. executeAndDecode(template, options) executes the send Http request and completes the result decoding (including decoding of the correct status code and error decoding).This step is more complex and is divided into the following sub-steps:
    1. Convert request template to request object feign.Request
      1. Execute all interceptors RequestInterceptor to complete customization of request template
      2. Call the target and change the request template to Request:target.apply(template);
    2. Send an Http request: client.execute(request, options) and get a Response object (where IO exceptions can also be wrapped as RetryableException and thrown again)
    3. Parse the Response object and return after parsing (returns Object: either an instance of Response or any type after decode decoding).The general situation is as follows:
      1. Response.class == metadata.returnType(), which means your method returns the value using Response.If response.body() == null, that is, if the server returns null/void, return the response directly; if response.body().length() == null, return the response directly; otherwise, return the contents of response.toBuilder().body(bodyData).build() body normally.
      2. If 200 <=Response Code<= 300, it means correct return.Decode the return value: decoder.decode(response, metadata.returnType()) (there may be exceptions during decoding and they can also be wrapped and thrown up as FeignException)
      3. If the response code is 404 and decode404 = true, the decode action is performed as above
      4. In other cases (response codes of 4xx or 5xx), error encoding is performed: errorDecoder.decode(metadata.configKey(), response)
  4. Sending an http request is over if everything is okay.Otherwise, retry logic is performed:
    1. Through retryer.continueOrPropagate(e); see if you want to execute a retry mechanism after receiving this exception
    2. continue if you need to try again (note that while(true) oh~ above)
    3. If you don't need to retry (or the number of retries has already been reached), re-throw the exception, throw it up
    4. Handle this exception, print the log...

I personally believe that this is the core logic of Feign as a HC, and it is important for your readers to understand it.

summary

The core API for Feign is described here. Although dull, it is hard core and knowledgeable, so long as you bite it, you will get something.
Although the core API can't be fully covered by 100%, most of them are already covered and there are few barriers to follow-up learning. Welcome to build them together.

statement

The original is not easy, the code is not easy. Thank you for your compliment, collection and attention.Sharing this article with your circle of friends is allowed, but you refuse to copy it.You can also join my family of Java engineers and architects in learning and communicating with Scavenger on the left.

288 original articles were published. 451 were praised. 380,000 visits+
His message board follow

Keywords: JDK Java github log4j

Added by calevans on Wed, 12 Feb 2020 05:09:59 +0200