This is my 10th day of the August Challenge

This series is the rearrangement of the previous series. With the development of the project and the use of the project, many things in the previous series have changed, and there are some things that were not mentioned in the previous series, so restart the series to rearrange, welcome to leave messages and exchange, thank you! ~

Spring-cloud-commons refers to the design of Spring-Cloud-Netflix and introduces NamedContextFactory mechanism. Typically used to configure client modules for different microservices with different sub-ApplicationContext.

Spring-cloud-commons is spring Cloud’s abstraction of a microservice base component. In A microservice, the configuration for calling microservice A may be different from that for calling Microservice B. A simple example is that microservice A is A simple user order query service with A fast interface return speed, while service 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 services through the registry for Service A and through DNS resolution for service B, so we might use different components for different microservices, which in Spring means different types of beans.

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

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 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 the Context structure:

The included ApplicationContext in the diagram can see the beans of the outer ApplicationContext, The enclosing enclosing ApplicationContext is called getBean(XXX). The enclosing enclosing ApplicationContext is called getBean(XXX). But the outer layer does not see the inner private Bean.

In our test code, first of all, to create a AnnotationConfigApplicationContext. This actually emulates the root core ApplicationContext that we normally use with the Spring framework, so we’ll name it Parent. We register the BaseConfig, and the BaseBeans in the BaseConfig are registered with the parent. Then we set up testClient1, which uses ClientCommonConfig by default. If we specify testClient1’s parent ApplicationContext as parent, then all beans in parent can be accessed by testClient1’s child ApplicationContext. We then 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.

Public

T getInstance(String name, Class

type); Use this method to retrieve beans from the child ApplicationContext of NamedContextFactory. The source code is:

NamedContextFactory.java

@param <T> @param <T> @param <T> @param <T> Public <T> T getInstance(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); } // If you want to add a name that starts 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 Application configuration related to Spring Boot // 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).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 the form of a fatJar, and the dependencies in the 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 gets the default classloader, which is a problem. 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

In this section, we took a detailed look at the Spring Cloud’s underlying NamedContextFactory, figuring out how it works, and using a simple example. Next, we’ll take a closer look at how to use and modify a Spring Cloud component.

Wechat search “my programming cat” to follow the public account, every day, easy to improve technology, win a variety of offers