[享學Eureka] 三十四、藉助Guice的DI依賴管理,輕鬆實現一鍵啓動Eureka Client端完成服務註冊

要麼做第一個,要麼做最好的一個。

–> 返回專欄總目錄 <–
代碼下載地址:https://github.com/f641385712/netflix-learning

前言

回想一下,在本系列第一篇文章就有提到過,Eureka它是使用輕量級DI框架:谷歌的Guice來管理其依賴的。通過前面這麼多篇文章的學習,有理想相信親們已經瞭解了Eureka幾乎每個組件的作用以及它們的依賴關係。即使如此,但若現在要你構件其一個完整可用的Eureka Client客戶端,你覺得呢?

相信拿到這個“題目”的感覺和我是一樣的:我去,這也太麻煩了吧,組件這麼多,框架依賴關係還一層一層的錯綜複雜,難點並不是因爲它困難,而是很麻煩。是的,這是每一個稍大型軟件均會遇見的難題:類/組件多了後,組織在一起便變成了一大難題,所以需要一個類似於Spring這樣的容器進行統一組織、管理依賴那是極好的。Eureka選擇了谷歌的輕量級DI框架Guice來化解該難題。本文將嘗試使用Guice來自動化管理其各個組件,一鍵啓動 Eureka Client端,讓其協調工作起來。親愛的小摩托從此便手動檔升級爲自動擋,本文你值得擁有。

說明:在理解了本文之後再去閱讀Spring Cloud整合Eureka,那就“易如反掌”了

提示:在閱讀本文之前,請務必確保你已經瞭解Guice是什麼,大概怎麼玩。參考文章:3分鐘帶你瞭解:輕量級依賴注入框架Google Guice【享學Java】


正文

前面文章我書寫代碼示例的時候,全靠我勤勞的雙手,各種new對象,各種組件的構建和組裝真的是蠻麻煩的。本文將這些依賴管理交給DI容器,切換成自動擋,一鍵完成啓動。


EurekaModule容器配置類

它是Eureka Client整合Guice的配置類,類比於Spring的@Configuration配置類,容器的啓動需要從本類開始。

下面根據此類源碼,看看其向容器內放置了哪些組件呢?

public final class EurekaModule extends AbstractModule {

    @Override
    protected void configure() {
        // 實例管理器,需要立馬初始化
        bind(ApplicationInfoManager.class).asEagerSingleton();

        // 下面的這些組件,如果你有需要可以自行在其它模塊裏通過Modules.override()覆蓋掉它
        // override these in additional modules if necessary with Modules.override()

		//實例配置:使用的CloudInstanceConfigProvider,也就是CloudInstanceConfig
		// 但是默認配置它的話需要特別注意的是:你必須配置metadata裏的instance-id等,或者關閉校驗,請看下面的示例就懂了
        bind(EurekaInstanceConfig.class).toProvider(CloudInstanceConfigProvider.class).in(Scopes.SINGLETON);
        //客戶端配置:使用的DefaultEurekaClientConfigProvider,也就是DefaultEurekaClientConfig
        // 配置信息來自於:archaius管理的配置文件,如eureka-client.properties
        bind(EurekaClientConfig.class).toProvider(DefaultEurekaClientConfigProvider.class).in(Scopes.SINGLETON);
        
		// InstanceInfo實例的實例化是個複雜過程,交給了EurekaConfigBasedInstanceInfoProvider
		// 它所需要的信息均來自EurekaInstanceConfig配置文件
        bind(InstanceInfo.class).toProvider(EurekaConfigBasedInstanceInfoProvider.class).in(Scopes.SINGLETON);
        
		// 客戶端毫無疑問,顯然是DiscoveryClient嘍
        bind(EurekaClient.class).to(DiscoveryClient.class).in(Scopes.SINGLETON);
        // 竟然EndpointRandomizer也可放置了一個實例,不錯哦
        bind(EndpointRandomizer.class).toInstance(ResolverUtils::randomize);
        // 默認使用的是基於Jersey的底層通信
        bind(AbstractDiscoveryClientOptionalArgs.class).to(Jersey1DiscoveryClientOptionalArgs.class).in(Scopes.SINGLETON);
    }

    @Override
    public boolean equals(Object obj) {
        return obj != null && getClass().equals(obj.getClass());
    }
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}

建立在已經基本瞭解Guice的使用的基礎上,對此配置類應該是沒有疑問的。它控制着容器初始化時向容器內放置的實例:對應類型放入對應實例,也就是默認實例。

如:EurekaInstanceConfig客戶端配置默認是CloudInstanceConfigProvider類型(CloudInstanceConfig類型),是基於AWS雲環境的配置,一般情況下不會使用它,需要替換~

CloudInstanceConfig和元數據強相關,關於eureka的元數據,後面我會用專門文章介紹如何去使用它,有較多的使用技巧以及黑黑科技


使用Guice啓動Eureka Client

有了Guice的基礎,使用起來自然不在話下,非常輕鬆:

@Inject
EurekaClient eurekaClient;

@Test
public void fun2(){
    // 啓動DI容器
    Injector injector = Guice.createInjector(new EurekaModule());
    // 讓其可以@Inject注入eurekaClient 提供使用
    // 說明:若你只想用純API方式使用,此句是沒有必要寫的~~~~~
    injector.injectMembers(this);

    // 可以看到注入的和API獲取到的是同一個實例
    EurekaClient injectorInstance = injector.getInstance(EurekaClient.class);
    System.out.println(eurekaClient == injectorInstance);
}

運行程序(注意:此時我沒有準備任何配置文件),拋錯:

Caused by: java.lang.RuntimeException: Your datacenter is defined as cloud but we are not able to get the amazon metadata to register. 
Set the property eureka.validateInstanceId to false to ignore the metadata call
	at com.netflix.appinfo.RefreshableAmazonInfoProvider.init(RefreshableAmazonInfoProvider.java:58)
	at com.netflix.appinfo.RefreshableAmazonInfoProvider.<init>(RefreshableAmazonInfoProvider.java:33)
	at com.netflix.appinfo.CloudInstanceConfig.<init>(CloudInstanceConfig.java:86)
	at com.netflix.appinfo.CloudInstanceConfig.<init>(CloudInstanceConfig.java:59)
	...

解決默認情況下啓動容器報錯

報錯發生在使用RefreshableAmazonInfoProvider初始化CloudInstanceConfig實例上,代碼如下:

RefreshableAmazonInfoProvider:

	private static AmazonInfo init(AmazonInfoConfig amazonInfoConfig, FallbackAddressProvider fallbackAddressProvider) {
		...
		if (info.get(AmazonInfo.MetaDataKey.instanceId) == null) {
			if (amazonInfoConfig.shouldValidateInstanceId()) {
				throw new RuntimeException("Your datacenter is defined as cloud but we are not able to get the amazon metadata to " ... );
			} else {
				...
			}
		}  else { ... }
		...
	}

我們已經知道AmazonInfo它對實例id、ip、hostname等配置都是使用metadata元數據來完成配置的,因此要想正常啓動容器不拋錯,可有如下兩種解決方案:


禁用InstanceId檢查

在配置(主配置or專用配置都行)上增加配置類:eureka.validateInstanceId = false,再次運行情況如下:

true

DI容器正常啓動(還不能完成自動註冊)。


使用自定義的EurekaInstanceConfig實現類

Eureka在Guice容器默認放入的是CloudInstanceConfig,該實例是和AWS綁定的,一般用於較爲複雜的雲環境。而對於本例使用中,我們可以替換爲自定義的(其實也是Eureka內置的)MyDataCenterInstanceConfig即可,做法如下。

自定義一個Module,注入EurekaInstanceConfig的實現爲MyDataCenterInstanceConfig這個實現:

private static class MyModule extends AbstractModule{

    @Override
    protected void configure() {
        bind(EurekaInstanceConfig.class).toProvider(MyDataCenterInstanceConfigProvider.class).in(Scopes.SINGLETON);
    }
}

使用我自定義的MyModule覆蓋默認的EurekaModule配置類:

@Test
public void fun2() throws InterruptedException {
    // 啓動DI容器
    Injector injector = Guice.createInjector(Modules.override(new EurekaModule()).with(new MyModule()));
    // 讓其可以@Inject注入eurekaClient 提供使用
    // 說明:若你只想用純API方式使用,此句是沒有必要寫的~~~~~
    injector.injectMembers(this);

    // 可以看到注入的和API獲取到的是同一個實例
    EurekaClient injectorInstance = injector.getInstance(EurekaClient.class);
    System.out.println(eurekaClient == injectorInstance);

    TimeUnit.MINUTES.sleep(2);
}

配置文件上加入如下配置項:

eureka.serviceUrl.defaultZone=http://localhost:8761/eureka

# 配置當前實例信息
eureka.instanceId = YourBatman
eureka.name = ACCOUNT
eureka.appGroup = USER-CENTER

運行測試程序,可以看到服務端能收到註冊信息:

在這裏插入圖片描述
Client端發送的實例信息截圖:

在這裏插入圖片描述
解釋:服務端收到的實例狀態是STARTING,是因爲InstanceInfo實例是通過EurekaConfigBasedInstanceInfoProvider構建出來的,默認該實例的狀態就是STARTING

如果你想它是UP的話,可以增加參數:eureka.traffic.enabled = true這樣初始註冊上去就是UP啦。詳見EurekaConfigBasedInstanceInfoProvider~

需要再次強調的是:Spring Cloud下可沒這個“規則”,因爲它的InstanceInfo實例是Spring Cloud自己創建、管理的,有一套它自己的邏輯,而不用遵從原生~


InternalEurekaStatusModule

它雖然也是一個Guice的配置類,但是由於已過期,不需要再使用,所以本文略過。


Spring Cloud整合Guice了嗎?

答案:完全沒有。雖然說Spring和Guice均爲DI框架,但是其實他倆是可以完成整合、和諧相處的。但實際上是,在Spring Cloud中使用Eureka時,它完全沒有使用到Guice,而是把所有Eureka的組件均交由Spring容器管理,完全的自己組織。

我個人認爲這樣做是無可厚非的,我大膽猜測了一下SC這麼做有如下原因:

  1. Spring家族希望你學習Spring Cloud只需要懂Spring即可,而完全沒有必要再去學習另外一個DI框架Guice(我覺得這是最最最重要的原因)
    1. Spring一把全包了,管生管養,管殺管埋
  2. 自身生態考慮。我自己全都能做,爲何還需要給“競品”機會呢,讓其死在襁褓裏豈不更好(這點原因是我自己YY的)
    1. 畢竟Spring Cloud“號召力”可不小,萬一學它的人多了,再加上Google強大的技術基因。。。你懂的
  3. 整合永遠停留在適配層面,而所有東西都自己來管理,才更好把握、更好定製、更好擴展、更好引導用戶使用我的方式去編碼
    1. 最爲典型的是:Spring Cloud下的配置項完全是Spring Boot的風格,而去掉了源生風格

總之,我覺得Spring這麼做,從用戶體驗角度來看,是非常非常好的舉措,或許也是雙贏的方案吧~


總結

關於藉助Guice的DI依賴管理,輕鬆實現一鍵啓動Eureka Client端完成服務註冊就先介紹到這了,通過本文示例同前面我書寫的示例做對比,你應該體會到了DI依賴管理容器的“威力”。另外,使用Guice啓動Eureka Client客戶端只是牛刀小試,下面在Spring Cloud下使用Eureka Client纔是生產上真正的使用方式,也就是所謂的正確姿勢。不過萬物皆關聯,況且還是近親呢?下文見吧~
分隔線

聲明

原創不易,碼字更不易,感謝關注。分享本文到你的朋友圈是被授權的,但拒絕抄襲。【左邊掃碼加我wx / wx號:fsx641385712】,邀你加入 【Java高工、架構師】 系列純純純技術羣,亦可掃碼加入我的知識星球【BAT的烏托邦】。
往期精選

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章