Ribbon源码阅读(二)Ribbon如何实现服务定制化配置

810 阅读4分钟

一、@RibbonClient实现定制化服务配置

案例

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {Application.class, SpecificationTest.RibbonClientTestConfiguration.class})
public class SpecificationTest {
    @Autowired
    private SpringClientFactory springClientFactory;

    @Test
    public void testRibbonSpecification() {
        // 默认ZoneAvoidanceRule
        IRule irule1 = springClientFactory.getInstance("stock-service", IRule.class);
        System.out.println(irule1.getClass());// com.netflix.loadbalancer.ZoneAvoidanceRule
        // RandomRule
        IRule irule2 = springClientFactory.getInstance("trade-service", IRule.class);
        System.out.println(irule2.getClass());// com.netflix.loadbalancer.RandomRule
    }

    @RibbonClient(name = "trade-service", configuration = SpecificationConfiguration.class)
    static class RibbonClientTestConfiguration {

    }

    static class SpecificationConfiguration {
        @Bean
        public IRule iRule(){
            return new RandomRule();
        }
    }
}

@RibbonClient如何做到服务定制化配置

RibbonClientSpecification

RibbonClientSpecification主要是包装两个属性

  • name:服务名(也对应了org.springframework.cloud.context.named.NamedContextFactory#contexts的key,也就是命名容器的名字)
  • configuration:自定义的配置类数组,里面可以写一些@Bean注解修饰的方法用于向之后的服务独立ioc容器注入bean
public class RibbonClientSpecification implements Specification {
    private String name;
    private Class<?>[] configuration;
}

@RibbonClient

@RibbonClient注解主要是import了RibbonClientConfigurationRegistrar

@Import({RibbonClientConfigurationRegistrar.class})
public @interface RibbonClient {
    String value() default "";
    String name() default "";
    Class<?>[] configuration() default {};
}

RibbonClientConfigurationRegistrar

RibbonClientConfigurationRegistrar负责将@RibbonClient的configuration数组对应的类,注册到全局spring容器中

public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    	// 1. 解析RibbonClients注解,注册RibbonClients注解里的配置到Spring容器,这里省略了,因为和RibbonClient类似
		// 2. 解析RibbonClient注解,注册RibbonClient注解里的配置到Spring容器
        Map<String, Object> client = metadata.getAnnotationAttributes(RibbonClient.class.getName(), true);
        // 优先取注解的value属性作为name,其次取name属性作为name
        String name = this.getClientName(client); 
        if (name != null) {
        	// 注册到RibbonClientSpecification实例到Spring父容器
            this.registerClientConfiguration(registry, name, client.get("configuration"));
        }

    }
	// 注册RibbonClientSpecification
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RibbonClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + ".RibbonClientSpecification", builder.getBeanDefinition());
    }
}

SpringClientFactory

SpringClientFactory装配的时候会注入所有的RibbonClientSpecification,具体逻辑在RibbonAutoConfiguration自动配置中。

public class RibbonAutoConfiguration {
	// 父容器中所有RibbonClientSpecification
    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList();

    @Bean
    @ConditionalOnMissingBean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        // 这里注入SpringClientFactory
        factory.setConfigurations(this.configurations);
        return factory;
    }
}

当创建子容器的时候,NamedContextFactory会过滤出name对应的所有特殊配置,注册到子容器中。

protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 根据name过滤出所有RibbonClientSpecification的configurations,并注册到新的子容器中
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
        // name以default.开头的也会注册到子容器中
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
        // 省略其他逻辑
        ...
		context.refresh();
		return context;
	}

二、配置文件实现定制化服务配置

案例

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {Application.class, SpecificationTest.RibbonClientTestConfiguration.class},
        value = {"trade-service.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule"})
public class SpecificationTest {
    @Autowired
    private SpringClientFactory springClientFactory;

    @Test
    public void testRibbonSpecification() {
        // 默认
        IRule irule1 = springClientFactory.getInstance("stock-service", IRule.class);
        System.out.println(irule1.getClass());// com.netflix.loadbalancer.ZoneAvoidanceRule
        // 根据配置文件自定义
        IRule irule2 = springClientFactory.getInstance("trade-service", IRule.class);
        System.out.println(irule2.getClass());// com.netflix.loadbalancer.RoundRobinRule
    }
}

SpringClientFactory

SpringClientFactory在他创建子容器的时候,会直接注册RibbonClientConfiguration到子容器中

// 父类NamedContextFactory.createContext
protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		// 这里注册了this.defaultConfigType
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.refresh();
		return context;
	}

this.defaultConfigType的来源,正是构造SpringClientFactory的时候传入的第一个参数

 	public SpringClientFactory() {
        super(RibbonClientConfiguration.class, "ribbon", "ribbon.client.name");
    }

RibbonClientConfiguration

看一下RibbonClientConfiguration对于IRule的注入逻辑

public class RibbonClientConfiguration {
    @RibbonClientName
    private String name = "client";
    @Autowired
    private PropertiesFactory propertiesFactory;
    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(this.name);
        return config;
    }
    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
    	// 如果配置了trade-service.ribbon.NFLoadBalancerRuleClassName,走反射实例化对应的IRule实现类
        if (this.propertiesFactory.isSet(IRule.class, this.name)) {
            return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
        } else {
        	// 如果没有配置,走默认的ZoneAvoidanceRule
            ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
            rule.initWithNiwsConfig(config);
            return rule;
        }
    }
 }
  • @RibbonClientName:上一章提到过,他其实就是@Value("${ribbon.client.name}"),而ribbon.client.name是在创建子容器的时候,默认注入子容器的一个配置,取的是子容器的name。
  • PropertiesFactory:维护自定义配置,可以通过SpringClientFactory.instantiateWithConfig方法反射实例化指定的类。
  • IClientConfig:保存子容器的配置文件
  • IRule:负载均衡策略

PropertiesFactory

大致看一下PropertiesFactory

  • 维护了class-配置key的映射关系
  • 操作SpringClientFactory实例化Bean
public class PropertiesFactory {
    @Autowired
    private Environment environment;
    private Map<Class, String> classToProperty = new HashMap();
	// 构造的时候就维护好 class-配置key的关系
    public PropertiesFactory() {
        this.classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
        this.classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
        this.classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
        this.classToProperty.put(ServerList.class, "NIWSServerListClassName");
        this.classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
    }
	// this.propertiesFactory.isSet(IRule.class, this.name) 
    // name就是子容器name,就是服务名称
    public boolean isSet(Class clazz, String name) {
        return StringUtils.hasText(this.getClassName(clazz, name));
    }
	// 从容器environment中查找servicename.ribbon.NFLoadBalancerClassName对应的value
    public String getClassName(Class clazz, String name) {
        if (this.classToProperty.containsKey(clazz)) {
            String classNameProperty = (String)this.classToProperty.get(clazz);
            String className = this.environment.getProperty(name + "." + "ribbon" + "." + classNameProperty);
            return className;
        } else {
            return null;
        }
    }
	// 操作SpringClientFactory反射实例化Bean,之前一章讲过
    public <C> C get(Class<C> clazz, IClientConfig config, String name) {
        String className = this.getClassName(clazz, name);
        if (StringUtils.hasText(className)) {
            try {
                Class<?> toInstantiate = Class.forName(className);
                return SpringClientFactory.instantiateWithConfig(toInstantiate, config);
            } catch (ClassNotFoundException var6) {
                throw new IllegalArgumentException("Unknown class to load " + className + " for class " + clazz + " named " + name);
            }
        } else {
            return null;
        }
    }
}

三、优先级

  • 配置文件配置:RibbonClientConfiguration作为子容器创建时必定注入的配置,里面的各种Bean注册都是被@ConditionalOnMissingBean修饰的,所以优先级低于注解配置。
  • 注解配置:作为特殊配置,直接注入子容器,优先级高于配置文件。

四、自己实现一个@RibbonClient

  • MyRibbonClient:引入MyRibbonClientConfigurationRegistrar
@Configuration(proxyBeanMethods = false)
@Import({MyRibbonClientConfigurationRegistrar.class})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRibbonClient {
    String value() default "";
    Class<?>[] configuration() default {};
}
  • MyRibbonClientConfigurationRegistrar:注册MyRibbonClientSpecification
public class MyRibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
    public MyRibbonClientConfigurationRegistrar() {
    }
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        Map<String, Object> client = metadata.getAnnotationAttributes(MyRibbonClient.class.getName(), true);
        if (client == null) {
            return;
        }
        String name = (String)client.get("value");
        if (name != null) {
            this.registerClientConfiguration(registry, name, client.get("configuration"));
        }
    }

    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyRibbonClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + ".MyRibbonClientSpecification", builder.getBeanDefinition());
    }
}
  • MyRibbonClientSpecification:封装服务名称和特殊配置类configuration
public class MyRibbonClientSpecification implements NamedContextFactory.Specification {
    private final String name;
    private final Class<?>[] configuration;

    public MyRibbonClientSpecification(String name, Class<?>[] configuration) {
        this.name = name;
        this.configuration = configuration;
    }
    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public Class<?>[] getConfiguration() {
        return this.configuration;
    }
}
  • 测试
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {Application.class, SpecificationTest.RibbonClientTestConfiguration.class},
        value = {"trade-service.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RoundRobinRule"})
public class SpecificationTest {
    // 自定义的
    @Autowired
    private MyContextFactory myContextFactory;
    // ribbon的
    @Autowired
    private SpringClientFactory springClientFactory;

    @Test
    public void testRibbonSpecification() {
        // ribbon不同客户端配置不同的负载均衡策略
        IRule irule1 = springClientFactory.getInstance("stock-service", IRule.class);
        System.out.println(irule1.getClass());// com.netflix.loadbalancer.ZoneAvoidanceRule
        // 注解形式的优先级高于配置文件
        // 因为org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration.ribbonRule 是ConditionOnMissingBean
        IRule irule2 = springClientFactory.getInstance("trade-service", IRule.class);
        System.out.println(irule2.getClass());// com.netflix.loadbalancer.RandomRule
    }

    @Test
    public void testMySpecification() {
        IRule irule1 = myContextFactory.getInstance("stock-service", IRule.class);
        System.out.println(irule1.getClass());// com.netflix.loadbalancer.ZoneAvoidanceRule
        IRule irule2 = myContextFactory.getInstance("trade-service", IRule.class);
        System.out.println(irule2.getClass());// com.netflix.loadbalancer.RandomRule
    }

    @RibbonClient(name = "trade-service", configuration = SpecificationConfiguration.class)
    @MyRibbonClient(value = "trade-service", configuration = SpecificationConfiguration.class)
    static class RibbonClientTestConfiguration {

    }

    static class SpecificationConfiguration {
        @Bean
        public IRule iRule(){
            return new RandomRule();
        }
    }
}

五、总结

  • Ribbon实现客户端自定义配置的两种方式包括注解形式和配置文件形式,前者优先级高于后者
  • 这些自定义配置主要依赖于SpringClientFactory的两个功能,一个是每个服务容器隔离,一个是反射实例化bean