Ribbon系列二、RibbonClient注解及SpringClientFactory源码分析

1,956 阅读7分钟

一、RibbonClient的使用

Ribbon作为一个负载均衡组件, 里面有一个个的负载均衡策略, 而这些策略的公共接口是IRule, 我们在开始
学习Ribbon的时候, 必然会接触到这个IRule接口, 通过往Spring容器中注入一个IRule接口的实现类, 可以
改变Ribbon默认的负载均衡策略(默认是轮询), 假设有这么一种场景, 我们有一个购物车服务CartService以
及一个订单服务OrderService, 我期望调用端在调用CartService的时候, 负载均衡策略为随机策略, 而调用
订单服务还是采用默认的策略, 那么我们可能会这样做:

@RibbonClient( value = "CartService", configuration = MyRibbonConfiguration.class )
@Configuration
public class SpringConfig () {}

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

通过上面的方式, 我们就使得对于CartService的调用, 负载均衡策略就变成了随机策略了, 接下来我们就来
分析下Ribbon到底是怎么来区分不同服务的负载均衡相关配置的

二、RibbonClientConfigurationRegistrar源码分析

2.1、@Import注解和RibbonClientConfigurationRegistrar

@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClient {
	String value() default "";

	String name() default "";

	Class<?>[] configuration() default {};
}

分析: 如果对Spring源码有所了解的话, 就可以知道, @Import注解有三种使用方式, 第一种是value为一个
Spring的配置类, 比如一个类A里面有一个@Bean方法, 当@Import注解中的value为这个类A的时候, 就会使得
@Bean返回的对象被注入到Spring容器中

第二种是value为ImportSelector的实现类, 这个函数式接口有一个selectImports方法, 返回的是一个
String类型的数组, 这个数组中应该是一个个的全路径类名, Spring会通过反射的方式将这些类注入到容器
中

第三种是value为ImportBeanDefinitionRegistrar接口的实现类, 这个函数式接口有一个
registerBeanDefinitions方法, 该方法会有一个BeanDefinitionRegistry参数, 通过这个参数可以使得我
们手动的注入Beanefinition到容器中

对于Import注解的解析是在一个BeanFactoryPostProcessor中完成的, 这些都是跟Spring相关的了, 如果对
这些都熟悉的话, 那么我们接下来的分析大家就会觉得很简单了

RibbonClientConfigurationRegistrar属于第三种, 这个类完成的作用就是, 解析RibbonClient注解或者
RibbonClients注解, 将一个个的RibbonClient注解中的内容封装成一个RibbonClientSpecification对象,
并注入到容器中

public class RibbonClientSpecification implements NamedContextFactory.Specification {
	private String name;
	private Class<?>[] configuration;
}

可以看到, 其实就是将RibbonClient中的name和configuration保存成RibbonClientSpecification的属性
而已, 接下来我们分析RibbonClientConfigurationRegistrar中的registerBeanDefinitions方法

2.2、registerBeanDefinitions第一段代码

public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    Map<String, Object> attrs = metadata
            .getAnnotationAttributes(RibbonClients.class.getName(), true);
    if (attrs != null && attrs.containsKey("value")) {
        AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
        for (AnnotationAttributes client : clients) {
            registerClientConfiguration(registry, getClientName(client),
                    client.get("configuration"));
        }
    }
}

分析:
    首先从@Import(RibbonClientConfigurationRegistrar.class)标注的类或者该注解的父注解标注的类
    中查找是否有RibbonClients注解, 如果有, 并且value不为空, 说明RibbonClients注解中有
    RibbonClient注解, 于是将这些注解一个个的取出来, 利用getClientName方法对RibbonClient注解
    进行解析, 得到里面的name或者value, 以及RibbonClient注解中的configuration, 进而通过
    registerClientConfiguration构造一个RibbonClientSpecification对象并注入到Spring容器中

private String getClientName(Map<String, Object> client) {
    if (client == null) {
        return null;
    }
    String value = (String) client.get("value");
    if (!StringUtils.hasText(value)) {
        value = (String) client.get("name");
    }
    if (StringUtils.hasText(value)) {
        return value;
    }
}

分析: 
    很简单, 就是取得RibbonClient注解中的name或者value

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());
}

分析:
    生成一个RibbonClientSpecification的BeanDefinition, 设置构造参数name为RibbonClient中的
    name或者value, configuration为RibbonClient中的configuration属性, 这样以后一旦Spring根据
    这个BeanDefinition创建了bean对象后, 里面属性中的name和configuration就是RibbonClient中的
    name/value和configuration了

    注入到Spring容器中的时候, RibbonClient中的name/value 加上.RibbonClientSpecification作为
    beanName, 指定了构造参数值后的RibbonClientSpecification作为bean

在我们最开始的那个例子中, 其实就是注入了一个beanName为CartService.RibbonClientSpecification,
bean实例如下样子的bean到Spring容器:
public class RibbonClientSpecification implements NamedContextFactory.Specification {
	private String name = "CartService";
	private Class<?>[] configuration = MyRibbonConfiguration.class
}

2.3、registerBeanDefinitions第二段代码

public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    if (attrs != null && attrs.containsKey("defaultConfiguration")) {
        String name;
        if (metadata.hasEnclosingClass()) {
            name = "default." + metadata.getEnclosingClassName();
        }
        else {
            name = "default." + metadata.getClassName();
        }
        registerClientConfiguration(registry, name,
                attrs.get("defaultConfiguration"));
    }
}

分析: 如果RibbonClients注解中配置了defaultConfiguration, 即默认的配置, 我们结合下面这个例子来
说明这段代码的功能:
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {}

当RibbonClients注解被解析的时候, 里面的@Import方法触发第二段代码的时候, 就会注入一个beanName为
default.RibbonEurekaAutoConfiguration.RibbonClientSpecification, bean为如下样子的bean到
Spring容器中:
public class RibbonClientSpecification implements NamedContextFactory.Specification {
	private String name = "default.RibbonEurekaAutoConfiguration";
	private Class<?>[] configuration = RibbonEurekaAutoConfiguration.class
}

2.4、registerBeanDefinitions第三段代码

public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    Map<String, Object> client = metadata
            .getAnnotationAttributes(RibbonClient.class.getName(), true);
    String name = getClientName(client);
    if (name != null) {
        registerClientConfiguration(registry, name, client.get("configuration"));
    }
}

分析:
    第三段代码就很简单了, 就是解析类上的RibbonClient注解

2.5、小小的总结

RibbonClientConfigurationRegistrar的registerBeanDefinitions方法, 一共完成了三个事情, 第一个
是解析@RibbonClients注解中的RibbonClient注解, 第二个是解析RibbonClients注解中的
defaultConfiguration属性, 第三个是解析RibbonClient注解, 第一个和第三个是不一样的, 前者是在
@RibbonClients中的value中, 而后者是直接标注在类上的

解析完毕后会封装成一个个的RibbonClientSpecification对象注入到Spring容器中, 该对象的name就是
@RibbonClient注解中的name / value, configuration就是@RibbonClient注解中的configuration

那么这一个个的RibbonClientSpecification对象到底有什么作用呢??接下来就要分析
SpringClientFactory这个类的作用了

三、SpringClientFactory源码分析

3.1、SpringClientFactory的创建

public class RibbonAutoConfiguration {
	@Autowired(required = false)
	private List<RibbonClientSpecification> configurations = new ArrayList<>(); 

    @Bean
	public SpringClientFactory springClientFactory() {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}    
}

在RibbonAutoConfiguration这个类中, 往Spring容器中注入了一个SpringClientFactory, 并且从Spring
容器中找到所有的RibbonClientSpecification, 并放入到SpringClientFactory中

SpringClientFactory, 是一个工厂类, 根据javadoc中的描述, 它的作用是: 
    创建客户端, 负载均衡器和客户端配置实例的工厂, 它为每个客户端名称创建一个Spring
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {}

先来看看其父类NamedContextFactory的作用吧, 根据javadoc的描述:
    创建一组次级的Spring容器上下文,可以使得每个次级Spring容器上下文定义一组专门的配置bean

用通俗的话来讲, 就是NamedContextFactory中会有一个这样的Map:
    Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
使得每类服务都可以拥有自己独立的Spring上下文环境来保存该类服务独有的配置, 比如CartSevice可以作为
这个map的key, 对应一个AnnotationConfigApplicationContext, 在这个context中保存CartService独有
的配置

3.2、NamedContextFactory源码分析

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
    private Map<String, AnnotationConfigApplicationContext> contexts 
                                        = new ConcurrentHashMap<>();
	private Map<String, C> configurations = new ConcurrentHashMap<>();
	private ApplicationContext parent;
	private Class<?> defaultConfigType;
}

首先分析四个属性, 第一个contexts表示不同的key可以拥有自己独立的Spring上下文, configurations表示
不同的服务可以有自己独有的NamedContextFactory.Specification, 在上面我们分析RibbonClients原理的
时候, 可以看到最后会创建一个个的RibbonClientSpecification, 而RibbonClientSpecification的父类
就是NamedContextFactory.Specification, 所以可以理解为, 不同类型的服务对应的负载均衡配置就存储
在这个configurations中, 在最上面的例子的条件下, 这个configurations中会保存一条记录为:
    key: CartService
    value: RibbonClientSpecification{ 
                 name: "CartService", configuration: MyRibbonConfiguration.class
           }

parent表示父容器, 即项目初始化所创建的Spring容器, 我们真正使用容器

defaultConfigType为RibbonClientConfiguration, 在我们创建SpringClientFactory的时候会设置进去,
表示Ribbon客户端默认的配置

public class RibbonAutoConfiguration {
	@Autowired(required = false)
	private List<RibbonClientSpecification> configurations = new ArrayList<>(); 

    @Bean
	public SpringClientFactory springClientFactory() {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}    
}

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
    public void setConfigurations(List<C> configurations) {
        for (C client : configurations) {
            this.configurations.put(client.getName(), client);
        }
    }

}

通过上面的代码, 我们可以验证, 最终NamedContextFactory的configurations对应的Map存储的就是通过
扫描RibbonClient等注解创建的RibbonClientSpecification对象

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
    public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                type).length > 0) {
            return context.getBean(type);
        }
        return null;
    }

    protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}
}

分析: 通过name从Map<String, AnnotationConfigApplicationContext> contexts 这个Map中找到对应
的Spring上下文, 然后从这个Spring上下文中获取type对应的bean对象, 这就是getInstance方法的作用,
如果这个Spring上下文不存在则会创建并放入到Map中, createContext就不分析了, 有兴趣的话可以看下,
其实就是判断configurations中存不存在name对应的配置, 如果存在就注入到这个次级的上下文中, 其次,
注入configurations中以default开头的配置到这个次级的上下文中(default开头的配置其实就是
RibbonClients中定义的defaultConfiguration属性值), 最后调用context.refresh方法刷新Spring容器

3.3、SpringClientFactory源码分析

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
    public SpringClientFactory() {
		super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
	}

    public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
		return getInstance(name, clientClass);
	}

    public ILoadBalancer getLoadBalancer(String name) {
		return getInstance(name, ILoadBalancer.class);
	}

    public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
		return getInstance(serviceId, RibbonLoadBalancerContext.class);
	}
}

分析: 
    可以看到, 其实SpringClientFactory就是对父类的getInstance进行了一下封装而已, 最终其实就是
    根据name从父类的所有次级Spring上下文中找到对应的上下文(如果不存在则创建), 然后从上下文中找到
    对应的bean对象, 其次在构造方法中指定了父类的defaultConfigType为RibbonClientConfiguration

四、总结

RibbonClients注解和RibbonClient注解通过RibbonClientConfigurationRegistrar来进行解析, 最终会
以RibbonClientSpecification类对象的形式存储在Spring容器中, 在RibbonAutoConfiguration中, 创建
了SpringClientFactory, 该对象提供了一种命名空间形式的配置保存方式, 不同名称拥有不同的Spring上下
文环境, 在该上下文环境中保存了对应名称的配置