Feign source code learning

Keywords: Programming github Java Retrofit Spring

feign introduction

Feign is a Restful client component of Java. Feign makes it easier to write Java HTTP client. Feign was inspired by Retrofit, JAXRS-2.0 and WebSocket. Feign has nearly 3K stars on github, which is a quite excellent open source component. Although it is inferior to nearly 30K stars of Retrofit, spring cloud integrates feign, which makes feign more widely used in Java ecosystem than Retrofit.

The basic principle of feign is to annotate the interface method, define the rest request, construct the dynamic proxy object of the interface, and then send the http request by calling the interface method, and automatically parse the http response as the return value of the method, which greatly simplifies the code for the client to call the rest api. An example of the official website is as follows:

 interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}

static class Contributor {
  String login;
  int contributions;
}

public static void main(String... args) {
  GitHub github = Feign.builder()
                       .decoder(new GsonDecoder())
                       .target(GitHub.class, "https://api.github.com");

  // Fetch and print a list of the contributors to this library.
  List<Contributor> contributors = github.contributors("OpenFeign", "feign");
  for (Contributor contributor : contributors) {
    System.out.println(contributor.login + " (" + contributor.contributions + ")");
  }
}

Please refer to the official website for feign tutorial https://github.com/OpenFeign/feign/

This paper mainly analyzes the source code of feign, and understands the design architecture and internal implementation technology of feign according to the source code.

Feign.build builds dynamic agent of interface

First, let's see how the dynamic proxy of the interface is built. The following figure is the class diagram of the main interface and class:

From the example above, we can see that the dynamic proxy object of the built interface is constructed by Feign.builder() to generate the constructor object of feign. Builder, then set the relevant parameters, and then call the target method. Feign.Builder's parameters include:

//Interceptor, after assembling the RequestTemplate, intercepts and processes the RequestTemplate before sending the request
private final List<RequestInterceptor> requestInterceptors = new ArrayList<RequestInterceptor>();

//log level
private Logger.Level logLevel = Logger.Level.NONE;

//Contract model, default is Contract.Default, and the user creates MethodMetadata, which is extended by spring cloud to implement spring MVC annotation
private Contract contract = new Contract.Default();

//Client, default to Client.Default, can extend Apache httpclient, OKHttpClient, RibbonClient, etc
private Client client = new Client.Default(null, null);

//Retry setting, not set by default
private Retryer retryer = new Retryer.Default();

//Log, can access Slf4j
private Logger logger = new NoOpLogger();

//Encoder, for body coding
private Encoder encoder = new Encoder.Default();

//Decoder, decoding of user response
private Decoder decoder = new Decoder.Default();

//Parameter encoder annotated with @ QueryMap
private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();

//Request error decoder
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();

//Parameter configuration, mainly including timeout
private Options options = new Options();

//Dynamic agent factory
private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();

//decode404 or not
private boolean decode404;

private boolean closeAfterDecode = true;

// Exception isolation strategy
private ExceptionPropagationPolicy propagationPolicy = NONE;

This is a typical constructor pattern. In the target method, first call the build method to create a new ReflectFeign object, and then call the newInstance method of ReflectFeign to create a dynamic agent. The code is as follows:

//HardCodedTarget is used by default
public <T> T target(Class<T> apiType, String url) {
	return target(new HardCodedTarget<T>(apiType, url));
}

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, 
								closeAfterDecode);
	
	ParseHandlersByName handlersByName = new ParseHandlersByName(contract, 
								options, 
								encoder, 
								decoder, 
								queryMapEncoder,
								errorDecoder, 
								synchronousMethodHandlerFactory);
	
	// handlersByName encapsulates all parameters and provides logic for parsing interface methods
	// invocationHandlerFactory is a Builder property. The default value is InvocationHandlerFactory.Default
	// Implementation of InvocationHandler for creating java Dynamic Proxy
	return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

Target source code is as follows:

public interface Target<T> {
	// api interface class
	Class<T> type();

	// name
	String name();

	// The prefix address of the api interface path (for example, http://localhost:8080), which will be used when sending the request and the url path in @ RequestLine (/ user/list)
	// Spliced together as a request url(http://localhost:8080/user/list)
	String url();

	public Request apply(RequestTemplate input);

	/**
	* Target Default implementation of, do nothing
	*/
	public static class HardCodedTarget<T> implements Target<T> {

		private final Class<T> type;
		private final String name;
		private final String url;

		public HardCodedTarget(Class<T> type, String url) {
			// You can see that the name is the same as the url by default
			this(type, url, url);
		}

		public HardCodedTarget(Class<T> type, String name, String url) {
			this.type = checkNotNull(type, "type");
			this.name = checkNotNull(emptyToNull(name), "name");
			this.url = checkNotNull(emptyToNull(url), "url");
		}
		
		@Override
		public Request apply(RequestTemplate input) {
			if (input.url().indexOf("http") != 0) {
				input.target(url());
			}
			return input.request();
		}
	}
}

The ReflectiveFeign constructor has three parameters:

  • ParseHandlersByName encapsulates all the builder parameters and provides the logic to parse interface methods
  • InvocationHandlerFactory java the factory class of the InvocationHandler of the dynamic agent. The default value is InvocationHandlerFactory.Default
  • When the QueryMap Encoder Interface Parameter annotation @ QueryMap, the encoder of the parameter

The ReflectiveFeign.newInstance method creates an interface dynamic proxy object:

public <T> T newInstance(Target<T> target) {
	//targetToHandlersByName is the ParseHandlersByName object passed in by the constructor. The MethodHandler mapping is generated based on the target object
	Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
	Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
	List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
	//Traverse all methods of the interface and build the mapping of method - > methodhandler
	for (Method method : target.type().getMethods()) {
		if (method.getDeclaringClass() == Object.class) {
			continue;
		} else if(Util.isDefault(method)) {
			//Handler of interface default method, which directly calls
			DefaultMethodHandler handler = new DefaultMethodHandler(method);
			defaultMethodHandlers.add(handler);
			methodToHandler.put(method, handler);
		} else {
			methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
		}
	}
	//Here, factory constructs the incoming, and creates the InvocationHandler
	InvocationHandler handler = factory.create(target, methodToHandler);
	//Dynamic agent of java
	T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
	//Bind the default method directly to the dynamic agent
	for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
		defaultMethodHandler.bindTo(proxy);
	}
	return proxy;
}

Most of the content comes from: http://techblog.ppdai.com/2018/05/14/20180514/
I add my understanding to it

Posted by jfgreco915 on Mon, 23 Dec 2019 02:05:29 -0800