Openfeign source code analysis

summarize

Feign is a declarative, templated HTTP client developed by Netflix, inspired by Retrofit, JAXRS-2.0 and WebSocket. Feign can help us call HTTP API more conveniently and elegantly. Feign supports a variety of annotations, such as Feign's own annotations or JAX-RS annotations. Spring Cloud openfeign enhances Feign to support Spring MVC annotations, and also integrates Ribbon and Eureka, making Feign more convenient to use

OpenFeign source code analysis

Let's first look at the added dependencies

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

Can you see the starter? Seeing this, the first reaction is that it is related to the automatic assembly of springboot. Let's go to the fascories file.

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

FeignAutoConfiguration automatically assembles FeignContext and Targeter, and Client configuration.

FeignContext is the assembly context of each FeignClient. The default configuration is FeignClientsConfiguration.
Targeter has two implementations: one is DefaultTargeter, which calls Feign.Builder directly; the other is HystrixTargeter, which calls HystrixFeign.Builder and turns on the fuse.
Client: ApacheHttpClient and OkHttpClient are automatically assembled. When the assembly conditions are not met, the default is Client.Default. But none of these clients has achieved load balancing.

FeignRibbonClientAutoConfiguration implements load balancing, which is implemented at the Client layer.

HttpClientFeignLoadBalancedConfiguration ApacheHttpClient for load balancing
OkHttpFeignLoadBalancedConfiguration OkHttpClient for load balancing
DefaultFeignLoadBalancedConfiguration Client.Default for load balancing

org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration, org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration : These two automatic assembly classes are about the compression configuration of requests and responses.

Next, we study the source code by thinking about the following questions:

How is the injected interface of @FeignClient parsed and injected?
@Autowired can inject instance objects for @FeignClient, how is it injected, and what object is injected?
After the interface declared by FeingClient is parsed, how is it stored and invoked? How does
OpenFeign integrate Ribbon to achieve load balancing?

Openfeign and springboot inheritance need to be annotated with @EnableFeignClients:

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
@EnableFeignClients
public class XXXApiApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(XXXApiApplication.class, args);
    }

}

So what methods do we know that can inject external beans into the ioc container?

1. importSelector (batch injection of beans, such as the principle of spring boot automatic assembly)
2. ImportBeanDefinitionRegister (dynamically build beans)
3. BeanFactoryPostProcessor (an extension provided by spring, first he must be a Bean)
4. SpringFactoryLoad (the mechanism of spi)

ImportBeanDefinitionRegister can realize the construction of dynamic beans, that is, we can inject the beans we need into the ioc container

Let's click in to see the @EnableFeignClients annotation:

insert image description here
@Import supports three methods
1. Configuration classes with @Configuration (only configuration classes can be imported before version 4.2, and ordinary classes can also be imported after version 4.2) 2.
Implementation of ImportSelector
3. Implementation of ImportBeanDefinitionRegistrar
Here, the FeignClientsRegistrar class is passed through Imported into the ioc container in three ways

Inject FeignClientsRegistrar.class for us via @Impor

FeignClientsRegistrar
insert image description here
implements the ImportBeanDefinitionRegistrar interface and rewrites the registerBeanDefinitions method

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    
    
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}

The registerDefaultConfiguration(metadata, registry); method is mainly to obtain the @EnableFeignClients annotation parameter defaultConfiguration

The point is registerFeignClients(metadata, registry)

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    
    
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;
		//收集该注解的元数据信息:value ,basePackages ,basePackageClasses 等
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		//获取@EnableFeignClients注解中的client属性
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		//如果没有配置client相关属性会进入到这里
		if (clients == null || clients.length == 0) {
    
    
			//添加需要扫描的注解@FeignClient
			scanner.addIncludeFilter(annotationTypeFilter);
			//该方法就是根据@EnableFeignClients注解的属性信息去获取需要扫描的路径
			basePackages = getBasePackages(metadata);
		}
		//基于client属性配置的类以及类所在的包进行扫描
		else {
    
    
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
    
    
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
    
    
				@Override
				protected boolean match(ClassMetadata metadata) {
    
    
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}
		//遍历所有的路径获取标有@FeignClient注解的接口
		for (String basePackage : basePackages) {
    
    
			//找到候选的对象(标有@FeignClient注解的接口)封装成BeanDefinition对象
			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");
					//获取每个接口中定义的元数据信息,即@FeignClient注解中配置的属性值例如,value,name,path,url等
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());
					//获取name的属性值
					String name = getClientName(attributes);
					//注册被调用客户端配置
                    //注册(微服务名).FeignClientSpecification类型的bean
                    //对beanname的名称进行拼接: name.FeignClientSpecification ,例如我们上面获取的naem值等于:${feign.baseInfoManagement.name:baseInfoManagement/baseInfoManagement}
                    //拼接后:${feign.baseInfoManagement.name:baseInfoManagement/baseInfoManagement}.FeignClientSpecification               
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
                    注册 FeignClient 重点分析*****
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

	protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
    
    
		//@EnableFeignClients 元数据信息就是我们在该注解中配置的key:value值
		Map<String, Object> attributes = importingClassMetadata
				.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
		//遍历属性信息,拿到需要扫描的路径
		Set<String> basePackages = new HashSet<>();
		for (String pkg : (String[]) attributes.get("value")) {
    
    
			if (StringUtils.hasText(pkg)) {
    
    
				basePackages.add(pkg);
			}
		}
		for (String pkg : (String[]) attributes.get("basePackages")) {
    
    
			if (StringUtils.hasText(pkg)) {
    
    
				basePackages.add(pkg);
			}
		}
		for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
    
    
			basePackages.add(ClassUtils.getPackageName(clazz));
		}

		if (basePackages.isEmpty()) {
    
    
			basePackages.add(
					ClassUtils.getPackageName(importingClassMetadata.getClassName()));
		}
		return basePackages;
	}

registerFeignClients(metadata, registry) The main steps of this method are as follows:
1. Find FeignClient
2. Get a collection of @FeignClient interfaces
3. Analyze the metadata information in the @FeignClient annotation
4. Traverse these FeignClient interfaces and inject a dynamic Bean instance (implemented by means of dynamic proxy)

registerFeignClient

Inject the proxy class corresponding to the interface into the ioc container: register FeignClient, assemble BeanDefinition, which is essentially a FeignClientFactoryBean, and then register it in the Spring IOC container.

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    
    
			//获取到标注了@FeignClient注解的接口全路径:com.train.service.feign.BaseInfoManagementFeign
		String className = annotationMetadata.getClassName();
		//构建FeignClientFactoryBean类型的BeanDefinitionBuilder
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
	    //属性值校验
		validate(attributes);
		//将属性设置到 FeignClientFactoryBean 中,也就是我们在@FeignClient中配置的属性值
		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"));
		//设置 Autowire注入的类型,按类型注入
		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
																// null

		beanDefinition.setPrimary(primary);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
    
    
			alias = qualifier;
		}
        //将BeanDefinition包装成BeanDefinitionHolder,用于注册
		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] {
    
     alias });
		//注册 BeanDefinition  
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

The analysis here is actually just to encapsulate our proxy class into a BeanDefinition object, but it is not instantiated. In spring, we know that the class loaded into the IOC container must first be encapsulated into a BeanDefinition object, because we not only need the class of the class Information, and some other attributes may also be configured, such as @Lazy, @DependsOn, etc. Then the follow-up must be instantiated through sring, so I won’t do too much analysis here. The main process is as follows:

The Spring container starts, calls the AbstractApplicationContext#refresh method, and
calls the finishBeanFactoryInitialization method inside the refresh method to initialize the singleton bean. The
finishBeanFactoryInitialization method calls getBean to obtain the bean instance corresponding to the name. If it does not exist, create one, that is, call the doGetBean method.
doGetBean calls the createBean method, and the createBean method calls the doCreateBean method.
The doCreateBean() method is mainly based on the beanName, mbd, args, using the corresponding strategy to create a bean instance, and returns the wrapper class BeanWrapper.
In the doCreateBean method, populateBean is called to fill the properties of the bean; among them, there may be properties that depend on other beans, and the dependent bean instances will be recursively initialized, and the initialization phase involves the implementation of three-level cache and AOP.

FeignClientFactoryBean

Above we analyze the BeanDefinitionBuilder that will build the FeignClientFactoryBean type, so what is this object, we will analyze it next:
insert image description here
First of all, we can see that it implements the three classes that provide us with extensions in Spring, namely FactoryBean, InitializingBean, and ApplicationContextAware

The role of InitializingBean
1: spring provides two ways to initialize beans for beans, implement the InitializingBean interface, implement the afterPropertiesSet method, or specify the same init-method in the configuration file, the two methods can be used at the same time 2: implementing the InitializingBean interface is
direct Calling the afterPropertiesSet method is relatively more efficient than calling the method specified by init-method through reflection. But the init-method method eliminates the dependence on spring
3: If an error occurs when calling the afterPropertiesSet method, the method specified by init-method will not be called.

ApplicationContextAware
through its Spring container will automatically call the context object setApplicationContext method in the ApplicationContextAware interface, you can get the Bean in the Spring container through this context object

The role of FactoryBean
implements the factoryBean interface and calls the getObject method to create a bean

	@Override
	public Object getObject() throws Exception {
    
    
		return getTarget();
	}
	<T> T getTarget() {
    
    
	//从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,
	//它是用来统一维护feign中各个feign客户端相互隔离的上下文。
	//FeignContext注册到容器是在FeignAutoConfiguration上完成的。
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		
		//构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。
		//FeignContext在上文中已经提到会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器。
		Feign.Builder builder = feign(context);
        //如果url为空,则走负载均衡,生成有负载均衡功能的代理类 (重点分析*****)
		if (!StringUtils.hasText(this.url)) {
    
    
			if (!this.name.startsWith("http")) {
    
    
				this.url = "http://" + this.name;
			}
			else {
    
    
				this.url = this.name;
			}
			this.url += cleanPath();
			//@FeignClient没有配置url属性,返回有负载均衡功能的代理对象
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
		//如果指定了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));
	}


	protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
    
    
		// 从上下文中获取一个 Client,默认是LoadBalancerFeignClient。
		//它是在FeignRibbonClientAutoConfiguration这个自动装配类中,通过Import实现的
		Client client = getOptional(context, Client.class);
		if (client != null) {
    
    
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			//这里就是为每个接口创建代理类这里有两个实现 HystrixTargeter 、DefaultTargeter 
			//很显然,如果没有配置 Hystrix ,这里会走 DefaultTargeter
			return targeter.target(this, builder, context, target);
		}

		throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
	}

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);
	}

}

Feign

public <T> T target(Target<T> target) {
    
    
	//创建一个动态代理类,最终会调用 ReflectiveFeign.newInstance
      return build().newInstance(target);
    }

public Feign build() {
    
    
//这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。会解析我们在每个接口中定义的参数,方法类型等。
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      //ReflectiveFeign创建一个动态代理类
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }

//target就是我们的原始接口
public <T> T newInstance(Target<T> target) {
    
    
//根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
//nameToHandle集合包含的属性可以看下面的图进行理解
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    //遍历接口中的所有方法
    for (Method method : target.type().getMethods()) {
    
    
    //如果是Object中提供的方法,跳过
      if (method.getDeclaringClass() == Object.class) {
    
    
        continue;
        //判断是不是接口中的默认方法
      } else if (Util.isDefault(method)) {
    
    
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
    
    
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    // 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {
    
    target.type()}, handler);

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

In the previous analysis, we know that OpenFeign finally returns a ReflectiveFeign.FeignInvocationHandler object. Then when the client initiates a request, it will enter the FeignInvocationHandler.invoke method, which everyone knows is an implementation of a dynamic proxy.
OpenFeign calling process:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
      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();
      }
// 利用分发器筛选方法,找到对应的handler 进行处理,也就是根据请求目标对应的url找到需要执行的方法进行调用
      return dispatch.get(method).invoke(args);
    }

SynchronousMethodHandler.invoke
And then, in the invoke method, this.dispatch.get(method)).invoke(args) will be called. this.dispatch.get(method) will return a SynchronousMethodHandler for interception processing. This method will generate the completed RequestTemplate object according to the parameters. This object is the template of the Http request. The code is as follows

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    
    
  	//得到RequestTemplate 对象
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    //重试机制的实现
    Retryer retryer = this.retryer.clone();
    while (true) {
    
    
      try {
    
    
      	//请求的调用 执行调用和解码工作
        return executeAndDecode(template, options);
      } 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;
      }
    }
  }

  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    
    
  //获取Request对象
    //此处也是自定义拦截器的实现
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
    
    
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
    
    
    //发起远程通信
    //这里的 client.execute 的 client 的类型是LoadBalancerFeignClient
    //走到这里就是我们前门分析的ribbon那一套了,这里不做说明
      response = client.execute(request, options);
    } catch (IOException e) {
    
    
      if (logLevel != Logger.Level.NONE) {
    
    
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
	获取返回结果,并解析
    boolean shouldClose = true;
    try {
    
    
      if (logLevel != Logger.Level.NONE) {
    
    
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
      }
      if (Response.class == metadata.returnType()) {
    
    
        if (response.body() == null) {
    
    
          return response;
        }
        if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
    
    
          shouldClose = false;
          return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
      }
      if (response.status() >= 200 && response.status() < 300) {
    
    
        if (void.class == metadata.returnType()) {
    
    
          return null;
        } else {
    
    
          Object result = decode(response);
          shouldClose = closeAfterDecode;
          return result;
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
    
    
        Object result = decode(response);
        shouldClose = closeAfterDecode;
        return result;
      } else {
    
    
        throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {
    
    
      if (logLevel != Logger.Level.NONE) {
    
    
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {
    
    
      if (shouldClose) {
    
    
        ensureClosed(response.body());
      }
    }
  }

The main steps above are that we will generate a proxy class for each interface through dynamic proxy based on the interface defined by the @FeignClient annotation. Each proxy class saves the corresponding management of the URL and the target method to call the corresponding method.

Overall process:

The main program entry adds the @EnableFeignClients annotation to enable the scanning and loading of FeignClient. According to the development specification of Feign Client, define the interface and add @FeignClient annotation.
When the program starts, it will scan the package, scan all @FeignClients annotated classes, and inject this information into the Spring IOC container. When the method in the defined Feign interface is called, it will be generated through the JDK dynamic proxy method. Concrete RequestTemplate. When generating a proxy, Feign will create a RequestTemplate object for each interface method, which encapsulates all the information required by the HTTP request, such as the request parameter name, request method and other information are determined in this process.
Then RequestTemplate generates a Request, and then hands the Request to the Client for processing. The Client referred to here can be JDK’s native URLConnection, Apache’s HttpClient, or OKhttp. Finally, the Client is encapsulated into the LoadBalanceClient class, which combines Ribbon load balancing to initiate services between calls.

Finally attach the schematic
insert image description here

Next: The use of OpenFeign

Guess you like

Origin blog.csdn.net/qq_42600094/article/details/130653752