Spring Cloud Open Feign series [6] ribbon configuration class source code analysis

Posted by Love_Daddy on Tue, 28 Dec 2021 15:36:26 +0100

Ribbon auto configuration class

Based on the automatic configuration function provided by Spring Boot, the Ribbon automatically configured classes are available at org.com springframework. cloud. netflix. The RibbonClientConfiguration and RibbonAutoConfiguration configuration classes of the Ribbon package.

RibbonAutoConfiguration

RibbonAutoConfiguration is equivalent to global configuration. It mainly loads Ribbon client factory, configuration class factory, retry mechanism factory, etc. this configuration class will be loaded when it is started.

First, take a look at the comments on the RibbonAutoConfiguration configuration class:

// Marked as configuration class Bean
@Configuration
// Inject Bean conditions, and the RibbonClassesConditions class determines whether to inject the Bean
@Conditional({RibbonAutoConfiguration.RibbonClassesConditions.class})
// @RibbonClients provides default configuration for all Ribbon clients, @ RibbonClients marks that this class is registered as a RibbonClientSpecification
@RibbonClients
//  Load the current class after loading the configured class
@AutoConfigureAfter(
    name = {"org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration"}
)
// Load the current class before loading the configured class
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
// Make RibbonEagerLoadProperties and ServerIntrospectorProperties configuration classes effective
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})

This class has two properties and uses @ Autowired injection

    @Autowired(
        required = false
    )
    // List collection, type RibbonClientSpecification, and inject the configuration class using @ RibbonClients.
    private List<RibbonClientSpecification> configurations = new ArrayList();
    // Ribbon's hungry load mode configuration class. Ribbon's Client for Client load balancing does not start when the service is started
    // The corresponding Client is created only when it is called, and the load starvation mode is enabled, which can be created in advance.
    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

Next, take a look at the Bean objects injected by this configuration class:

	// HasFeatures can view some features of the system startup to understand the system characteristics.
    @Bean
    public HasFeatures ribbonFeature() {
        return HasFeatures.namedFeature("Ribbon", Ribbon.class);
    }
    //  Spring creates a factory for Ribbon clients, load balancers, and client configuration instances
    @Bean
    @ConditionalOnMissingBean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }
    // LoadBalancerClient is a load balancer client provided by spring cloud. It can query all services and select one
    @Bean
    @ConditionalOnMissingBean({LoadBalancerClient.class})
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(this.springClientFactory());
    }
    // Ribbon retry factory
    @Bean
    @ConditionalOnClass(
        name = {"org.springframework.retry.support.RetryTemplate"}
    )
    @ConditionalOnMissingBean
    public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
        return new RibbonLoadBalancedRetryFactory(clientFactory);
    }
    // It is used to obtain the client configuration class, such as interface com netflix. loadbalancer. IRule rule configuration class, corresponding to an attribute = < NFLoadBalancerRuleClassName >
    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
        return new PropertiesFactory();
    }
    // If eager load mode is configured, the RibbonApplicationContextInitializer context initialization object is registered
    @Bean
    @ConditionalOnProperty({"ribbon.eager-load.enabled"})
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(this.springClientFactory(), this.ribbonEagerLoadProperties.getClients());
    }


RibbonClientConfiguration

RibbonClientConfiguration is the Ribbon client related configuration. This class will not be loaded until the first Feign client request is executed.

For example, if the Feign client is currently order service and it makes a request for the first time, it will enter the method to obtain the client configuration:

Then get the context of the changed client in the NamedContextFactory. If not, it will be created.

    protected AnnotationConfigApplicationContext getContext(String name) {
    // Does the current context contain the client order service
        if (!this.contexts.containsKey(name)) {
            synchronized(this.contexts) {
            	// If not, create and place
                if (!this.contexts.containsKey(name)) {
                    this.contexts.put(name, this.createContext(name));
                }
            }
        }
		// There is a direct return
        return (AnnotationConfigApplicationContext)this.contexts.get(name);
    }

Finally, the createContext method of NamedContextFactory is called to create a context for each client.

Through the NamedContextFactory mechanism, different sub applicationcontexts are created according to different applications, so as to isolate and not affect each other. Each Feign client is configured with different application contexts, so that each different client can use different configurations and different beans.

After the createContext method creates the Context of the client and enters the refresh, it will start loading the beans, pre and post processors and other processes directly required by the current Context. RibbonClientConfiguration is loaded at this time.

	// Create a Context object for a proxy object corresponding to the @ FeignClient interface before obtaining it
    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // Query the configuration class of @ RibbonClients in RibbonAutoConfiguration
        if (this.configurations.containsKey(name)) {
        	// If there is a configuration with the name of the current client in the configuration class, add the configuration and register it in the context
            Class[] var3 = ((NamedContextFactory.Specification)this.configurations.get(name)).getConfiguration();
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Class<?> configuration = var3[var5];
                context.register(new Class[]{configuration});
            }
        }
		// The configuration class of @ RibbonClients is recycled
        Iterator var9 = this.configurations.entrySet().iterator();

        while(true) {
            Entry entry;
            do {
                if (!var9.hasNext()) {
                	// register
                    context.register(new Class[]{PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType});
                    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName, Collections.singletonMap(this.propertyName, name)));
                    if (this.parent != null) {
                        context.setParent(this.parent);
                        context.setClassLoader(this.parent.getClassLoader());
                    }

                    context.setDisplayName(this.generateDisplayName(name));
                    // Refresh context
                    context.refresh();
                    return context;
                }

                entry = (Entry)var9.next();
             // The configuration class does not start with default At the beginning, it enters the do loop. There is no
            } while(!((String)entry.getKey()).startsWith("default."));
			
            Class[] var11 = ((NamedContextFactory.Specification)entry.getValue()).getConfiguration();
            int var12 = var11.length;
			// Cycle through the default configuration and register
            for(int var7 = 0; var7 < var12; ++var7) {
                Class<?> configuration = var11[var7];
                context.register(new Class[]{configuration});
            }
        }
    }

You can see that there are two @ RibbonClients configuration classes by default:

After creating the context, you can see the context information of the current client.

The RibbonClientConfiguration class uses @ Import to Import other configuration classes.

@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})

The properties of the RibbonClientConfiguration class are as follows:

	// The default connection timeout is 1 second
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
    // The default read timeout is 1 second
    public static final int DEFAULT_READ_TIMEOUT = 1000;
    public static final boolean DEFAULT_GZIP_PAYLOAD = true;
    // @Ribbonclientname ribbonclientname
    @RibbonClientName
    private String name = "client";
    // Configuration factory, injected by RibbonAutoConfiguration
    @Autowired
    private PropertiesFactory propertiesFactory;

Finally, let's take a look at the beans injected by RibbonClientConfiguration:

	// Current client configuration 
    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
    	// Default configuration class
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        // load configuration
        config.loadProperties(this.name);
        // set configuration 
        config.set(CommonClientConfigKey.ConnectTimeout, 1000);
        config.set(CommonClientConfigKey.ReadTimeout, 1000);
        config.set(CommonClientConfigKey.GZipPayload, true);
        return config;
    }
	
	// Load the load balancing algorithm. The default is ZoneAvoidanceRule polling.
    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, this.name)) {
            return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
        } else {
            ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
            rule.initWithNiwsConfig(config);
            return rule;
        }
    }
	// Ping detection service health status
    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        return (IPing)(this.propertiesFactory.isSet(IPing.class, this.name) ? (IPing)this.propertiesFactory.get(IPing.class, config, this.name) : new DummyPing());
    }
	// Ribbon's service list 
    @Bean
    @ConditionalOnMissingBean
    public ServerList<Server> ribbonServerList(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerList.class, this.name)) {
            return (ServerList)this.propertiesFactory.get(ServerList.class, config, this.name);
        } else {
            ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
            serverList.initWithNiwsConfig(config);
            return serverList;
        }
    }
	// Service list Updater
    @Bean
    @ConditionalOnMissingBean
    public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
        return new PollingServerListUpdater(config);
    }
	// Load balancer ILoadBalancer
    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
        return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
    }
	// ServerListFilter service list filter of the five components of Ribbon's LoadBalancer
    @Bean
    @ConditionalOnMissingBean
    public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerListFilter.class, this.name)) {
            return (ServerListFilter)this.propertiesFactory.get(ServerListFilter.class, config, this.name);
        } else {
            ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
            filter.initWithNiwsConfig(config);
            return filter;
        }
    }
	// Load balancer context
    @Bean
    @ConditionalOnMissingBean
    public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer, IClientConfig config, RetryHandler retryHandler) {
        return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
    }
	// Retry processor
    @Bean
    @ConditionalOnMissingBean
    public RetryHandler retryHandler(IClientConfig config) {
        return new DefaultLoadBalancerRetryHandler(config);
    }
	// Service interceptor
    @Bean
    @ConditionalOnMissingBean
    public ServerIntrospector serverIntrospector() {
        return new DefaultServerIntrospector();
    }
	// Post Processors 
    @PostConstruct
    public void preprocess() {
        RibbonUtils.setRibbonProperty(this.name, CommonClientConfigKey.DeploymentContextBasedVipAddresses.key(), this.name);
    }

summary

Project startup = > RibbonAutoConfiguration (load factory class, default configuration) = > first access = > proxy client = > initialize the client context = > RibbonClientConfiguration (load service list, filter, interceptor, client configuration, etc.) = > process the request.

Topics: Spring Boot Spring Cloud eureka