This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

This series code address: github.com/JoJoTec/spr…

Next, we began to analyze the OpenFeign lifecycle, combined with the source code of OpenFeign itself. The first step is to create the OpenFeign proxy from the interface definition. We only care about synchronous clients here, because asynchronous clients are still being implemented, and in our project, asynchronous responsive clients don’t use OpenFeign, but the official WebClient

Create an OpenFeign agent

Creating an OpenFeign agent consists of the following steps:

  1. Use Contract to parse each method of the interface and generate a list of metadata for each method:List<MethodMetadata> metadata
  2. According to eachMethodMetadataTo generate the corresponding request template factoryRequestTemplate.FactoryIs used to generate subsequent requests. At the same time, the corresponding method handlers are generated using this template factory and other configurationsMethodHandler, for synchronous OpenFeign,MethodHandlerImplemented as aSynchronousMethodHandler. Interface methods withMethodHandlerOne-to-one mapping is established, and the result isMap<Method, MethodHandler> methodToHandler. For the interface Default method introduced in Java 8, you need to use something differentMethodHandler, i.e.,DefaultMethodHandler, because this method does not need to generate the corresponding HTTP call, its implementation is to directly call the corresponding default method code.
  3. Using the InvocationHandlerFactory factory, createInvocationHandlerUsed for proxy calls.
  4. Call the JDK dynamic proxy generation class methodInvocationHandlerCreate the proxy class.

Create an OpenFeign proxy based on the DYNAMIC proxy implementation of the JDK. Let’s start with a simple example of creating a JDK dynamic proxy to use as an analogy.

JDK dynamic proxy

To use the JDK dynamic proxy, the following steps are required:

1. Write interfaces and corresponding proxy classes. Here we write a simple interface and the corresponding implementation class:

public interface TestService {
    void test();
}
Copy the code
public class TestServiceImpl implements TestService { @Override public void test() { System.out.println("TestServiceImpl#test is called"); }}Copy the code

2. Create a proxy class implements the Java. Lang. Reflect the InvocationHandler, and, in the core method, call the actual object, here is the implementation class which we TestService TestServiceImpl object.

With built-in in the JDK dynamic proxy API, its core is the Java lang. Reflect. InvocationHandler. Let’s start by creating a simple InvocationHandler implementation class:

public class SimplePrintMethodInvocationHandler implements InvocationHandler { private final TestService testService; public SimplePrintMethodInvocationHandler(TestService testService) { this.testService = testService; } @override public Object invoke(// invoke Object, // invoke Method, Object[] args) throws Throwable {System.out.println("Invoked Method: "+ method.getName()); // The Object[] args) throws Throwable {system.out.println ("Invoked Method:" + method.getName()); Return method.invoke(testService, args); }}Copy the code

3. Create a proxy object and invoke it using the proxy object. It is usually created using the static method of Proxy, for example:

TestServiceImpl = new TestServiceImpl(); / / and then use the proxy objects to create the corresponding InvocationHandler SimplePrintMethodInvocationHandler SimplePrintMethodInvocationHandler = new SimplePrintMethodInvocationHandler(testServiceImpl); // Create a proxy class, because a class can implement multiple interfaces, so return Object, Users according to their own need to cast to use interface Object proxyInstance = Proxy. NewProxyInstance (TestService. Class. GetClassLoader (), testServiceImpl.getClass().getInterfaces(), simplePrintMethodInvocationHandler ); // cast TestService proxied = (TestService) proxyInstance; // Call proxied.test() with a proxy object;Copy the code

Thus, we implemented a simple dynamic proxy using the JDK’s built-in dynamic proxy mechanism. In the use of OpenFeign, there is a slight difference from our example. First, we only need to define the interface to be propped up, not the implementation class. Because all the OpenFeign interfaces do is actually HTTP calls, and their information can be automatically generated from the interface definition, we can use a unified object to host the requests defined by the OpenFeign interface based on the interface definition. In OpenFeign, this is equal to realize the object, is according to the interface to generate MethodHandler, in synchronous OpenFeign, namely feign. SynchronousMethodHandler. OpenFeign then creates the InvocationHandler, which is essentially the corresponding method that forwards the call to the corresponding SynchronousMethodHandler.

The process for creating an OpenFeign proxy object

Using the previous example, let’s look at the process of creating an agent:

Interface GitHub {/** * define the get method, including path parameters, Response returns serialization classes * @param owner * @Param Repository * @return */ @Requestline ("GET /repos/{owner}/{repo}/ polymorphism ") List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repository); /** * Contributor class */ class Contributor {String Contributor; int contributions; public Contributor() { } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public int getContributions() { return contributions; } public void setContributions(int contributions) { this.contributions = contributions; }} /** * static class implements Decoder {@override public Object decode(Response response, Type type) throws IOException, DecodeException, [] body = response.body().asinputStream ().readallBytes (); FeignException {// Read body byte[] body = response.body().asinputStream ().readallBytes (); return JSON.parseObject(body, type); }}Copy the code
GitHub GitHub = feign.builder () public static void main(String[] args) {GitHub GitHub = feign.builder ( FastJsonDecoder. Decoder(new FastJsonDecoder())) The base address is https://api.github.com. Target (GitHub. Class, "https://api.github.com"); List<GitHub.Contributor> contributors = github.contributors("OpenFeign", "feign"); }Copy the code

What we are interested in here is the internal flow of creating the HTTP call interface for the Feign proxy. When we initialize a Feign Builder by calling feign.Builder (), we create the following components (which are configurable, if not previously mentioned) :

Private final List<RequestInterceptor> requestInterceptors = new ArrayList(); // logLevel. No logs are printed by default. Private Level logLevel = level.none; Private Contract Contract = new contract.default (); // The HTTP request Client, Private Client Client = new Feign.client. Default(SSLSocketFactory)null, (HostnameVerifier)null); Default private Retryer Retryer = new feign.retryer.Default(); Private Logger Logger = new NoOpLogger(); / decoder/Encoder is also the Default private Encoder Encoder = new feign. Codec. Encoder. The Default (); private Decoder decoder = new feign.codec.Decoder.Default(); Private QueryMapEncoder QueryMapEncoder = new FieldQueryMapEncoder(); / / error encoder, the Default for the Default private ErrorDecoder ErrorDecoder = new feign. Codec. ErrorDecoder. Default (); // Default Settings for various timeout Options private Options Options = new Options(); The Factory used to generate InvocationHandler is also the default private InvocationHandlerFactory InvocationHandlerFactory = new feign.InvocationHandlerFactory.Default(); Private Boolean decode404 = false; private Boolean decode404 = false; // Whether to close Response immediately after decoding. Default: private Boolean closeAfterDecode = true; / / exception propagation rule, the default is not spread private ExceptionPropagationPolicy propagationPolicy = ExceptionPropagationPolicy. NONE. Private Boolean forceDecoding = false; private Boolean forceDecoding = false; private List<Capability> capabilities = new ArrayList();Copy the code

Our code specifies the Decoder as FastJsonDecoder, so Decoder is FastJsonDecoder. Target (GitHub. Class, “https://api.github.com”); The Feign proxy class is generated by specifying GitHub as the designated proxy class and api.github.com as the base address:

Feign

Public <T> T target(Class<T> apiType, String URL) {// Create HardCodedTarget with proxy interface type and base address, HardCodedTarget return Target (new HardCodedTarget<T>(apiType, url)); } public <T> T target(Target<T> target) { return build().newInstance(target); } public Feign build() {// Pass all components over all capabilities. Client = capability.enrich (this.client, capabilities); Retryer retryer = Capability.enrich(this.retryer, capabilities); List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream() .map(ri -> Capability.enrich(ri, capabilities)) .collect(Collectors.toList()); Logger logger = Capability.enrich(this.logger, capabilities); Contract contract = Capability.enrich(this.contract, capabilities); Options options = Capability.enrich(this.options, capabilities); Encoder encoder = Capability.enrich(this.encoder, capabilities); Decoder decoder = Capability.enrich(this.decoder, capabilities); InvocationHandlerFactory invocationHandlerFactory = Capability.enrich(this.invocationHandlerFactory, capabilities); QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities); // Create a SynchronousMethodHandler Factory that generates SynchronousMethodHandler, SynchronousMethodHandler implementation class is the actual bearing Feign agent request SynchronousMethodHandler. Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding); // Metadata parsing for different interface methods is distinguished by method names, ParseHandlersByName = new ParseHandlersByName(Contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); / / create ReflectiveFeign return new ReflectiveFeign (handlersByName invocationHandlerFactory, queryMapEncoder); }}Copy the code

After creating ReflectiveFeign, the newInstance method is called:

ReflectiveFeign

Public <T> T newInstance(Target<T> Target) {// Use the aforementioned ParseHandlersByName to parse metadata and generate a MethodHandler for all methods that need to be propeted, What we're looking at here is synchronous Feign, So is SynchronousMethodHandler Map < String, MethodHandler > nameToHandler = targetToHandlersByName. Apply (target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); // match the Method to the corresponding MethodHandler. Target.type ().getMethods()) {if (method.getdeclaringClass () == object.class) {// For Object methods, skip continue; } else if (util.isdefault (method)) {// If this is the default method of Java 8 interface, 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, InvocationHandler); methodToHandler); T Proxy = (T) proxy.newproxyInstance (target.type().getClassLoader(), new Class<? >[] {target.type()}, handler); // Associate the proxy with the DefaultMethodHandler. defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }Copy the code

For the MethodHandler that uses the aforementioned ParseHandlersByName to parse the metadata and generate all the methods that need to be proided, the main step involves parsing the metadata out of the method using a Contract, This metadata is then bound with the corresponding encoder for subsequent encodings:

ReflectiveFeign

public Map<String, List<MethodMetadata> metadata = List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type()); Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); / / for each parsing out the method of metadata for (MethodMetadata md: metadata) {BuildTemplateByResolvingArgs buildTemplate; if (! Md.formparams ().isEmpty() &&md.template ().bodyTemplate() == null) {// buildTemplate = new with forms BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else if (md.bodyIndex() ! = null) {/ / a body of case buildTemplate = new BuildEncodedTemplateFromArgs (md, encoder, queryMapEncoder, target); Other information} else {/ / buildTemplate = new BuildTemplateByResolvingArgs (md, queryMapEncoder, target); } if (md.isIgnored()) { result.put(md.configKey(), args -> { throw new IllegalStateException(md.configKey() + " is not a method handled by feign"); }); } else {// Use SynchronousMethodHandler's Factory to generate SynchronousMethodHandler result.put(md.configKey(), factory.create(target, md, buildTemplate, options, decoder, errorDecoder)); } } return result; }}Copy the code

The default InvocationHandlerFactory generated InvocationHandler is ReflectiveFeign FeignInvocationHandler:

static final class Default implements InvocationHandlerFactory { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { return new ReflectiveFeign.FeignInvocationHandler(target, dispatch); }}Copy the code

It reads:

@override public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable {// For equals, hashCode, 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(); } // For other methods, call the corresponding SynchronousMethodHandler to return dispatch.get(method).invoke(args); }Copy the code

From here, we can see that the Proxy we generated actually proxies requests to the SynchronousMethodHandler.

In this section, we walk through the OpenFeign Proxy creation process in detail, and you can see that for a synchronized Feign generated Proxy, the method request defined by the interface HTTP request is proxying to the SynchronousMethodHandler. In the next section, we’ll take a closer look at how the SynchronousMethodHandler does the actual HTTP calls to see how all of Feign’s components work together.