
Code address of this series: https://github.com/JoJoTec/spring-cloud-parent
Next, we begin to analyze the life cycle of OpenFeign, combined with the source code of OpenFeign itself. The first is to create an OpenFeign proxy from the interface definition. We only care about the synchronous client here, because the asynchronous client is still being implemented, and in our project, the asynchronous responsive client does not use OpenFeign, but the official WebClient
Create OpenFeign proxy
Creating an OpenFeign agent is mainly divided into the following steps:
- Use Contract to parse each method of the interface and generate a metadata list of each method: List < methodmetadata > metadata
- According to each MethodMetadata, generate the corresponding request template factory requesttemplate Factory, used to generate subsequent requests. At the same time, the template factory and other configurations are used to generate the corresponding method processor MethodHandler. For synchronous OpenFeign, the MethodHandler is implemented as synchronous MethodHandler. Map the interface method and MethodHandler one by one, and the result is map < method, MethodHandler > methodtohandler. For the interface default method introduced by Java 8, you need to use a different MethodHandler, DefaultMethodHandler, because this method does not need a proxy and does not generate a corresponding http call. Instead, it is implemented by directly calling the corresponding default method code.
- Use the InvocationHandlerFactory factory to create an InvocationHandler for proxy calls.
- Call JDK dynamic proxy generation class method to create proxy class using InvocationHandler.
Create OpenFeign agent, which is mainly based on the dynamic agent implementation of JDK. Let's take a simple example to create a JDK dynamic agent for analogy.
JDK dynamic agent
Using JDK dynamic agent requires the following steps:
1. Write the interface and the corresponding proxy class. Here we write a simple interface and corresponding implementation class:
public interface TestService { void test(); }
public class TestServiceImpl implements TestService { @Override public void test() { System.out.println("TestServiceImpl#test is called"); } }
2. Create a proxy class to implement Java lang.reflect. InvocationHandler, and, in the core method, invoke the actual object, which is the object of the implementation class TestServiceImpl of our TestService above.
JDK has a built-in dynamic proxy API, and its core is Java lang.reflect. InvocationHandler. Let's create 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( //Proxy object Object proxy, //Method called Method method, //Parameters used Object[] args) throws Throwable { System.out.println("Invoked method: " + method.getName()); //Make the actual call return method.invoke(testService, args); } }
3. Create a Proxy object and call it with the Proxy object. It is generally created through the static method of Proxy, for example:
//First, create the object to proxy TestServiceImpl testServiceImpl = new TestServiceImpl(); //Then create the corresponding InvocationHandler using the object to proxy SimplePrintMethodInvocationHandler simplePrintMethodInvocationHandler = new SimplePrintMethodInvocationHandler(testServiceImpl); //Create a proxy class. Because a class may implement multiple interfaces, the Object returned here is forced into the interface to be used according to your needs Object proxyInstance = Proxy.newProxyInstance( TestService.class.getClassLoader(), testServiceImpl.getClass().getInterfaces(), simplePrintMethodInvocationHandler ); //Force conversion TestService proxied = (TestService) proxyInstance; //Call with proxy object proxied.test();
In this way, we use the built-in dynamic proxy mechanism of JDK to implement a simple dynamic proxy. In the use of OpenFeign, it is a little different from our example. First of all, we only need to define the interface to proxy, not the implementation class. Because what all OpenFeign interfaces need to do is actually HTTP calls, and its information can be automatically generated from the interface definition. We can use unified objects to carry the requests defined by the OpenFeign interface according to the interface definition. In OpenFeign, this is equivalent to the implementation object, that is, the MethodHandler generated according to the interface, in the synchronized OpenFeign, that is, feign SynchronousMethodHandler. After that, the InvocationHandler created by OpenFeign actually forwards the call to the corresponding method of the corresponding synchronous MethodHandler.
Detailed explanation of the process of creating OpenFeign proxy object
Using the previous example, let's take a look at the process of creating an agent:
interface GitHub { /** * Define get methods, including path parameters, and return serialized classes in response * @param owner * @param repository * @return */ @RequestLine("GET /repos/{owner}/{repo}/contributors") List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repository); /** * Responder structure class */ class Contributor { String login; 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; } } } /** * Deserialization decoder based on fastjason */ static class FastJsonDecoder implements Decoder { @Override public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException { //Read body byte[] body = response.body().asInputStream().readAllBytes(); return JSON.parseObject(body, type); } }
public static void main(String[] args) { //Create the HTTP call interface implementation of Feign proxy GitHub github = Feign.builder() //Specify the decoder as FastJsonDecoder .decoder(new FastJsonDecoder()) //Specify that the proxy class is GitHub and the base address is https://api.github.com .target(GitHub.class, "https://api.github.com"); List<GitHub.Contributor> contributors = github.contributors("OpenFeign", "feign"); }
What we care about here is to create the HTTP call interface of Feign proxy to realize the internal process of this step. First, let's look at the structure of Feign builder. When we initialize a Feign builder, we call Feign When Builder (), the following components will be created (it also shows that the following components can be configured. If some configurations are not mentioned before, they can be configured):
//Request interceptor list, which is empty by default private final List<RequestInterceptor> requestInterceptors = new ArrayList(); //Log level. No logs are printed by default private Level logLevel = Level.NONE; //The Contract responsible for parsing class metadata is the default Contract that supports OpenFeign built-in annotations private Contract contract = new Contract.Default(); //The Client that hosts HTTP requests is the Default Client based on Java HttpURLConnection by default private Client client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null); //The retrier is also Default by Default private Retryer retryer = new feign.Retryer.Default(); //The default log Logger does not record any logs by default private Logger logger = new NoOpLogger(); //The encoder decoder is also the default private Encoder encoder = new feign.codec.Encoder.Default(); private Decoder decoder = new feign.codec.Decoder.Default(); //Query parameter code, which we generally do not modify private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder(); //Error encoder, Default private ErrorDecoder errorDecoder = new feign.codec.ErrorDecoder.Default(); //Default configuration 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(); //Is there a special resolution 404 error? Because we may not want to throw an exception for 404. The default is false private boolean decode404 = false; //Whether to close the Response immediately after decoding. The default is yes private boolean closeAfterDecode = true; //Exception propagation rule. The default is no propagation private ExceptionPropagationPolicy propagationPolicy = ExceptionPropagationPolicy.NONE; //Whether to force decoding is mainly to be compatible with the configuration introduced by asynchronous Feign. We directly ignore it and think it is false private boolean forceDecoding = false; private List<Capability> capabilities = new ArrayList();
Our code specifies that the Decoder is FastJsonDecoder, so the Decoder is FastJsonDecoder. Finally, through target (GitHub. Class“ https://api.github.com "); specify that the proxy class is GitHub and the base address is https://api.github.com At this time, Feign proxy class will be generated. The steps are as follows:
public <T> T target(Class<T> apiType, String url) { //HardCodedTarget is created by using proxy interface type and base address, which means hard coded Target return target(new HardCodedTarget<T>(apiType, url)); } public <T> T target(Target<T> target) { return build().newInstance(target); } public Feign build() { //Pass all components through all capabilities. From here, we can see that we can implement the Capability interface to dynamically modify components when creating Feign agent Client 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 Factory of SynchronousMethodHandler, which is used to generate SynchronousMethodHandler. SynchronousMethodHandler is an implementation class that actually hosts Feign proxy requests SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding); //The metadata resolution of different interface methods is distinguished by method names, which are used to generate and route to the corresponding proxy methods ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); //Create reflective feign return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); } }
After creating the ReflectiveFeign, the newInstance method will be called:
public <T> T newInstance(Target<T> target) { //ParseHandlersByName mentioned earlier is used to parse metadata and generate methodhandlers for all methods that need proxy. We analyze synchronous Feign here, so it is synchronous MethodHandler Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); //Correspond the method to the corresponding MethodHandler one by one for (Method method : target.type().getMethods()) { if (method.getDeclaringClass() == Object.class) { //For Object methods, skip directly continue; } else if (Util.isDefault(method)) { //If it is the default method of the interface in java 8, use DefaultMethodHandler DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } //Create an InvocationHandler using the InvocationHandlerFactory in the previous Builder InvocationHandler handler = factory.create(target, methodToHandler); //Creating a Proxy using InvocationHandler T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); //Associate proxy with DefaultMethodHandler for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }
For the MethodHandler that uses the ParseHandlersByName parsing metadata mentioned earlier and generates all the methods that need the agent, this involves the metadata that is parsed by Contract, and then binds them to the corresponding encoding to use the encoding after that:
public Map<String, MethodHandler> apply(Target target) { // Use Contract to parse the metadata of all methods List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type()); Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); // For each parsed method metadata for (MethodMetadata md : metadata) { BuildTemplateByResolvingArgs buildTemplate; if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) { //With forms buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else if (md.bodyIndex() != null) { //With body buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else { //Other situations 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 the Factory of SynchronousMethodHandler to generate SynchronousMethodHandler result.put(md.configKey(), factory.create(target, md, buildTemplate, options, decoder, errorDecoder)); } } return result; } }
The InvocationHandler generated by the default InvocationHandlerFactory is reflective feign FeignInvocationHandler:
static final class Default implements InvocationHandlerFactory { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { return new ReflectiveFeign.FeignInvocationHandler(target, dispatch); } }
The contents are:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // For equals, hashCode, toString methods, call directly 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 for processing return dispatch.get(method).invoke(args); }
From here, we can see that the Proxy we generated actually proxies the request to the synchronous methodhandler.

In this section, we introduce in detail the detailed process of creating Proxy by OpenFeign. It can be seen that the Proxy generated by synchronous Feign actually proxies the method request defined by the interface HTTP request to the SynchronousMethodHandler. In the next section, we will analyze the actual HTTP call process of SynchronousMethodHandler in detail to find out how all Feign components work in coordination.