This is the fifth day of my participation in the More text Challenge. For details, see more text Challenge

General documentation: Article directory Github: github.com/black-ant

A. The preface

Article purpose:

  • Feign main process source analysis
  • Feign’s key points
  • Design ideas and diffusion thinking at Feign

2. Source code combing

In the most basic OpenFeign example, we usually do the following:

// Start the Feign client
@EnableFeignClients
@SpringBootApplication
public class ComGangCloudTemplateOrderApplication {... }// Step 2: Prepare FeignClient
@Component
@FeignClient("product-server")
public interface ProductFeignClient {
    @GetMapping("/template/get")
    CloudTemplateEntity get(@RequestParam("desc") String desc);
}

// Step 3: Call FeignClient object
@Autowired
private ProductFeignClient productFeignClient;
productFeignClient.get("order-server")

Copy the code

Looking at these three steps, we can probably get a few questions:

  1. What is the role of EnableFeignClients?
  2. @ FeignClient scanning

2.1 EnableFeignClients using @enablefeignclients

The key configuration is in the FeignClientsRegistrar and will include the following:

First look at the call process for FeignClientsRegistrar:

  • RegisterBeanDefinitions by calling registerBeanDefinitions when the Bean is loaded
  • Step 2: registerDefaultConfiguration registered config
  • Step 3: Scan FeignClient and register
  • Step 4: Register all scanned FeignClients

RegisterBeanDefinitions by calling registerBeanDefinitions when the Bean is loaded

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // There are two main steps involved:
    // 1. Register Configuration
    registerDefaultConfiguration(metadata, registry);
    // 2. Register Feign Client
    registerFeignClients(metadata, registry);
}
Copy the code

Step 2: registerDefaultConfiguration registered config

// 1. Get attribute above annotation -> PS:001
Map<String, Object> defaultAttrs = 
    metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

// 2. Register BeanDefinitionBuilder (PS: This step actually builds a BeanDefinitionBuilder)
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
    
    Definitionbuilder (DefinitionBuilder); // Define BeanDefinitions using the constructor pattern
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientSpecification.class);
    // name : default.com.gang.cloud.template.demo.ComGangCloudTemplateOrderApplication
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(configuration);
    // default.com.gang.cloud.template.demo.ComGangCloudTemplateOrderApplication.FeignClientSpecification
    registry.registerBeanDefinition(
        name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition()
    );
        
}


Copy the code

PS: 001 defaultAttrs parameters

Step 3: Scan FeignClient

The overall outline is:

  1. Get the attribute attrs from EnableFeignClients
  2. Obtain the basic scan path basePackages through the attribute attrs
  3. Scan all classes labeled @Component under basePackages
  4. Get the @feignClient in there
  5. Registry the class annotated by the annotation
// The annotation class is FeignClientsRegistrarC05- FeignClientsRegistrar(AnnotationMetadata metadata,BeanDefinitionRegistry registry) M5_01- registerFeignClients : FeignClient core mode - ready to object ClassPathScanningCandidateComponentProvider - get marked EnableFeignClients Map collections - build a AnnotationTypeFilter ? - why not use getAnnotationAttributes to get the related class directly -> PS:M5_01_01 - getBasePackages to get the path Set<String>->LV001 FOR- LV001 - findCandidateComponents ->LV002? -> PS:M5_01_02 FOR- loop BeanDefinition: LV002 - Get FeignClient attribute Map<String, Object > - > LV003 - registerClientConfiguration currently registered configuration information - registerFeignClient FeignClient M5_02 - registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) ? - The core function is to construct a BeanDefined, which can be divided into4Step - > PS: M5_02_011- BeanDefinitionBuilder. GenericBeanDefinition build a BeanDefinitionBuilder2- definition. GetBeanDefinition () to build a AbstractBeanDefinition3- newBeanDefinitionHolder {DefinitionHolder;4- BeanDefinitionReaderUtils. RegisterBeanDefinition inject beansCopy the code

That’s the main process, but let’s take a look at a few small details:

// PS: omitted part of the code, you can see the full version of the source code
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // Prepare scan objects for scanning
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);

    // This is an exclusion filter for Scan. It will mask useless beans
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
                                
    if (clients == null || clients.length == 0) {
        // Add an exclusion filter -> PS:0002
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    } else {
        // Omit, again for special matching
    }

    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                // Get the attribute -> PS:0003
                Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(FeignClient.class.getCanonicalName());
                / / register FeignClientregisterFeignClient(registry, annotationMetadata, attributes); }}}}// PS:0002 excludes the use of the filter
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    for (TypeFilter tf : this.includeFilters) {
        / /...}}Copy the code

Step 3: Scan FeignClient


private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map
       
         attributes)
       ,> {
    String className = annotationMetadata.getClassName();
    
    / / prepare BeanDefinitionBuilder - > org. Springframework. Cloud. Openfeign. FeignClientFactoryBean
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    // Check whether the attribute is valid
    validate(attributes);
                
    // Omit all addPropertyValue operations, which add properties to definition

    String alias = contextId + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

    // Since there is a default BeanDefinition, this is overwritten by primary
    boolean primary = (Boolean) attributes.get("primary");
    beanDefinition.setPrimary(primary);

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
        new String[] { alias });
    // Register the given bean definition with the given bean factory
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
Copy the code

PS: This object is called during the Bean creation process, we will talk about it later!!

2.2 FeignAutoConfiguration configuration

Feign actually supports OKHttp calls. This method is configured in the FeignAutoConfiguration class, which provides two connection frames: HttpClientFeignConfiguration / OkHttpFeignConfiguration

/ / HttpClientFeignConfiguration configuration items
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(ApacheHttpClientConnectionManagerFactory connectionManagerFactory,FeignHttpClientProperties httpClientProperties) {
    final HttpClientConnectionManager connectionManager = connectionManagerFactory
        .newConnectionManager(httpClientProperties.isDisableSslValidation(),
        // Maximum number of connections
        httpClientProperties.getMaxConnections(),
        httpClientProperties.getMaxConnectionsPerRoute(),
        // Connect the lifetimes and units
        httpClientProperties.getTimeToLive(),
        httpClientProperties.getTimeToLiveUnit(),
        this.registryBuilder);
        
        this.connectionManagerTimer.schedule(new TimerTask() {
                @Override
                public void run(a) { connectionManager.closeExpiredConnections(); }},30000, httpClientProperties.getConnectionTimerRepeat());
        
        return connectionManager;
}

@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
    
    RequestConfig defaultRequestConfig = RequestConfig.custom()
        .setConnectTimeout(httpClientProperties.getConnectionTimeout())
        .setRedirectsEnabled(httpClientProperties.isFollowRedirects())
        .build();
        
        this.httpClient = httpClientFactory.createBuilder()
            .setConnectionManager(httpClientConnectionManager)
            .setDefaultRequestConfig(defaultRequestConfig).build();
            
        return this.httpClient;
}


// Configuration items of OkHttpFeignConfiguration
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool( FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) {
    // OKHttp connection pool property
    Integer maxTotalConnections = httpClientProperties.getMaxConnections();
    Long timeToLive = httpClientProperties.getTimeToLive();
    TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
    return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
}

@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
    
    Boolean followRedirects = httpClientProperties.isFollowRedirects();
    Integer connectTimeout = httpClientProperties.getConnectionTimeout();
    Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
    this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
        .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
        .followRedirects(followRedirects).connectionPool(connectionPool)
        .build();
        
    return this.okHttpClient;
}

Copy the code

So here’s the question: how do you switch to OKHttp?

PS: There is a method on the Internet, through the configuration of okHttp3.okHttpClient method, but after personal testing, may be due to the version of different problems

// If you look closely at the source code, there are two OkHttpFeignConfiguration
/ / a within FeignAutoConfiguration, another exists in org. Springframework. Cloud. Openfeign. Clientconfig package

// Suspicious: click FeignAutoConfiguration$OkHttpFeignConfiguration. There are multiple conditionals on the configuration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {
    / /...
}

// [Pro] : if you pass the OnClassCondition test, you will find that ILoadBalancer exists, and the configuration under this class will not be loadedIn org. Springframework. Cloud. Openfeign. Clientconfig configuration under the package, have the following requirements@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)

// Therefore, the known configuration will actually cause both configuration items to stay



/ /!!!!!! So how do you configure it?



// Step 1: Add eign-okhttp configuration
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>


// Step 2: Import the configuration file on Application
@Import(value = {OkHttpFeignConfiguration.class})


// Step 3: Register Client in config
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureAfter(value = {FeignAutoConfiguration.class, OkHttpFeignConfiguration.class})
public class FeignOkHttpConfig {

    @Bean
    @ConditionalOnMissingBean(Client.class)
    public Client feignClient(okhttp3.OkHttpClient client) {
        // There are some related configurations, which will be omitted temporarily and added when writing the process
        return new OkHttpLoadBalancingClient(....);
    }
}


Copy the code

conclusion

That’s all for the Configuration section of Feign, which explains the creation of a FeignBean and an Invoke process

The appendix

PS:0003 Attributes include what attributes?

C- FeignClient
    M- String value(a) default ""
    M- String serviceId(a) default ""
    M- String contextId(a) default"" : If present, it will be used as the bean name rather than the name, but not as the service ID. M- Stringname(a) default ""
    M- String qualifier(a) default ""
    M- String url(a) default ""
    M- boolean decode404(a) default falseM- Class<? > []configuration(a): A configuration class customized for the virtual client. M- Class<? >fallback(a)M- Class<? >fallbackFactory(a)
    M- String path(a) default ""
    M- boolean primary(a) default true
        

Copy the code

Common FeignClient configuration

// Override the default configuration
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
link : https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign-overriding-defaults    
    
// Obtain the rollback cause
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
    
// Obtain the cause of triggering the rollback
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",fallbackFactory = TestFallbackFactory.class)

// Configure the main Bean
// PS: When using Feign and Spring Cloud CircuitBreaker to roll back, there are multiple beans in the same type of ApplicationContext
@FeignClient(name = "hello", primary = false)    

Copy the code

Application configuration article

feign:
    client:
        config:
            feignName:
                connectTimeout: 5000
                readTimeout: 5000
                loggerLevel: full
                errorDecoder: com.example.SimpleErrorDecoder
                retryer: com.example.SimpleRetryer
                defaultQueryParameters:
                    query: queryValue
                defaultRequestHeaders:
                    header: headerValue
                requestInterceptors:
                    - com.example.FooRequestInterceptor
                    - com.example.BarRequestInterceptor
                decode404: false
                encoder: com.example.SimpleEncoder
                decoder: com.example.SimpleDecoder
                contract: com.example.SimpleContract
                capabilities:
                    - com.example.FooCapability
                    - com.example.BarCapability
                metrics.enabled: false
                
                
// Debug get/set to see the node in use
@ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties {

	// Disable the default value for SSL authentication
	public static final boolean DEFAULT_DISABLE_SSL_VALIDATION = false;

        // The default value of the maximum NUMBER of od connections
	public static final int DEFAULT_MAX_CONNECTIONS = 200;

	// The default maximum number of connections for each route
	public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 50;

	// The default value of the time to live
	public static final long DEFAULT_TIME_TO_LIVE = 900L;

	// Default survival time unit.
	public static final TimeUnit DEFAULT_TIME_TO_LIVE_UNIT = TimeUnit.SECONDS;

	// Whether to redirect
	public static final boolean DEFAULT_FOLLOW_REDIRECTS = true;

	// The default value for connection timeout
	public static final int DEFAULT_CONNECTION_TIMEOUT = 2000;

	// Connect the timer to repeat the default values
	public static final int DEFAULT_CONNECTION_TIMER_REPEAT = 3000;

	private boolean disableSslValidation = DEFAULT_DISABLE_SSL_VALIDATION;

	private int maxConnections = DEFAULT_MAX_CONNECTIONS;

	private int maxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE;

	private long timeToLive = DEFAULT_TIME_TO_LIVE;

	private TimeUnit timeToLiveUnit = DEFAULT_TIME_TO_LIVE_UNIT;

	private boolean followRedirects = DEFAULT_FOLLOW_REDIRECTS;

	private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;

	private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT;
        
}

@ConfigurationProperties("feign.client")
public class FeignClientProperties {

	private boolean defaultToProperties = true;
	private String defaultConfig = "default";
	private Map<String, FeignClientConfiguration> config = new HashMap<>();
        
}       
          
public static class FeignClientConfiguration {

    private Logger.Level loggerLevel;

    private Integer connectTimeout;

    private Integer readTimeout;

    private Class<Retryer> retryer;

    private Class<ErrorDecoder> errorDecoder;

    private List<Class<RequestInterceptor>> requestInterceptors;

    private Boolean decode404;

    private Class<Decoder> decoder;

    private Class<Encoder> encoder;

    private Class<Contract> contract;

    private ExceptionPropagationPolicy exceptionPropagationPolicy;         
    
    / /...
    
}
          
// Step 1: Config-configured classes
FeignClientConfiguration     
FeignHttpClientProperties
FeignClientProperties


// Step 2: The configuration position
C- FeignClientFactoryBean
    M- configureUsingProperties
    M- configureUsingConfiguration
                
Copy the code