Spring-cloud-Commons refers to the design of Spring-cloud-Netflix and introduces the NamedContextFactory mechanism. It is generally used to configure client modules of different microservices with different child ApplicationContext.

Spring-cloud-Commons is Spring Cloud’s abstraction of the underlying components of a microservice. Within A microservice, the configuration for invoking microservice A may be different from that for invoking microservice B. A relatively simple example is that A microservice is A simple user order query service with A fast interface return speed, while B is A report microservice with A slow interface return speed. This way we cannot use the same timeout configuration for calling microservice A and microservice B. Also, we might discover through A registry for service A and through DNS resolution for service B, so we might use different components for different micro-services, which in Spring means different types of beans.

Under this requirement, clients of different microservices have different and identical configurations, with different beans and the same beans. Therefore, we can separate the ApplicationContext in which their beans are located for each microservice. Different microservice clients use different ApplicationContext. NamedContextFactory is used to implement this mechanism.

Learn how to use the NamedContextFactory by example

Write the source code:

package com.github.hashjang.spring.cloud.iiford.service.common;

import org.junit.Assert;
import org.junit.Test;
import org.springframework.cloud.context.named.NamedContextFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import java.util.List;
import java.util.Objects;

public class CommonNameContextTest {

    private static final String PROPERTY_NAME = "test.context.name";

    @Test
    public void test() {
        //创建 parent context
        AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
        //添加 BaseConfig 相关配置
        parent.register(BaseConfig.class);
        //初始化 parent
        parent.refresh();
        //创建 testClient1,默认配置使用 ClientCommonConfig
        TestClient testClient1 = new TestClient(ClientCommonConfig.class);
        //创建 service1 与 service2 以及指定对应额外的配置类
        TestSpec testSpec1 = new TestSpec("service1", new Class[]{Service1Config1.class, Service1Config2.class});
        TestSpec testSpec2 = new TestSpec("service2", new Class[]{Service2Config.class});
        //设置 parent ApplicationContext 为 parent
        testClient1.setApplicationContext(parent);
        //将 service1 与 service2 的配置加入 testClient1
        testClient1.setConfigurations(List.of(testSpec1, testSpec2));
        BaseBean baseBean = testClient1.getInstance("service1", BaseBean.class);
        System.out.println(baseBean);
        //验证正常获取到了 baseBean
        Assert.assertNotNull(baseBean);
        ClientCommonBean commonBean = testClient1.getInstance("service1", ClientCommonBean.class);
        System.out.println(commonBean);
        //验证正常获取到了 commonBean
        Assert.assertNotNull(commonBean);
        Service1Bean1 service1Bean1 = testClient1.getInstance("service1", Service1Bean1.class);
        System.out.println(service1Bean1);
        //验证正常获取到了 service1Bean1
        Assert.assertNotNull(service1Bean1);
        Service1Bean2 service1Bean2 = testClient1.getInstance("service1", Service1Bean2.class);
        System.out.println(service1Bean2);
        //验证正常获取到了 service1Bean2
        Assert.assertNotNull(service1Bean2);
        BaseBean baseBean2 = testClient1.getInstance("service2", BaseBean.class);
        System.out.println(baseBean2);
        //验证正常获取到了 baseBean2 并且 baseBean2 就是 baseBean
        Assert.assertEquals(baseBean, baseBean2);
        ClientCommonBean commonBean2 = testClient1.getInstance("service2", ClientCommonBean.class);
        System.out.println(commonBean2);
        //验证正常获取到了 commonBean2 并且 commonBean 和 commonBean2 不是同一个
        Assert.assertNotNull(commonBean2);
        Assert.assertNotEquals(commonBean, commonBean2);
        Service2Bean service2Bean = testClient1.getInstance("service2", Service2Bean.class);
        System.out.println(service2Bean);
        //验证正常获取到了 service2Bean
        Assert.assertNotNull(service2Bean);
    }

    @Configuration(proxyBeanMethods = false)
    static class BaseConfig {
        @Bean
        BaseBean baseBean() {
            return new BaseBean();
        }
    }

    static class BaseBean {}

    @Configuration(proxyBeanMethods = false)
    static class ClientCommonConfig {
        @Bean
        ClientCommonBean clientCommonBean(Environment environment, BaseBean baseBean) {
            //在创建 NamedContextFactory 里面的子 ApplicationContext 的时候,会指定 name,这个 name 对应的属性 key 即 PROPERTY_NAME
            return new ClientCommonBean(environment.getProperty(PROPERTY_NAME), baseBean);
        }
    }

    static class ClientCommonBean {
        private final String name;
        private final BaseBean baseBean;

        ClientCommonBean(String name, BaseBean baseBean) {
            this.name = name;
            this.baseBean = baseBean;
        }

        @Override
        public String toString() {
            return "ClientCommonBean{" +
                    "name='" + name + '\'' +
                    ", baseBean=" + baseBean +
                    '}';
        }
    }

    @Configuration(proxyBeanMethods = false)
    static class Service1Config1 {
        @Bean
        Service1Bean1 service1Bean1(ClientCommonBean clientCommonBean) {
            return new Service1Bean1(clientCommonBean);
        }
    }

    static class Service1Bean1 {
        private final ClientCommonBean clientCommonBean;

        Service1Bean1(ClientCommonBean clientCommonBean) {
            this.clientCommonBean = clientCommonBean;
        }

        @Override
        public String toString() {
            return "Service1Bean1{" +
                    "clientCommonBean=" + clientCommonBean +
                    '}';
        }
    }

    @Configuration(proxyBeanMethods = false)
    static class Service1Config2 {
        @Bean
        Service1Bean2 service1Bean2() {
            return new Service1Bean2();
        }
    }

    static class Service1Bean2 {
    }

    @Configuration(proxyBeanMethods = false)
    static class Service2Config {
        @Bean
        Service2Bean service2Bean(ClientCommonBean clientCommonBean) {
            return new Service2Bean(clientCommonBean);
        }
    }

    static class Service2Bean {
        private final ClientCommonBean clientCommonBean;

        Service2Bean(ClientCommonBean clientCommonBean) {
            this.clientCommonBean = clientCommonBean;
        }

        @Override
        public String toString() {
            return "Service2Bean{" +
                    "clientCommonBean=" + clientCommonBean +
                    '}';
        }
    }

    static class TestSpec implements NamedContextFactory.Specification {
        private final String name;
        private final Class<?>[] configurations;

        public TestSpec(String name, Class<?>[] configurations) {
            this.name = name;
            this.configurations = configurations;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public Class<?>[] getConfiguration() {
            return configurations;
        }
    }

    static class TestClient extends NamedContextFactory<TestSpec> {

        public TestClient(Class<?> defaultConfigType) {
            super(defaultConfigType, "testClient", PROPERTY_NAME);
        }
    }
}

The result output is:

com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d
ClientCommonBean{name='service1', baseBean=com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d}
Service1Bean1{clientCommonBean=ClientCommonBean{name='service1', baseBean=com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d}}
com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$Service1Bean2@4648ce9
com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d
ClientCommonBean{name='service2', baseBean=com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d}
Service2Bean{clientCommonBean=ClientCommonBean{name='service2', baseBean=com.github.hashjang.spring.cloud.iiford.service.common.CommonNameContextTest$BaseBean@3faf2e7d}}

The code implements the following Context structure:

The enclosed ApplicationContext in the figure shows the beans of the outer ApplicationContext, The Parent ApplicationContext (Parent ApplicationContext) is retrieved by calling getBean(XXX) on the included ApplicationContext, and the Parent ApplicationContext is retrieved by calling getBean(XXX) on the included ApplicationContext. But the outer layer does not see the inner private Bean.

In our test code, first of all, to create a AnnotationConfigApplicationContext. This mimics the root core ApplicationContext that we normally use with the Spring Framework, so we’ll name it Parent. We registered BaseConfig with it, and BaseBeans in BaseConfig are registered with Parent. Then we set up TestClient1, with ClientCommonConfig as the default configuration. If we specify the Parent ApplicationContext for TestClient1 as Parent, then all the beans in the Parent context can be accessed by the child ApplicationContext in TestClient1. Then, we create service1 and service2 and specify the corresponding additional configuration classes. Service1 creates the beans configured in ClientCommonConfig, Service1Config1, and Service1Config2. Service2 creates the beans configured in ClientCommonConfig and Service2Config.

NamedContextFactory basic principle and source code

Public

T getInstance(String name, Class

type), This method gets the Bean in the child ApplicationContext of the NamedContextFactory. The source code is:

NamedContextFactory.java

/** * Get a Bean of type in ApplicationContext of name * @Param Name child ApplicationContext * @Param type * @Param <T> Bean type * @return Bean */ public <T> T getInstance(String name, String name, String name, String name) Class < T > type) {/ / obtain the corresponding name or create ApplicationContext AnnotationConfigApplicationContext context = getContext (name); Try {/ / Bean was obtained from the corresponding ApplicationContext, if there is no will throw NoSuchBeanDefinitionException return context. The getBean (type); } the catch (NoSuchBeanDefinitionException e) {/ / ignore NoSuchBeanDefinitionException} / / couldn't find it returns null return null; } protected AnnotationConfigApplicationContext getContext (String name) {/ / if the map doesn't exist, then create an if (! This. Contexts. Either containsKey (name)) {/ / prevent concurrent create multiple synchronized (enclosing contexts) {/ / judge again, to prevent multiple wait for locks if (! this.contexts.containsKey(name)) { this.contexts.put(name, createContext(name)); } } } return this.contexts.get(name); } / / by name to create the corresponding context protected AnnotationConfigApplicationContext createContext (String name) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); / / if a configuration Class with corresponding name in configurations, the registration of the if (this. Configurations. Either containsKey (name)) {for (Class <? > configuration : this.configurations.get(name).getConfiguration()) { context.register(configuration); // Configurations // Configurations For (map.entry <String, C> Entry: this.configurations.entrySet()) { if (entry.getKey().startsWith("default.")) { for (Class<? > configuration : entry.getValue().getConfiguration()) { context.register(configuration); }}} / / registered PropertyPlaceholderAutoConfiguration, This resolves the Spring Boot associated Application configuration // Register the default configuration class DefaultConfigType context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType); // put the name of the current context into the corresponding property, which may be used in the configuration class // You get this property 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 context.setParent(this.parent); Spring Boot can be packaged in a FatJar format. The dependencies in FatJar are loaded incorrectly through the default classloader. Need to pass the custom class loader / / because the JDK 11 LTS relative to the JDK 8 LTS more modular, through ClassUtils. GetDefaultClassLoader are different () / / in the JDK 8 is custom class loaders, JDK 11 is getting the default classloader, which can be problematic. Here need to manually set the current context of the class loader for the parent context class loader context. SetClassLoader (this) the parent) getClassLoader ()); } // Generate context.setDisplayName(generateDisplayName(name)); context.refresh(); return context; }