本系列為之前系列的整理重啟版,隨著專案的發展以及專案中的使用,之前系列裡面很多東西發生了變化,並且還有一些東西之前系列並沒有提到,所以重啟這個系列重新整理下,歡迎各位留言交流,謝謝!~
spring-cloud-commons 中參考了 spring-cloud-netflix 的設計,引入了 NamedContextFactory 機制,一般用於對於不同微服務的客戶端模組使用不同的 子 ApplicationContext 進行配置。
spring-cloud-commons 是 Spring Cloud 對於微服務基礎元件的抽象。在一個微服務中,呼叫微服務 A 與呼叫微服務 B 的配置可能不同。比較簡單的例子就是,A 微服務是一個簡單的使用者訂單查詢服務,介面返回速度很快,B 是一個報表微服務,介面返回速度比較慢。這樣的話我們就不能對於呼叫微服務 A 和微服務 B 使用相同的超時時間配置。還有就是,我們可能對於服務 A 通過註冊中心進行發現,對於服務 B 則是通過 DNS 解析進行服務發現,所以對於不同的微服務我們可能使用不同的元件,在 Spring 中就是使用不同型別的 Bean。
在這種需求下,不同微服務的客戶端有不同的以及相同的配置,有不同的 Bean,也有相同的 Bean。所以,我們可以針對每一個微服務將他們的 Bean 所處於 ApplicationContext 獨立開來,不同微服務客戶端使用不同的 ApplicationContext。NamedContextFactory 就是用來實現這種機制的。
編寫原始碼:
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 結構:
圖中的被包含的 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。
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;
}
我們這一節詳細分析了 Spring Cloud 的基礎 NamedContextFactory,搞清楚了其中的原理,並且舉了一個簡單的例子。接下來我們會詳細分析下如何使用以及分析改造一個 Spring Cloud 元件。
微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer: