程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 模塊化Java:靜態模塊化

模塊化Java:靜態模塊化

編輯:關於JAVA

模塊化是大型Java系統的一個重要特征。在這些項目中構建腳本和項目通常被劃分為 多個模塊,以便改進構建過程,但是在運行時卻很少考慮劃分模塊的問題。

在“模塊化Java”系列文章的第二篇裡,我們將討論靜態模塊化(static modularity )。內容包括如何創建bundle、將其安裝到OSG引擎以及怎樣建立bundle之間的版本依賴 。在下一篇文章中,我們將討論動態模塊化(dynamic modularity)並展示bundle如何對 其他bundle作出響應。

在上篇文章《模塊化Java簡介》中講到,Java在開發時把package作為模塊化單元,部 署時把JAR文件作為模塊化單元。可是盡管像Maven這樣的構建工具能夠在編譯時保證 package和JAR的特定組合,但這些依賴在運行時classpath下仍可能出現不一致的情況。 為了解決這一問題,模塊可以聲明其依賴需求,這樣,在運行時就可以在執行之前進行依 賴檢查。

OSGi是一個Java的運行時動態模塊系統。OSGi規范描述了OSGi運行時的工作行為方式 ;當前版本是OSGi R4.2。

一個OSGi模塊(也稱為bundle)就是一個普通的JAR文件,但在其MANIFEST.MF中帶有 附加信息。一個bundle的manifest必須至少包含如下內容:

Bundle-ManifestVersion:對OSGi R4 bundle來說必須是2(OSGi R3 bundle則默認為 1)

Bundle-SymbolicName:bundle的文本標識符,通常以反向域名的形式出現,如 com.infoq,並且往往對應了包含其中的package

Bundle-Version:major.minor.micro.qualifier形式的版本號,前三個元素是數字( 缺省是0),qualifier則是文本(缺省是空字符串)

創建一個bundle

最簡單的bundle必須在manifest文件中包含如下內容:

Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.infoq.minimal
Bundle-Version: 1.0.0

創建bundle並沒有什麼可稀奇的,那麼讓我們創建一個帶activator的bundle吧。下面 是OSGi特定的代碼片段,在bundle啟動時被調用,有點像是bundle的main方法。

package com.infoq;
import org.osgi.framework.*;
public class ExampleActivator implements BundleActivator {
  public void start(BundleContext context) {
   System.out.println("Started");
  }
  public void stop(BundleContext context) {
   System.out.println("Stopped");
  }
}

為了讓OSGi知道哪個類是activator,我們需要在manifest中加入額外的信息項:

Bundle-Activator: com.infoq.ExampleActivator
Import-Package: org.osgi.framework

Bundle-Activator聲明了在bundle啟動時要實例化並調用其start()方法的類;類似的 ,在bundle停止時將調用該類的stop()方法。

那麼Import-Package又是干什麼的?每個bundle都需要在manifest中定義其依賴,以 便在運行時判斷所有必需代碼是否可用。在本例中,ExampleActivator依賴於 org.osgi.framework包中的BundleContext;如果我們不在manifext中聲明該依賴,在運 行時就會碰到NoClassDefFoundError錯誤。

下載OSGi引擎

要編譯並測試我們的bundle,需要一個OSGi引擎。對OSGi R4.2,下面羅列了幾個可用 的開源引擎。你也可以下載Reference API來編譯(這樣可以確保沒有用到任何平台特定 特性);可是,要運行bundle,還是需要一個OSGi引擎。以下引擎都可供選擇:

Equinox 許可 Eclipse Public License 文獻 http://www.eclipse.org/equinox/ 下載 org.eclipse.osgi_3.5.0.v20090520.jar 評注 該org.eclipse.osgi bundle包含了框架、運行時和shell,是全部合一的。它 的文件名也最長,用tab補全(或改名為equinox.jar)可以解決這一問題。
要啟動console,在命令行輸入java -jar org.eclipse.osgi_3.5.0.v20090520.jar -console即可。 框架 org.eclipse.osgi_3.5.0.v20090520.jar Felix 許可 Apache License 文獻 http://felix.apache.org/ 下載 Felix Framework Distribution 2.0.0 評注 這是所見遵守規范最嚴格的OSGi引擎,還被用在GlassFish及許多其他開源產 品中。運行時需要在命令行輸入java -jar bin/felix.jar而不是 java -jar felix.jar ,因為啟動時它要從當前目錄查找bundles 路徑。 框架 bin/felix.jar Knopflerfish 許可 Knopflerfish License (BSD-esque) 文獻 http://www.knopflerfish.org/ 下載 knopflerfish_fullbin_osgi_2.3.3.jar 評注 該JAR是一個自解壓zip文件;剛開始你必須運行java -jar進行解壓。不要下 載“bin_osgi”,它無法啟動。 框架 knopflerfish.org/osgi/framework.jar

另外還有更小的定位於嵌入設備的OSGi R3運行時可用(比如Concierge),但本系列 文章只關注OSGi R4。

編譯並運行bundle

獲得framework.jar之後,把OSGi加入classpath並編譯上面的例子,然後將其打包成 JAR:

javac -cp framework.jar com/infoq/*/*.java

jar cfm example.jar MANIFEST.MF com

每種引擎都有shell,命令也相似(但不相同)。為了便於練習,讓我們看看如何獲得 這些引擎並使之運行、安裝和啟/停bundle。

一旦引擎啟動並運行起來,你就可以安裝bundle(由 file:// URL來定位)了,然後 可以使用安裝bundle所返回的bundle id,啟動或停止該bundle。

  Equinox Felix Knopflerfish 啟動應用 java -jar org.eclipse.osgi_*.jar -console java -jar bin/felix.jar java -jar framework.jar -xargs minimal.xargs 幫助 help 列表 ss ps bundles 安裝 install file:///path/to/example.jar 啟動 start id 更新 update id 停止 stop id 卸載 uninstall id 退出 exit shutdown

盡管所有的shell工作起來大都一樣,但命令之間還是有容易混淆的細微差別。有兩個 統一console的項目(Pax Shell,Posh)和運行器(Pax Runner)可以利用;OSGi RFC 132則是一個正在進行中的提案,試圖標准化command shell。另外,Apache Karaf可以運 行在Equinox或Felix之上,提供統一的shell以及其他特性。盡管使用這些項目或工具進 行實際部署是可取的,但本系列文章還是關注於普通的OSGi框架實現。

如果你啟動了OSGi框架,你應該能夠安裝上面所講的com.infoq.minimal-1.0.0.jar( 你還可以用鏈接地址及install命令直接從網站上進行安裝)。每次安裝bundle,引擎都 會打印出該bundle的數字ID。

在安裝好bundle之前不可能知道bundle的ID是多少,這取決於系統中其它bundle的ID 分配情況;但是你可以使用適當的命令羅列出已安裝的bundle將其找出來。

依賴

迄今為止,我們只有一個bundle。模塊化的一個好處是可以把系統分解為多個小模塊 ,在此過程中,減小應用的復雜性。從某種程度上,Java的 package已經做到了這一點: 例如,一個common包有獨立的client和server包,他們都依賴於該common包。但是Jetty 最近的例子(client意外地依賴於server)表明做到這一點並不總是很容易。實際上,有 些由OSGi給項目帶來的好處純粹就是強制模塊間的模塊化約束。

模塊化的另一個好處是把'public'包從非public包中分離出來。Java的編譯時系統允 許隱藏非public類(在特定包中是可見的),但是不支持更大程度的靈活性。然而在OSGi 模塊中,你可以選擇哪些包是exported(輸出)的,這就意味著沒有輸出的包對其他模塊 是不可見的。

讓我們繼續開發一個功能,用來初始化URI Templates(與Restlet中使用的一樣)。 因為該功能可重用,我們想將其放在一個單獨模塊中,讓使用它的客戶端依賴於它。(通 常,bundle不適合這麼細粒度的用法,但是其可用於說明工作原理)。該功能將根據一個 模板(比如http://www.amazon.{tld}/dp/{isbn}/)和一個包含有 tld=com,isbn=1411609255的Map,產生出URL http://www.amazon.com/dp/1411609255/( 這麼做的一個原因是,如果Amazon URL模式發生了變化,我們能夠改變該模板,盡管好的 URI是不會改變的)。

為了提供一個在不同實現之間切換的簡單方法,我們將提供一個接口和一個工廠。這 會讓我們看到在提供功能的同時實現是怎樣對client隱藏的。代碼(對應幾個源文件)如 下:

package com.infoq.templater.api;
import java.util.*;
public interface ITemplater {
  public String template(String uri, Map data);
}
// ---
package com.infoq.templater.api;
import com.infoq.templater.internal.*;
public class TemplaterFactory {
  public static ITemplater getTemplater() {
   return new Templater();
  }
}
// ---
package com.infoq.templater.internal;
import com.infoq.templater.api.*;
import java.util.*;
public class Templater implements ITemplater {
  public String template(String uri, Map data) {
   String[] elements = uri.split("\\{|\\}");
   StringBuffer buf = new StringBuffer();
   for(int i=0;i

該實現隱藏在com.infoq.templater.internal包中,而public API則位於 com.infoq.templater.api包中。這就給了我們巨大的靈活性,如果需要,以後可以修改 實現以提供更加有效的手段。(internal包名是約定俗成,你可以起其他名字)。

為了讓其他bundle能夠訪問該public API,我們需要將其export(輸出)。我們的 manifest如下:

Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.infoq.templater
Bundle-Version: 1.0.0
Export-Package: com.infoq.templater.api 

創建一個client bundle

我們現在可以創建一個使用templater的client。利用上面的例子創建一個activator ,其start()方法如下:

package com.infoq.amazon;
import org.osgi.framework.*;
import com.infoq.templater.api.*;
import java.util.*;
public class Client implements BundleActivator {
  public void start(BundleContext context) {
   Map data = new HashMap();
   data.put("tld", "co.uk"); // or "com" or "de" or ...
   data.put("isbn", "1411609255"); // or "1586033115" or ...
   System.out.println( "Starting\n" + 
     TemplaterFactory.getTemplater().template(
     "http://www.amazon.{tld}/dp/{isbn}/", data));
  }
  public void stop(BundleContext context) {
  }
}

我們需要在manifest中顯式輸入templater API,否則我們的bundle無法編譯。用 Import-Package或Require-Bundle都可指定依賴。前者可以讓我們單獨輸入包;後者則將 隱式輸入該bundle中所有輸出包。(多個包及bundles可以用逗號分開)。

Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.infoq.amazon
Bundle-Version: 1.0.0
Bundle-Activator: com.infoq.amazon.Client
Import-Package: org.osgi.framework
Require-Bundle: com.infoq.templater

注意在前面的例子中,我們已經使用了Import-Package來輸入org.osgi.framework。 在這個例子中,我們將演示 Require-Bundle的用法,其使用了Bundle-SymbolicName。當 然,用Import-Package: org.osgi.framework, com.infoq.templater.api可以達到相同 的效果。

不管如何聲明依賴於templater的bundle,我們都只能訪問單一的輸出包 com.infoq.templater。盡管client可以通過 TemplaterFactory.getTemplater()來訪問 templater,但是我們不能直接從internal包中訪問該類。這樣我們在未來就可以在不影 響client的情況下改變templater類的實現。

測試該系統

任何OSGi應用實際上都是一組bundle。在本例中,我們需要編譯並把bundle打包為JAR (如前面所述),啟動OSGi引擎,安裝兩個bundle。下面是在Equinox中進行操作的例子 :

java -jar org.eclipse.osgi_* -console
osgi> install file:///tmp/com.infoq.templater-1.0.0.jar
Bundle id is 1
osgi> install file:///tmp/com.infoq.amazon-1.0.0.jar
Bundle id is 2
osgi> start 2
Starting 
http://www.amazon.co.uk/dp/1411609255 

Amazon client bundle現在已經啟動了;當其啟動時,它先用(硬編碼的)給定值為 我們初始化URI template。然後在該bundle啟動過程中打印出來以確定其已正常工作。當 然,一個真正的系統不會這麼死板;但是Templater服務可以用於任何其他應用(例如, 產生一個基於Web的應用中的鏈接)。將來,我們將能夠在OSGi環境中查看Web應用。

帶版本的依賴

本文最後要指出的是目前我們所談的依賴都沒有版本;更確切的說,我們可以使用任 意版本。兩個bundle整體及各個包都可以有版本,增大minor 號通常 意味著增加了新特 性(但保持向後兼容)。以org.osgi.framework包為例,OSGi R4.1中是1.4.0,OSGi R4.2中是1.5.0。(順便提一句,這是bundle版本和銷售版本保持分離的很好理由,Scala 語言已經這麼做了)。

要聲明依賴處於一個特定版本,我們必須在Import-Package或Require-Bundle中來表 達。比如,我們可以指定 Require- Bundle: com.infoq.templater;bundle- version="1.0.0"以表示工作所需的最低版本為1.0.0。類似的,我們可以用 Import- Package: com.infoq.templater.api;version="1.0.0"做相同的事情 —— 但是要記住 package與bundle版本是完全分開的。如果你不指定版本,缺省為0.0.0,因此,除非指定 了相應的Export-Package: com.infoq.templater.api;version="1.0.0"否則該輸入不會 被解析。

還可以指定一個版本范圍。例如,按慣例OSGi版本的major號增大表示向後兼容發生改 變,因此我們可能想將其限制在1.x的范圍內。我們可以通過 (bundle-) version="[1.0,2.0)"的方式來表達這一依賴約束。該例中,[表示‘包含’,而)表示‘ 不包含’。換句話說,‘從 1.0到2.0但不包括2.0’。實際上,將一個依賴約束表達為‘ 1.0’與“[1.0,∞)”意思是一樣的——換句話說,比1.0版高都可以。

盡管這些內容超出了本文的范圍,但是在一個OSGi系統中,一個bundle同時有兩個版 本也是可能的。如果你有一個老client依賴於1.0版本 API,同時又一個新client依賴於 2.0版本API,這就非常有用了。只要每個bundle的依賴是一致的(換句話說,一個bundle 不能直接或間接同時輸入1.0和2.0)那麼應用程序將工作良好。作為讀者練習,你可以創 建一個使用泛型的2.0版的Templater API,然後讓一個client依賴於1.x,而另一個依賴 於2.x。

總結

在本文中,我們探討了開源OSGi引擎Equinox、Felix和Knopflerfish,並且創建了兩 個有依賴關系的bundle。我們還談到了帶版本的依賴。截止目前,模塊化還是靜態的;我 們還沒有涉及到OSGi的動態本質。在下一篇文章中我們將涉及這一內容。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved