Ribbon源码阅读(一)SpringClientFactory命名容器工厂

2,412 阅读3分钟

1 NamedContextFactory抽象命名容器工厂

Creates a set of child contexts that allows a set of Specifications to define the beans in each child context.Ported from spring-cloud-netflix FeignClientFactory and SpringClientFactory

这是命名工厂的抽象父类,由spring-cloud-context提供,根据文档意思是:通过这个工厂可以创建定制化spring子容器,服务于spring-cloud-netflix的Feign和Ribbon。因为这是Ribbon和Feign的基础,先来看一下这个类的源码。

1.1 成员变量

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
	// PropertySource的name属性,由子类构造方法传入
	private final String propertySourceName;
	// 首个property的key,由子类构造方法传入
	private final String propertyName;
	// 自定义的name(对于Ribbon来说就是服务名称) 映射 Spring容器
	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
	// 自定义的name(对于Ribbon来说就是服务名称) 映射 Specification,目的是独立Spring容器启动时,注入一些个性化的Configuration,对于Ribbon来说就是可以针对单独服务,设置IRule、IPing等
	private Map<String, C> configurations = new ConcurrentHashMap<>();
	// 父容器,ApplicationContextAware被调用时设置,一般就是我们普通的Spring应用,名字是application-1
	private ApplicationContext parent;
	// 容器启动时,注入的一个配置类,由子类构造方法传入
	private Class<?> defaultConfigType;

}

1.2 唯一构造方法,必须子类实现

	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName) {
		this.defaultConfigType = defaultConfigType; // 一个之后会注册到子容器中的配置类
		this.propertySourceName = propertySourceName;// 一个子容器中的propertySource名字
		this.propertyName = propertyName;// 对于Ribbon这个是读取@RibbonClientName获取ribbon.client.name的关键,对于子容器来说是一个普通的properties中的key,对应的value就是子容器创建时传入的name
	}

1.3 关键方法createContext:根据自定义name,创建Spring容器

	protected AnnotationConfigApplicationContext createContext(String name) {
		// 1. 先new一个AnnotationConfigApplicationContext
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		// 2. 注册name对应的自定义Configuration类
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
		// 3. 注册default.开头命名的name对应的配置
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		// 4. 注册PropertyPlaceholderAutoConfiguration和构造方法传入的默认配置类
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		// 5. PropertySource初始化
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));
		// 6. 设置父容器
		if (this.parent != null) {
			context.setParent(this.parent);
		}
		// 7. 刷新容器
		context.refresh();
		return context;
     }

2 SpringClientFactory-Ribbon命名容器工厂实现

SpringClientFactory是作为ribbon的自动配置项引入的,主要目的有几个

  • 根据service名称,创建不同的容器,使不同的服务可以使用不同的配置,比如不同的负载均衡策略。这是NamedContextFactory抽象父类主要提供的功能。
  • 通过IClientConfig,反射实例化一些bean。这是Ribbon命名工厂的特殊的功能。

2.1 构造方法(重要)

public SpringClientFactory() {
     super(RibbonClientConfiguration.class, "ribbon", "ribbon.client.name");
 }
  • RibbonClientConfiguration.class: 是子容器会自动注入的配置类,提供了默认的负载均衡相关的抽象概念的实现,包括IRule、ServerList等等。先大致浏览一下,它就是注册了默认bean。
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
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);
      config.set(CommonClientConfigKey.ConnectTimeout, 1000);
      config.set(CommonClientConfigKey.ReadTimeout, 1000);
      config.set(CommonClientConfigKey.GZipPayload, true);
      return config;
  }
  @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;
      }
  }
  ...
  • ribbon.client.name:这个是@RibbonClientName注解(一个@Value("${ribbon.client.name}")的注解)获取子容器name的关键

2.2 getInstance方法:根据serviceName和Bean类型获取Bean实例

 	public <C> C getInstance(String name, Class<C> type) {
 	    // 1. 首先尝试从NamedContextFactory的getInstance获取实例
        C instance = super.getInstance(name, type);
        if (instance != null) {
            return instance;
        } else {
        	// 2. 通过反射实例化
            IClientConfig config = (IClientConfig)this.getInstance(name, IClientConfig.class);
            return instantiateWithConfig(this.getContext(name), type, config);
        }
    }

2.3 instantiateWithConfig方法:通过反射实例化对象,利用Spring容器注入依赖

 static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context, Class<C> clazz, IClientConfig config) {
        Object result = null;
        // 1. 尝试用有IClientConfig的构造方法实例化对象,如果成功直接返回
        try {
            Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
            result = constructor.newInstance(config);
        } catch (Throwable var5) {
        }
        // 2. 无参构造实例化对象
        if (result == null) {
            result = BeanUtils.instantiateClass(clazz);
            // 2-1. IClientConfigAware的initWithNiwsConfig方法执行
            if (result instanceof IClientConfigAware) {
                ((IClientConfigAware)result).initWithNiwsConfig(config);
            }
            // 2-2. 利用Spring容器做依赖注入
            if (context != null) {
                context.getAutowireCapableBeanFactory().autowireBean(result);
            }
        }

        return result;
    }

2.4 工具方法

提供一些获取ILoadBalancer、IClientConfig、RibbonLoadBalancerContext、IClient的包装方法,主要还是调用getInstance,例如:

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

3 实现一个自己的NamedContextFactory

目标:实现一个自己的NamedContextFactory,让不同的容器可以使用不同的配置

3.1 自定义NamedContextFactory

public class MyContextFactory extends NamedContextFactory<MyRibbonClientSpecification> {
   public MyContextFactory() {
       super(DefaultConfiguration.class, "myRibbon", "myRibbon.client.name");
   }
}
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;
   }
}

3.2 定义一个子容器默认会注入的配置类

public class DefaultConfiguration {
    @Value("${myRibbon.client.name}") // 服务名称
//    @RibbonClientName
    private String name = "client";
	// 每个服务会有不同的配置
    @Bean
    public IClientConfig config() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl("myRibbon");
        config.loadProperties(this.name);
        config.set(CommonClientConfigKey.ConnectTimeout, 1000);
        config.set(CommonClientConfigKey.ReadTimeout, 1000);
        config.set(CommonClientConfigKey.GZipPayload, true);
        return config;
    }
	// 使用ConfigurationBasedServerList,是通过读取配置文件的方式获取服务实例列表的
    @Bean
    @ConditionalOnMissingBean
    public ServerList<Server> ribbonServerList(IClientConfig config) {
        ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
        serverList.initWithNiwsConfig(config);
        return serverList;
    }
}

3.3 测试类

stock-service.myRibbon.listOfServers=127.0.0.1:111相当于在application.properties里加入了这行配置。stock-service是创建子容器时传入的name,代表服务名称。myRibbonDefaultClientConfigImpl config = new DefaultClientConfigImpl("myRibbon")传入的名称空间。listOfServers是Ribbon自己的配置属性,ConfigurationBasedServerList会读取这个配置,来获取ServerList。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {Application.class},
        value={"stock-service.myRibbon.listOfServers=127.0.0.1:111", "stock-service.ribbon.listOfServers=127.0.0.1:456"})
public class SpringClientFactoryTest {
    @Autowired
    private MyContextFactory myContextFactory;
    @Autowired
    private SpringClientFactory springClientFactory;
    @Test
    public void testMyContextFactory() {
        // 1. 自定义ContextFactory
        ServerList serverList1 = myContextFactory.getInstance("stock-service", ServerList.class);
        System.out.println(serverList1.getInitialListOfServers()); // 127.0.0.1:111
        // 2. springClientFactory
        ServerList serverList2 = springClientFactory.getInstance("stock-service", ServerList.class);
        System.out.println(serverList2.getInitialListOfServers()); // 127.0.0.1:456
    }
}

附ConfigurationBasedServerList部分源码

public class ConfigurationBasedServerList extends AbstractServerList<Server>  {

	private IClientConfig clientConfig;
		
	@Override
	public List<Server> getInitialListOfServers() {
	    return getUpdatedListOfServers();
	}

	// 获取服务列表,通过配置文件里的<clientName>.<nameSpace>.listOfServers
	@Override
	public List<Server> getUpdatedListOfServers() {
        String listOfServers = clientConfig.get(CommonClientConfigKey.ListOfServers);
        return derive(listOfServers);
	}

	// 配置ConfigurationBasedServerList时会主动调用
	@Override
	public void initWithNiwsConfig(IClientConfig clientConfig) {
	    this.clientConfig = clientConfig;
	}