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
編寫源碼: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 結構:
app
圖中的被包含的 ApplicationContext 能夠看到外層 ApplicationContext 的 Bean,也就是經過對被包含的 ApplicationContext 調用 getBean(xxx)
能夠獲取到外層 ApplicationContext 的 Bean (其實外層就是 parent ApplicationContext),可是外層的看不到內層私有的 Bean。框架
在咱們的測試代碼中,首先,建立了一個 AnnotationConfigApplicationContext
。這個其實就是模擬了咱們日常使用 Spring 框架的時候的根核心 ApplicationContext,因此咱們將其命名爲 parent。咱們向裏面註冊了 BaseConfig
,BaseConfig
裏面的 BaseBean
會註冊到 parent。以後咱們 建 testClient1,默認配置使用 ClientCommonConfig。若是咱們指定了 testClient1 的 parent ApplicationContext 爲 parent,那麼 parent 裏面的 Bean 都能被 testClient1 裏面的子 ApplicationContext 訪問到。而後,咱們建立 service1 與 service2 以及指定對應額外的配置類。service1 會建立 ClientCommonConfig
、Service1Config1
和 Service1Config2
裏面配置的 Bean。service2 會建立 ClientCommonConfig
和 Service2Config
裏面配置的 Bean。ide
NamedContextFactory 的核心方法是 public <T> T getInstance(String name, Class<T> type)
,經過這個方法獲取 NamedContextFactory 裏面的子 ApplicationContext 裏面的 Bean。源碼是:模塊化
/** * 獲取某個 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; }