The last article, Playing with OpenFeign, covered some common configurations of OpenFeign, but left out a few things.

This article mainly introduces how to configure different connection timeout, read timeout parameters for different clients, and from the source point of view of the configuration is how to work, and what parameters can be configured, the content is not much.

Or from FeignAutoConfiguration this configuration, the configuration class using the @ EnableConfigurationProperties registered the two beans for loading OpenFeign configuration, FeignClientProperties, FeignHttpClientProperties.

@EnableConfigurationProperties(
      { FeignClientProperties.class,
		FeignHttpClientProperties.class})
public class FeignAutoConfiguration {}Copy the code
  • FeignHttpClientProperties: Used for configurationHttpClient.HttpClientIs really used to initiateHttpThe requested client utility class.

If the Client is using the Default Default OpenFeign, is due to the Default of the Client using HttpClient HttpURLConnection, so FeignHttpClientProperties this configuration is not used to. Do not use the Default value.

If Client use OpenFeign is OkHttpClient, FeignHttpClientProperties for loading OkHttpClient connection pool, connection timeout configuration.

  • FeignClientProperties: for eachClientConfigure connection timeouts, read timeouts, retries, request interceptors, etc. You can view the supported configuration parametersFeignClientPropertiesThe inner classFeignClientConfigurationWhat are the fields?

FeignClientProperties is used to receive parameters such as connection timeout, read timeout, retries, request interceptors, encoders, decoders configured for each Client in the application.yaml configuration file.

It is not recommended to configure retries, request interceptors, etc., in application.yaml because the class names of retries and request interceptors are configured in application.yaml. OpenFeign gets the Bean based on the class name from the ApplicationContext from which the Spring Boot application is launched, without using the Client-isolated ApplicationContext provided by OpenFeign.

This is not recommended unless you want all clients to use the same retrayer and request interceptor. Since you need to create these retrackers and request interceptors in code, it is better to configure them directly in code. In the previous article, we explained how to configure different retries and request interceptors for different clients through code.

The connection timeout and read timeout parameters can be set in code by injecting a Request.Options Bean into the Client’s ApplicationContext.

First, create the automatic Configuration class Configuration, inject Request.Options into the container, and configure connection timeout and read timeout for Request.Options.

@AutoConfigureBefore(FeignClientsConfiguration.class)
public class DefaultFeignConfig {

    @Bean
    public Request.Options options(a) {
        return new Request.Options(
                // Connection timeout configuration
                5, TimeUnit.SECONDS,
                // Read timeout configuration
                6, TimeUnit.SECONDS,
                // If the request responds to 3xx, whether to redirect the request
                false); }}Copy the code

Do not annotate @Configuration on this Configuration class, because this is not registered with the ApplicationContext of the application, but with the ApplicationContext OpenFeign creates for the Client.

Finally, add the configuration class to the @FeignClient annotation’s Configuration property.

@FeignClient(name = "demo",
        path = "/v1",
        url = "${fegin-client.demo-url}",
        configuration = {DefaultFeignRetryConfig.class,
           / / import DefaultFeignConfig
            DefaultFeignConfig.class})
public interface DemoService {}Copy the code

The connection timeout and read timeout parameters of the Client are set in application.yaml.

# configure feign to use okHTTP
feign:
  okhttp:
    enabled: true
Configure connection timeouts separately for each Client
  client:
    Make the properties configuration take precedence
    default-to-properties: true
    config:
      service1:
        connectTimeout: 6000
        readTimeout: 5000
      service2:
        connectTimeout: 6000
        readTimeout: 5000
      .
Copy the code

The class that receives feign.client.config configuration is FeignClientConfiguration.

@ConfigurationProperties("feign.client")
public class FeignClientProperties {
	private boolean defaultToProperties = true;
	private String defaultConfig = "default";
	// key: Client --> FeignClientConfiguration
	private Map<String, FeignClientConfiguration> config = new HashMap<>();
}
Copy the code

The config field is a map that supports multiple configurations. The key configured for each Client is the Client name (service provider name) and the value type is FeignClientConfiguration. You can set parameters such as connectTimeout and readTimeout.

How does the source code analysis configuration work

OpenFeign scans the interface annotated with the @FeignClient annotation and injects a FeignClientFactoryBean into each Client’s respective ApplicationContext. The getObject method of the FeignClientFactoryBean returns the code object of the interface.

FeignClientFactoryBean creates a feign.Builder when it creates a proxy object for the interface, and then uses this Feign.Builder to create the proxy object.

FeignClientFactoryBean reads the configuration when it creates feign.Builder, writes the configuration to Feign.Builder, and feign.Builder uses the configuration when it creates the proxy object. This is finally used to create a SynchronousMethodHandler for the method interceptor.

FeignClientFactoryBean populates feign.Builder with the source code below.

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean.ApplicationContextAware {
	
	privateClass<? > type;private String name;
	private String url;
	private String contextId;
	private String path;
	private boolean decode404;
	private boolean inheritParentContext = true;
	private ApplicationContext applicationContext;
	privateClass<? > fallback =void.class;
	privateClass<? > fallbackFactory =void.class;
	
    protected void configureFeign(FeignContext context, Feign.Builder builder) {
		FeignClientProperties properties = this.applicationContext
				.getBean(FeignClientProperties.class);

		FeignClientConfigurer feignClientConfigurer = getOptional(context,
				FeignClientConfigurer.class);
		setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
		// When the configuration is not empty and inheritParentContext is true
		// inheritParentContext defaults to true
		if(properties ! =null && inheritParentContext) {
		    // Default-to-properties is set to true
			if (properties.isDefaultToProperties()) {
			    // Use the bean injected by the Configuration class
				configureUsingConfiguration(context, builder);
				// Use the default Settings
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				// Override the previous configuration with the configuration added in application.yaml
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
			}
			// If default-to-properties is false,
			// The bean injected using the Configuration class overrides the Configuration in application.yaml
			else {
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId), builder); configureUsingConfiguration(context, builder); }}else{ configureUsingConfiguration(context, builder); }}}Copy the code

Yaml: default-to-properties set to true in application. Yaml: default-to-properties set to true in application. Yaml: default-to-properties set to true in application.

If the project is used in the Ribbon, then FeignRibbonClientAutoConfiguration will inject a Request. The Options, when the default – to – the properties configured to false, This Request.Options will override the configuration added in application.yaml. Therefore, the configuration takes effect only when default-to-properties is set to true.

The inheritParentContext parameter is set to true by default. When inheritParentContext is false, none of the configuration we added in application.yaml takes effect.

How do I set inheritParentContext to false?

Inject a FeignClientConfigurer Bean into the configuration class specified in the configuration attribute of the @FeignClient annotation. Implement FeignClientConfigurer inheritParentConfiguration method of the interface in the method returns false.

@FeignClient(name = "demo",
        path = "/v1",
        url = "${fegin-client.demo-url}",
        configuration = {DefaultFeignConfig.class})
public interface DemoService {}Copy the code

Register FeignClientConfigurer in the DefaultFeignConfig configuration class.

@AutoConfigureBefore(FeignClientsConfiguration.class)
public class DefaultFeignConfig {

    @Bean
    public FeignClientConfigurer feignClientConfigurer(a) {
        return new FeignClientConfigurer() {
            @Override
            public boolean inheritParentConfiguration(a) {
                return false; }}; }}Copy the code

The real concern is the method interceptor (SynchronousMethodHandler) for the created proxy object. Feign.Builder passes the Request.Options object that encapsulates the connection timeout and read timeout configurations to the SynchronousMethodHandler when it is created. The Request.Options configuration object is passed by the SynchronousMethodHandler to a Client, such as OkHttpClient, when an HTTP Request is made.

public final class OkHttpClient implements Client {
   // This is an HttpClient, not to be confused with OpenFeign's Client
   private final okhttp3.OkHttpClient delegate;
  
  // Initiates the request with a SynchronousMethodHandler call
  @Override
  public feign.Response execute(feign.Request input, feign.Request.Options options)
      throws IOException {
    okhttp3.OkHttpClient requestScoped;
    // If the connection timeout configured globally is different from the connection timeout configured for the current Client, re-create OkHttpClient.
    // Use the same connection pool
    if(delegate.connectTimeoutMillis() ! = options.connectTimeoutMillis() || delegate.readTimeoutMillis() ! = options.readTimeoutMillis() || delegate.followRedirects() ! = options.isFollowRedirects()) { requestScoped = delegate.newBuilder() .connectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS) .readTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS) .followRedirects(options.isFollowRedirects()) .build(); }else {
      requestScoped = delegate;
    }
    Request request = toOkHttpRequest(input);
    Response response = requestScoped.newCall(request).execute();
    returntoFeignResponse(response, input).toBuilder().request(input).build(); }}Copy the code

Each Client is implemented in a different way. The implementation of OkHttpClient is to recreate HttpClient when the connection timeout configured globally is different from the connection timeout configured by the current Client. That is, the OkHttpClient is recreated to actually initiate Http requests. The same connection pool is used.