Cd Chen's Services

ba ba ba la~~

Java 鬆綁 (二):利用 Package 與動態載入徹底鬆綁

先前的例子裡,我們利用 Java 多型的技巧,來鬆綁類別之間的依賴關係。但在這個例子裡,變更實作類別時,還是需要重新編譯程式,還是不夠方便!!

有沒有更棒的方法呢??

有的。

動態載入

Java 中的 java.lang.Class 類別有提供一個 forName() 的靜態方法 (Static Method),可以讓我們請 JVM 動態的載入指定的類別。使用 Class.forName() 時,你必須指定要動態載入的類別之 FQCN,如果 JVM 可以找得到該類別,就可在執行時期載入我們所需的類別。

實作的方法如下示範:

package org.cdchen.serviceloaders.apps;

import org.cdchen.serviceloaders.GreetingService;

public class TraditionalDynamicLoadingMain {
	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		try {
			Class<GreetingService> clz = (Class<GreetingService>) Class.forName("org.cdchen.serviceloaders.impls.HiGreetingService");
			GreetingService service = clz.newInstance();
			GreetingServiceUtil.callService(service);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

實務上當然不會像這個範例一樣,直接在程式中指定要載入的類別 FQCN,畢竟這樣未來需要更改類別時,還得重新編譯一次,就失去了彈性的要求。解決這個問題的方法,有很多種;其中最常見的一種,就是把要實作的類別,寫在某一個檔案裡。然後在呼叫端 (Caller) 讀入該檔案,以便取得設定中的類別 FQCN,最後再呼叫 Class.forName()。不過,我不想這麼麻煩來處理,下列的示範使用 System.getProperty() 的方式,取得名為 greetingservice.impls 的 Property,作為類別的名稱:

package org.cdchen.serviceloaders.apps;

import org.cdchen.serviceloaders.GreetingService;

public class TraditionalDynamicLoadingMain2 {
	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		String className = System.getProperty("greetingservice.impls");
		try {
			Class<GreetingService> clz = (Class<GreetingService>) Class.forName(className);
			GreetingService service = clz.newInstance();
			GreetingServiceUtil.callService(service);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

這樣之後,以後執行這個 Main Class 時:

$ java -Dgreetingservice.impls=org.cdchen.serviceloaders.impls.HiGreetingService \
    org.cdchen.serviceloader.apps.TraditionalDynamicLoadingMain2 

只需要變更 -Dgreetingservice.impls 的值即可在執行期間動態載入指定的類別。

再鬆綁

截至目前為止,我們都是在同一個 Package (注意,這裡的 Package 是指 Jar 檔) 中來實作鬆綁的策略。雖然可以達到鬆綁依賴關係的目的,但如果未來增加或修改了實作的類別,都還得重新製作與部署 Package (注意:Jar 檔)。為了能夠把鬆綁的原則發揮到淋漓盡致,我們也可以考慮把介面與實作分散在不同的 Package (注意:還是 Jar 檔)。例如:

  • apis-0.0.1-SNAPSHOT.jar
    提供介面。以本文的例子來說,就是 org.cdchen.serviceloaders.GreetingService 這個介面。
  • imps-0.0.1-SNAPSHOT.jar
    提供實作的類別,以本文的範例來說,就是 org.cdchen.serviceloaders.impls 這個 Package (這裡的 Package 是指 Java 的 Package) 中所有的類別。

透過這樣的方式,只要在執行 JVM 的時候,配合 -cp 參數,指定實作的 Package (Jar 檔) 名稱即可:

$ java -cp impls-0.0.1-SNAPSHOT.jar:apis-0.0.1-SNAPSHOT.jar \
     -Dgreetingservice.impls=org.cdchen.serviceloaders.impls.HiGreetingService \
     org.cdchen.serviceloader.apps.TraditionalDynamicLoadingMain2 

如此一來,爾後若有新增或修改實作類別,只需重新部署 impls-*.jar 檔案即可。

這樣是不是更迷人??

這篇內容的 Trackback 網址:

http://cdchen.idv.tw/trackback/1059