一、@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