客户管理系统开发定制在跟踪源码的过程中看到了 NamedContextFactory,客户管理系统开发定制不懂其存在的精髓,客户管理系统开发定制特此记录下。
在SpringCloud中,客户管理系统开发定制微服务之间由于系统的不同,客户管理系统开发定制可能对于来说可能需要不同的配置,比如订单系统 A 和库存系统 B,ribbon请求A,B可能需要的连接超时时间重试次数是不一致的,这个时候怎么做到ribbon请求A,B系统
的时候使用不同的配置呢。这就引入了NamedContextFactory。
从注释上来看:
它可以创建一系列的子容器,允许一系列的 在每个子容器中定义自己的bean。可以参见FeignClientFactory,SpringClientFactory。就是从netflix借鉴来的。
Ribbon使用的是SpringClientFactory。
在这个NamedContextFactory 内部还有一个内部接口:Specification
注释的意思是:使用name,configuration区分的规范。
总的来说:NamedContextFactory 维护某个客户端使用的子容器的集合, 配置,获取容器中的bean等。
其实这个内容有点类似nacos的group,Specification用于配置分组,一个Specification实例是一个配置组,按name区分。NamedContextFactory 里面维护一系列的Specification实例中的configuration。
NamedContextFactory 中会根据这些Specification中的配置创建创建一些列的子容器,这个子容器的也按这个name分组。这样调用NamedContextFactory#getInstance的时候是带着name去获取的,就是看要获取
那个子容器内的bean。
我们先看下NamedContextFactory 里面的一些属性。
1:propertySourceName,propertyName 构造函数传递进来赋值,会在每个子容器中生成一条配置propertySourceName=propertyName
2: Map<String, AnnotationConfigApplicationContext> contexts 每个分组创建的子容器,都放在这个map中,key就是分组的name。就是Specification实例中的那个name。
3:Map<String, C> configurations 存储一系列Specification实例,key就是Specification中的name。
4:ApplicationContext parent 每个子容器的父容器,一般都是当前启动的spring项目上下文。
4:defaultConfigType 每个子容器都会使用的一个配置类。默认配置类。
1:构造函数。
第一个参数:是默认的配置类,这个配置类,是个公共的,所有的子容器都会加载它,在下面createContext方法中可以看到。
第二个参数,第三个参数:也是公共的,在每个子容器中增加一条配置属性:propertySourceName=propertyName 在下面createContext方法中可以看到。
2:设置所有子容器的父容器,在创建子容器的时候,如果其不为空都会把这个parent设置为自己的父容器。一般这个parent都是当前的spring项目。
3:这个是用来把创建的一系列Specification实例传递进来,用里面的configuration用来创建子容器的。
4: 获取所有子容器的分组name。
5:获取某个子容器,可以被重写。
6:创建子容器
- protected AnnotationConfigApplicationContext createContext(String name) {
- AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
- if (this.configurations.containsKey(name)) {
- //this.configurations.get(name) 得到的就是对应的Specification实例
- for (Class<?> configuration : this.configurations.get(name) .getConfiguration()) {
- // 输入名称为name的子容器的配置类
- context.register(configuration);
- }
- }
- for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
- // 自定义的Specification实例也可以命名为default.开头,这样每个子容器也会使用注入。
- if (entry.getKey().startsWith("default.")) {
- for (Class<?> configuration : entry.getValue().getConfiguration()) {
- context.register(configuration);
- }
- }
- }
- // 子容器使用的默认配置类。
- context.register(PropertyPlaceholderAutoConfiguration.class,
- this.defaultConfigType);
- // 增加一个配置。
- context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
- this.propertySourceName,
- Collections.<String, Object>singletonMap(this.propertyName, name)));
- if (this.parent != null) {
- // Uses Environment from parent as well as beans
- // 子容器可以获取父容器的bean
-
- context.setParent(this.parent);
- // 解决java 11的问题
- // jdk11 issue
- // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
- context.setClassLoader(this.parent.getClassLoader());
- }
- context.setDisplayName(generateDisplayName(name));
- context.refresh();
- return context;
- }
7:根据name获取一个实例,先根据name获取某个子容器然后再获取bean。
- 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;
- }
实践
我们自定义一套Client,和Specification来感受下这个机制。
- 作为客户端
-
- public class MytestClientNamedContextFactory extends NamedContextFactory<MytestClientSpecification> {
- public MytestClientNamedContextFactory() {
- super(BeanBaseConfig.class, "test", "myTest");
- }
- }
-
- Specification实例
-
- public class MytestClientSpecification implements NamedContextFactory.Specification {
- private String name;
-
- private Class<?>[] configuration;
-
- public MytestClientSpecification(){
-
- }
-
- public MytestClientSpecification(String name, Class<?>[] configuration){
- this.name=name;
- this.configuration=configuration;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public Class<?>[] getConfiguration() {
- return configuration;
- }
- }
-
- 默认配置类
- @Configuration
- public class BeanBaseConfig {
- @Bean
- public TestBean testBeanCommon(){
- TestBean testBean = new TestBean();
- testBean.setBeanName("byBeanBaseConfig1");
- return testBean;
- }
- }
-
- @Configuration
- public class BeanBaseConfig1 {
- @Bean
- public TestBean testBean(){
- TestBean testBean = new TestBean();
- testBean.setBeanName("byBeanBaseConfig1");
- return testBean;
- }
- }
-
- @Configuration
- public class BeanBaseConfig2 {
- @Bean
- public TestBean testBean1(){
- TestBean testBean = new TestBean();
- testBean.setBeanName("byBeanBaseConfig1");
- return testBean;
- }
- }
-
- public class TestBean {
- private String beanName="testBean";
-
- public String getBeanName() {
- return beanName;
- }
-
- public void setBeanName(String beanName) {
- this.beanName = beanName;
- }
- }
测试:
- @SpringBootTest
- public class SpringRunTest {
-
- @Test
- public void test(){
- AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
- parent.register(BeanBaseConfig.class);
-
- parent.refresh();
-
- // 声明一个客户端 使用默认的配置 BeanBaseConfig
- MytestClientNamedContextFactory testClient=new MytestClientNamedContextFactory();
-
- // 声明客户端的 specification 其实这个specification 相当于一个nacos group 把配置分了组
- // 比如在ribbon client中 可能需要请求订单微服务和库存微服务的配置不一样 就需要到它
- MytestClientSpecification mytestClientSpecification1 = new MytestClientSpecification("specification1",new Class[]{BeanBaseConfig1.class});
-
- MytestClientSpecification mytestClientSpecification2 = new MytestClientSpecification("specification2",new Class[]{BeanBaseConfig1.class, BeanBaseConfig2.class});
-
- testClient.setConfigurations(Arrays.asList(mytestClientSpecification1,mytestClientSpecification2));
- testClient.setApplicationContext(parent);
-
- System.out.println(testClient.getInstances("specification1", TestBean.class));
- System.out.println("=====================================");
- System.out.println(testClient.getInstances("specification2", TestBean.class));
- }
-
- }
结果如下:
- {testBean=com.tuling.mall.user.TestBean@11c9af63, testBeanCommon=com.tuling.mall.user.TestBean@757acd7b}
- =====================================
- {testBean=com.tuling.mall.user.TestBean@55f616cf, testBean1=com.tuling.mall.user.TestBean@1356d4d4, testBeanCommon=com.tuling.mall.user.TestBean@c03cf28}
可以看到通过Specification实例可以起到子容器分离的效果。
Ribbon也是用这个机制。后面分析Ribbon源码的时候再提。