Introduction of Feign

  • Feign is a declarative, templated HTTP client. Declarative invocation calls remote methods as if they were local methods, unaware of remote HTTP requests, allowing us to ignore the details of interaction with the remote, let alone the development of a distributed environment.

  • Integrated Ribbon and Eureka load balancing client supports Hystrix circuit breaker, service fuses and downgrades.

The basic use

1. Import the JAR package

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> < version > 2.1.2. RELEASE < / version > < / dependency >Copy the code

2. Start the class with an annotation EnableFeignClients

@SpringBootApplication @ComponentScan(basePackages = {"com.example","com.mock"}) @EnableFeignClients(basePackages = {"com.example"}) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}Copy the code

3. Define interfaces and methods

@FeignClient(value = "userService")
public interface UserFeignClient {
    @RequestMapping("/user/id")
    String findById(String id);
}
Copy the code

4. Service invocation

@Resource
private UserFeignClient userFeignClient;
@Override
public String findUser(String id) {
    return userFeignClient.findById(id);
}
Copy the code

In this way, we call microservices as if they were local methods, and the details of the internal HTTP remote calls are all encapsulated in the Feign client.

The source code interpretation

1. Start the scan process

Start the class notes EnableFeignClients introduce FeignClientsRegistrar, this class implements a ImportBeanDefinitionRegistrar interface, in the process of the IOC container startup, The interface is scanned as an implementation class, registered as a Bean Definition, and eventually added to the container.

@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {/ / registered default configuration registerDefaultConfiguration (metadata registry); // Register a FeignClientFactoryBean registerFeignClients(metadata, registry) for each interface that adds FeignClient; }Copy the code

This process does not instantiate the bean into the container; it simply resolves to BeanDefinition first. default.com.example.demo.DemoApplication

The first step, registerDefaultConfiguration () method:

private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// Get the EnableFeignClients attribute Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); // If defaultConfiguration exists, resolve the EnableFeignClients configuration class // then construct beanName for the fully qualified name of the default+ startup class if (defaultAttrs! = null && defaultAttrs.containsKey("defaultConfiguration")) { String name; if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } / / execute register configuration class to the container here (and elsewhere in the following this method used) registerClientConfiguration (registry, name, defaultAttrs.get("defaultConfiguration")); }}Copy the code

RegisterClientConfiguration () method:

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(FeignClientSpecification.class); / / add the name builder. AddConstructorArgValue (name); / / add the specified configuration builder. AddConstructorArgValue (configuration). registry.registerBeanDefinition( name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); }Copy the code

The value of the defaultConfiguration property configured on EnableFeignClients is wrapped into a FeignClientSpecification container.

Let’s look at the FeignClientSpecification class structure. Name is default and Configuration is the configuration class specified by our defaultConfiguration property.

class FeignClientSpecification implements NamedContextFactory.Specification { private String name; private Class<? >[] configuration; }Copy the code

Note that we are wrapping our configured classes into a FeignClientSpecification object, and even though we did not specify the configuration class on the startup class annotation, there will still be a FeignClientSpecification, Its configuration is empty.

The process of registering the default configuration is complete.

Second, the registerFeignClients() method:

EnableFeignClients has several properties that specify FeignClient. The registerFeignClients method is used to obtain packets that need to be scanned.

String[] basePackages() default {}; Class<? >[] basePackageClasses() default {}; Class<? >[] clients() default {};Copy the code

Let’s look at the code that actually registers FeignClient:

  • Parse the configuration of each client
for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); // Get the FeignClient attribute Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); / / in accordance with the priority will configure contextId, value, name, and serviceId value as FeignClint unique name, the name will be bound to the client. String name = getClientName(attributes); The configuration classes specified by each Feign client are resolved here, just as the configuration classes specified by the bootstrap class annotations are resolved. Once again call registerClientConfiguration methods. registerClientConfiguration(registry, name, attributes.get("configuration")); RegisterFeignClient (Registry, annotationMetadata, Attributes); }}}Copy the code

Here again invoked the registerClientConfiguration method, will be for each generate a FeignClientSpecification FeignClient. So, if we had N Feign clients in our program, then the IOC container would have N+1 FeignClientSpecification.

  • Registering clients
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); FeignClientFactoryBean BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); definition.addPropertyValue("url", getUrl(attributes)); definition.addPropertyValue("path", getPath(attributes)); String name = getName(attributes); definition.addPropertyValue("name", name); String contextId = getContextId(attributes); definition.addPropertyValue("contextId", contextId); definition.addPropertyValue("type", className); definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("fallback", attributes.get("fallback")); definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); String alias = contextId + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }Copy the code

Here, the various properties of the client configuration are parsed and placed into the FeignClientFactoryBean object, wrapping the client information again just as you wrapped the configuration classes earlier. Just as BeanDefinition describes a bean, FeignClientFactoryBean describes a Feign client.

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { private Class<? > type; private String name; private String url; private String contextId; private String path; private boolean decode404; private ApplicationContext applicationContext; private Class<? > fallback = void.class; private Class<? > fallbackFactory = void.class; }Copy the code

The FeignClientFactoryBean properties correspond to FeignClient.

Startup process execution flow chart:

Start loading flowchart

1. Register N+1 FeignClientSpecification classes

2. Register the client description class FeignClientFactoryBean

3. Because FeIGN is interface oriented, the interface cannot be instantiated, so the client information is wrapped and the proxy class is obtained through this wrapper class

2. Instantiate the client proxy class

The FeignClientFactoryBean class is the core class in Feign, and the entry to get the proxy class is here.

The getTarget method eventually returns a proxy class that is eventually injected into the IOC container.

<T> T getTarget() { 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

2.1 FeignContext

Class diagram of FeignContext:

Feigncontext class diagram

FeignAutoConfiguration:

The FeignAutoConfiguration can be found in the Spring. factories file in the meta-INF directory of Feign, according to the starter mechanism of SpringBoot.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
Copy the code
public class FeignAutoConfiguration {
   @Autowired(required = false)
   private List<FeignClientSpecification> configurations = new ArrayList<>();
   @Bean
   public FeignContext feignContext() {
      FeignContext context = new FeignContext();
      context.setConfigurations(this.configurations);
      return context;
   }
 }  
Copy the code

In this auto-assembly class, you get all the FeignClientSpecification instances (that is, the default configuration registered in the launch scan and each client configuration) via the container, saving them into the collection Configurations. Set these configurations in the FeignContext constructor. The setConfigurations() method is a method in the FeignContext parent class NamedContextFactory.

NamedContextFactory:

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware { private final String propertySourceName; private final String propertyName; / / for children where private Map < String, AnnotationConfigApplicationContext > contexts = new ConcurrentHashMap < > (); N+1 private Map<String, C> configurations = new ConcurrentHashMap<>(); N+1 private Map<String, C> configurations = new ConcurrentHashMap<>(); private ApplicationContext parent; private Class<? > defaultConfigType;Copy the code

So, all the FeignClientSpecifications are saved into the Configurations collection. Also of concern is the Contexts property, which holds the sub-containers created for each client.

FeignContext is a feign context. There is only one global context, which stores the configuration of each client and its subcontainers.

2.2 Feign. Builder

Builder is an inner class of Feign. Let’s take a look at their structure.

public abstract class Feign { public static class Builder { private final List<RequestInterceptor> requestInterceptors =  new ArrayList<RequestInterceptor>(); private Logger.Level logLevel = Logger.Level.NONE; private Contract contract = new Contract.Default(); private Client client = new Client.Default(null, null); private Retryer retryer = new Retryer.Default(); private Logger logger = new NoOpLogger(); private Encoder encoder = new Encoder.Default(); private Decoder decoder = new Decoder.Default(); private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default(); private ErrorDecoder errorDecoder = new ErrorDecoder.Default(); private Options options = new Options(); private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default(); private boolean decode404; private boolean closeAfterDecode = true; private ExceptionPropagationPolicy propagationPolicy = NONE; }}Copy the code

Most of Feign’s functionality is achieved through Builder, so we just need to focus on Builder. Builder contains the important components for the Client to send and receive requests, including encoder, decoder and Client. Feign can be configured differently for each client, so the components required by each client are saved, and if the program does not explicitly specify a component, the default is used. The components in the Builder will eventually be saved into the proxy classes we generate, and each component will be used to execute the request or parse the response.

Feign Builder Builder = Feign (context)

Protected Feign.Builder Feign (FeignContext context) {// Get decoder, encoder and other components from the child container. FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(this.type); 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)); // Load the decoder, encoder component configureFeign(context, Builder) defined in the configuration file; return builder; }Copy the code

This method can be divided into two steps: one is to get the component configuration from the child container, the other is to get the component configuration from the configuration file. Therefore, the configuration of the configuration file overrides the configuration in the code, which gives us more flexibility to change the components used by the client by changing the configuration file.

  • Load the configuration from the container

Here the get() method is called several times, as follows:

protected <T> T get(FeignContext context, Class<T> type) {
   T instance = context.getInstance(this.contextId, type);
   if (instance == null) {
      throw new IllegalStateException(
            "No bean found of type " + type + " for " + this.contextId);
   }
   return instance;
}
Copy the code

Call the method in the FeignContext parent class NamedContextFactory:

public <T> T getInstance(String name, Class<T> type) {
   AnnotationConfigApplicationContext context = getContext(name);
   if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
         type).length > 0) {
      return context.getBean(type);
   }
   return null;
}
Copy the code

Although the get() method takes FeignContext as an input, it does not get the component instance directly from FeignContext. Instead, it first gets the current client’s child container from FeignContext, and then gets the corresponding instance from the child container.

Now look at the getContext(name) method:

protected AnnotationConfigApplicationContext getContext(String name) { if (! this.contexts.containsKey(name)) { synchronized (this.contexts) { if (! This. Contexts. Either containsKey (name)) {/ / create a container this. Contexts. The put (name, createContext (name)); }}} // Return the current client's subcontainer this.context.get (name); }Copy the code

Child containers are stored in the map and are returned if there is one, or created using createContext(name) if there is none.

Procedure for creating child containers:

Protected AnnotationConfigApplicationContext createContext (String name) {/ / create a context instance AnnotationConfigApplicationContext  context = new AnnotationConfigApplicationContext(); // Register the current client configuration class. Mentioned for each client to create FeignClientSpecification * * * *, it contains the configuration Class we specify the if (this. Configurations. Either containsKey (name)) {for (Class <? > configuration : this.configurations.get(name) .getConfiguration()) { context.register(configuration); }} // Register the default configuration class whose name begins with default (on the EnabledFeignClients annotation for the startup class). for (Map.Entry<String, C> entry : this.configurations.entrySet()) { if (entry.getKey().startsWith("default.")) { for (Class<? > configuration : entry.getValue().getConfiguration()) { context.register(configuration); } } } context.setDisplayName(generateDisplayName(name)); // Refreshing the current container adds our configured bean to the child container. The process of the refresh() method in Spring is too complex to go into detail. context.refresh(); return context; }Copy the code

After the child containers are created, we load the default and the classes we configured for each FeignClient into the child containers, and then refresh the child containers.

Feign creates a sub-container for each client, which stores its component configuration, including encoders, decoders, and so on, to achieve configuration isolation.

Once you get the child container, you can get the components specified in the configuration class from it.

Feign.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)); 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));
Copy the code

Set various components into the Builder by chain programming.

  • Loads the configuration from the configuration file
protected void configureFeign(FeignContext context, Feign.Builder builder) { FeignClientProperties properties = this.applicationContext .getBean(FeignClientProperties.class); // Use the configuration file if (properties! = null) {// We need to determine the value of defaultConfig. // The following branch executes the same code, but in a different order. The following branch overwrites the same configuration as before. // Therefore, if defaultConfig is true, the configuration file is preferred. This is also the default configuration // If defaultConfig is false, the code configuration is preferred. if (properties.isDefaultToProperties()) { configureUsingConfiguration(context, builder); configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(this.contextId), builder); } else { configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(this.contextId), builder); configureUsingConfiguration(context, builder); }} else {/ / use the code in the configuration of configureUsingConfiguration (the context, the builder); }}Copy the code

FeignClientProperties’ internal class, FeignClientConfiguration, is the configuration file class to which configuration items are resolved.

Public class FeignClientProperties {// Whether to use the default configuration private Boolean defaultToProperties = true; // Prefix of configuration file parameters private String defaultConfig = "default"; // Save the set of each configuration. Key is read from the configuration file. private Map<String, FeignClientConfiguration> config = new HashMap<>();Copy the code

Used in properties or YAML configuration files, above is the default configuration, below is the userService configuration, default and userService can be changed depending on the specific service. Example:

# default
feign.client.config.default.decode404=true
feign.client.config.default.connect-timeout=1000
feign.client.config.default.decoder=com.example.demo.feign.config.DefaultDecoder
# userService       
feign.client.config.userService.connect-timeout=111
feign.client.config.userService.decode404=false
feign.client.config.userService.decoder=com.example.demo.feign.config.UserDecoder
Copy the code

The configuration items are read into Map

config = new HashMap<>(). For example, in the configuration above, the key is default and userService.
,>

If it is not configured in a file, the code’s configuration is used directly. If it is configured in the configuration file, it depends on the value of defaultConfig, which affects the priority issue, but whatever is configured in the configuration file for a single service always takes precedence over default.

2.3 Obtaining a Proxy Class

We now only analyze the case without considering load balancing:

return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
Copy the code

First, we create the HardCodedTarget object, which holds the Type,name, and URL.

DefaultTargeter:

class DefaultTargeter implements Targeter { @Override public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { return feign.target(target); }}Copy the code

The target method of feign.Builder is then called. As we analyzed earlier, feign.Builder holds the various components that our client needs, and now the proxy class is created through it as well.

public <T> T target(Target<T> target) { return build().newInstance(target); } public Feign build () {/ / create MethodHandler Factory () SynchronousMethodHandler. Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy); MethodHandler handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }Copy the code

Build () first creates an instance of a factory that creates a MethodHandler; You then create a MethodHandler, which is used to parse the methods in the Feign interface. And then it returns one

ReflectiveFeign.

NewInstance (target) :

Public <T> T newInstance(Target<T> Target) { Each method has a MethodHandler Map < String, MethodHandler > nameToHandler = targetToHandlersByName. Apply (target); // Convert the mapping of the previous name to MethodHandler to the mapping of method to MethodHandler. Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); For (Method Method: target.type().getMethods()) {// Check if it is an Object Method. if (method.getDeclaringClass() == Object.class) { continue; } else if (util.isdefault (method)) {// Check if it is the default method of the interface. Interfaces can have not only abstract methods, but also default methods in which concrete logic can be implemented. DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else {// store converted methodToHandler.put(method, nameToHandler.get(feign.configkey (target.type(), method))); }} // JDK dynamic proxy helper class, created by factoy. InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) proxy.newproxyInstance (target.type().getClassLoader(), new Class<? >[] {target.type()}, handler);Copy the code

for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }

  1. TargetToHandlersByName. Apply (target). TargetToHandlersByName is an internal class of ReflectiveFeign,
public Map<String, MethodHandler> apply(Target key) {// Parsing methods to form metadata List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type()); Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); for (MethodMetadata md : Metadata) {// Create () constructs a MethodHandler result.put(md.configkey (),factory.create(key, md, buildTemplate, options, decoder, errorDecoder)); } return result; }Copy the code

Previously mentioned SynchronousMethodHandler MehodHandler implementation classes, and SynchronousMethodHandler. The Factory is responsible for creating the Factory.

final class SynchronousMethodHandler implements MethodHandler { private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L; private final MethodMetadata metadata; private final Target<? > target; private final Client client; private final Retryer retryer; private final List<RequestInterceptor> requestInterceptors; private final Logger logger; private final Logger.Level logLevel; private final RequestTemplate.Factory buildTemplateFromArgs; private final Options options; private final Decoder decoder; private final ErrorDecoder errorDecoder; private final boolean decode404; private final boolean closeAfterDecode; private final ExceptionPropagationPolicy propagationPolicy; }Copy the code

The created SynchronousMethodHandler object contains the client’s various components, parameters, and method metadata. At this point, we can analyze that the various configuration parameters of the client, whether in the code or through the configuration file, are eventually encapsulated in this object. The Invoke() of this object is also the method that will eventually execute the remote call, using these component parameters.

  1. We know that dynamic proxies in the JDK require a helper class, and that helper class is the one hereInvocationHandler handler = factory.create(target, methodToHandler)It’s created here. There is also a factory for creating helper classes. Create () creates the FeignInvocationHandler object, which is also the inner class of ReflectiveFeign, but it has two properties, target and Dispatch, The type,name, URL,dispatch of the Feign interface in target is a mapping of all the methods in the interface.
private final Target target;
private final Map<Method, MethodHandler> dispatch;
Copy the code
  1. Create the proxy class and return it

2.4 Interface Invocation

We know that the JDK dynamic proxy always calls the invoke() method of the InvocationHandler helper class when calling a method of the target class. In this case, we create the FeignInvocationHandler implementation class object, so we call the Feign interface method invoke() of FeignInvocationHandler.

@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// Method is the method instance corresponding to the currently called method. return dispatch.get(method).invoke(args); }Copy the code
  1. Dispatch.get (method) returns the corresponding MethodHandler for the current method
  2. Invoke (args) executes a MethodHandler method. Here is the SynchronousMethodHandler object, which implements MethodHandler. So we look at the invoke() method of the SynchronousMethodHandler.
@Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); Retryer retryer = this.retryer.clone(); While (true) {try {// Return executeAndDecode(template); } catch (RetryableException e) { try { retryer.continueOrPropagate(e); } catch (RetryableException th) { Throwable cause = th.getCause(); if (propagationPolicy == UNWRAP && cause ! = null) { throw cause; } else { throw th; } } if (logLevel ! = Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; }}}Copy the code

ExecuteAndDecode (Template) is really going to execute the HTTP request.


The whole process can be represented by a sequence diagram:

Feign proxy class creation process

The overall process is to load the configuration files and components for each client at startup, package them into Feign.Builder, create a ReflectiveFeign instance, and map method and MethodHandler into FeignInvocationHandler. To call the FeignClient method, simply fetch the corresponding MethodHandler instance based on method and then execute invoke.