This article is participating in the “Java Theme Month – Java Brush questions punch card”, see the activity link for details

Spring-cloud-commons refers to the design of Spring-Cloud-Netflix and introduces the NamedContextFactory mechanism. This is typically used to configure different subApplicationContext for different microservice client modules.

Spring-cloud-commons is an abstraction of Spring Cloud’s microservices infrastructure components. In A microservice, the configuration for calling microservice A may be different from that for calling microservice B. A 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. In this way we cannot use the same timeout configuration for calling microservice A and microservice B. Also, we might discover service A through A registry and service B through DNS resolution, so we might use different components for different microservices, in Spring using different types of beans.

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

Learn the use of NamedContextFactory through examples

Write 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);
        }
    }
}
Copy the code

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}}

Copy the code

The code implements a Context structure like this:

The included ApplicationContext in the diagram shows the Bean of the outer ApplicationContext, GetBean (XXX) ¶ getBean(XXX) ¶ getBean(XXX) ¶ But the outer layer cannot see the inner layer private Bean.

In our test code, first of all, to create a AnnotationConfigApplicationContext. This emulates the root core ApplicationContext we normally use with the Spring framework, so we’ll name it parent. We registered the BaseConfig, and the BaseBean in the BaseConfig will be registered with the parent. After that we build testClient1 and use ClientCommonConfig as the default configuration. If we specify the parent ApplicationContext of testClient1 as parent, then the beans in parent can be accessed by the child ApplicationContext in testClient1. Next, 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 principles 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

@param name Child ApplicationContext name @param type type @param <T> Public <T> T getInstance(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); }} // If you have a name in configurations starting with default. 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-related application configuration // registers 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. 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 as a fatjar, which puts all dependent JARS into the same JAR. The dependencies in fatjar are not loaded correctly through the default class loader. 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 gets the default class loader, which is problematic // so, 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 the display name context.setDisplayName(generateDisplayName(name)); context.refresh(); return context; }Copy the code