Spring Cloud 升級之路 - 2020.0.x - 5. 理解 NamedContextFactory

spring-cloud-commons 中參考了 spring-cloud-netflix 的設計,引入了 NamedContextFactory 機制,通常用於對於不一樣微服務的客戶端模塊使用不一樣的 子 ApplicationContext 進行配置。java

spring-cloud-commons 是 Spring Cloud 對於微服務基礎組件的抽象。在一個微服務中,調用微服務 A 與調用微服務 B 的配置可能不一樣。比較簡單的例子就是,A 微服務是一個簡單的用戶訂單查詢服務,接口返回速度很快,B 是一個報表微服務,接口返回速度比較慢。這樣的話咱們就不能對於調用微服務 A 和微服務 B 使用相同的超時時間配置。還有就是,咱們可能對於服務 A 經過註冊中心進行發現,對於服務 B 則是經過 DNS 解析進行服務發現,因此對於不一樣的微服務咱們可能使用不一樣的組件,在 Spring 中就是使用不一樣類型的 Bean。git

在這種需求下,不一樣微服務的客戶端有不一樣的以及相同的配置有不一樣的 Bean,也有相同的 Bean。因此,咱們能夠針對每個微服務將他們的 Bean 所處於 ApplicationContext 獨立開來,不一樣微服務客戶端使用不一樣的 ApplicationContext。NamedContextFactory 就是用來實現這種機制的。github

經過實例瞭解 NamedContextFactory 的使用

編寫源碼:spring

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

結果輸出爲:併發

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

代碼中實現了這樣一個 Context 結構:
imageapp

圖中的被包含的 ApplicationContext 能夠看到外層 ApplicationContext 的 Bean,也就是經過對被包含的 ApplicationContext 調用 getBean(xxx) 能夠獲取到外層 ApplicationContext 的 Bean (其實外層就是 parent ApplicationContext),可是外層的看不到內層私有的 Bean。框架

在咱們的測試代碼中,首先,建立了一個 AnnotationConfigApplicationContext。這個其實就是模擬了咱們日常使用 Spring 框架的時候的根核心 ApplicationContext,因此咱們將其命名爲 parent。咱們向裏面註冊了 BaseConfigBaseConfig 裏面的 BaseBean 會註冊到 parent。以後咱們 建 testClient1,默認配置使用 ClientCommonConfig。若是咱們指定了 testClient1 的 parent ApplicationContext 爲 parent,那麼 parent 裏面的 Bean 都能被 testClient1 裏面的子 ApplicationContext 訪問到。而後,咱們建立 service1 與 service2 以及指定對應額外的配置類。service1 會建立 ClientCommonConfigService1Config1Service1Config2 裏面配置的 Bean。service2 會建立 ClientCommonConfigService2Config 裏面配置的 Bean。ide

NamedContextFactory 的基本原理以及源碼

NamedContextFactory 的核心方法是 public <T> T getInstance(String name, Class<T> type),經過這個方法獲取 NamedContextFactory 裏面的子 ApplicationContext 裏面的 Bean。源碼是:模塊化

NamedContextFactory.java微服務

/**
 * 獲取某個 name 的 ApplicationContext 裏面的某個類型的 Bean
 * @param name 子 ApplicationContext 名稱
 * @param type 類型
 * @param <T> Bean 類型
 * @return Bean
 */
public <T> T getInstance(String name, Class<T> type) {
    //獲取或者建立對應名稱的 ApplicationContext
    AnnotationConfigApplicationContext context = getContext(name);
    try {
        //從對應的 ApplicationContext 獲取 Bean,若是不存在則會拋出 NoSuchBeanDefinitionException
        return context.getBean(type);
    }
    catch (NoSuchBeanDefinitionException e) {
        //忽略 NoSuchBeanDefinitionException
    }
    //沒找到就返回 null
    return null;
}

protected AnnotationConfigApplicationContext getContext(String name) {
    //若是 map 中不存在,則建立
    if (!this.contexts.containsKey(name)) {
        //防止併發建立多個
        synchronized (this.contexts) {
            //再次判斷,防止有多個等待鎖
            if (!this.contexts.containsKey(name)) {
                this.contexts.put(name, createContext(name));
            }
        }
    }
    return this.contexts.get(name);
}

//根據名稱建立對應的 context
protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //若是 configurations 中有對應名稱的配置類,則註冊之
    if (this.configurations.containsKey(name)) {
        for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
            context.register(configuration);
        }
    }
    //若是 configurations 中有名稱開頭爲 default. 的配置類,則註冊之
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
        if (entry.getKey().startsWith("default.")) {
            for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
            }
        }
    }
    //註冊 PropertyPlaceholderAutoConfiguration,這樣能夠解析 spring boot 相關的 application 配置
    //註冊默認的配置類 defaultConfigType
    context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
    //將當前 context 的名稱,放入對應的屬性中,在配置類中可能會用到
    //咱們上面舉得例子,就是經過 environment.getProperty() 獲取了這個屬性
    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 能夠打包成一種 fatjar 的形式,將依賴的 jar 包都打入同一個 jar 包中
        //fatjar 中的依賴,經過默認的類加載器是加載不正確的,須要經過定製的類加載器
        //因爲 JDK 11 LTS 相對於 JDK 8 LTS 多了模塊化,經過 ClassUtils.getDefaultClassLoader() 有所不一樣
        //在 JDK 8 中獲取的就是定製的類加載器,JDK 11 中獲取的是默認的類加載器,這樣會有問題
        //因此,這裏須要手動設置當前 context 的類加載器爲父 context 的類加載器
        context.setClassLoader(this.parent.getClassLoader());
    }
    //生成展現名稱
    context.setDisplayName(generateDisplayName(name));
    context.refresh();
    return context;
}
相關文章
相關標籤/搜索