一年一度的Ig Nobel prize典禮都都會帶來一些非常新穎的觀點、發現,這些內容甚至超過了Ig Nobel prizes本身。每位獲獎者在做完七個字的總結后,還有機會利用24秒的時間對自己的新觀點、新發現進行闡述。
這是一個極其絕妙的主意,這對每位獲獎者都是一個需要完成的挑戰。
OSGi
是近來業界經常提到的事物,隨著Equinox成為Eclipse的頂級項目,Felix被用于Sling和Glassfish V3的容器,以及Spring-Modules的發布。但是,很多人都不熟悉OSGi…而且一直不去了解它,也不在意他人正在循序漸進的了解OSGi。
至于我,我不是OSGi的鼓吹者,僅僅是喜歡花些時間試圖向那些還未了解OSGi的朋友進行講解。顯然,做點少量的范例代碼能幫助大家盡快上手。再次強調,我不是OSGi的鼓吹者(也不會站在任何OSGi的立場上。)我僅僅是個普通人,看著OSGi的確不錯,并且它的文檔也不多才寫作本文。當然,如果你很熟悉OSGi,那么不需要閱讀本文。(譯注:這人太羅嗦了,和唐僧有一拼?。?span lang="EN-US">
首先,我給大家做七字總結,然后是24秒的闡述,接著我將用范例來解釋這一切。
OSGi
的七字總結和24秒闡述
OSGi
是一個為Java而設計的組件框架。
24
秒的闡述:OSGi是一個Java框架,該框架能裝載以bundle為單位的資源。Bundle能提供服務或響應處理請求,而他們之間的依賴都是被管理起來的,正如一個bundle能從容器中獲得它所需要的管理。每個bundle都可以有它自己的內部類路徑,所以它可以作為獨立的服務單元。所有的這些符合OSGi規范的bundle理論上都可以安裝在任何符合OSGi規范的容器中。
閑聊一下,不管我念得如何得快都要花掉24秒,貌似我就只能念這么快了。還缺少什么?喔,這則闡述缺少對為何需要這樣一個模塊化系統的解釋。
為什么需要模塊化系統?
模塊化系統為分布式bundle提供了翻譯支持(這里的“bundle”超過了“OSGi bundle”的范疇—我習慣使用這一術語來做比喻。)當然,模塊話系統的依賴關系是個話題,生命周期也是個話題… …有趣。
所有這些都很重要;所謂翻譯并不是使用web service,EJB翻譯依然被強迫通過JNDI方式,jar之間的依賴由并行jar部署來管理(除了JCA和WAR,當然他們也有不同的方式進行依賴管理)。
Java EE
是一套解決方案,盡管這些都不是必要的:WAR和JCA可以包含jar文件,EJB jar通過配置他們的manifest能參考其他jar文件,盡管應用服務器能提供高級的類資源庫;一旦你在相同web應用程序或web service中使用不同的版本,JNDI將提供版本檢測機制。生命周期是為web應用程序(加載并啟動 servlets、上下文監聽器)和JCA而存在的,但是EJB3.1可能會有自己的生命周期機制—還不確定。
現在我們知道了,Java EE就是個棒槌可以搞定一切事情。
OSGi
和JSR-277試圖把Java的模塊化部署標準化起來,而不是強行往Java EE概念上靠攏,這樣也能避免Java EE關于依賴和版本檢測以及生命周期方面的弱點。既然本文以OSGi而非模型為主題,那就集中在OSGi上吧…
開始OSGi
簡單的講,運行OSGi是非常簡單的,基本上沒什么樂趣:尋找一個OSGi容器的實現方案(Equinox、Felix、Knopflerfish、ProSyst),并運行這些容器的啟動命令,有點像在運行Java EE的服務器。類似Java EE,每個容器都有不同的啟動環境和細小的性能差異;請檢查你選擇容器的具體信息和選項。為了更加清晰點,我將在本文中采用Equinox。
Equinox
是一個OSGi容器,你可以從http://download.eclipse.org/eclipse/equinox/下載它。下載的文件是ZIP格式,解壓縮到“eclipse”目錄,不必驚訝:Equinox是Eclipse內置的OSGi容器。(我將把包含所有Equinox發布的頂級目錄“/eclipse”作為$EQUINOX變量。)文檔在Equinox快速入門中可以找到,訪問$EQUINOX/plugins將顯示出很多jar文件:
javax.servlet.jsp_2 .0.0 .v200706191603.jar????????????????????org.eclipse.equinox.jsp.jasper_1 .0.1 .R33x_v20070816.jar
javax.servlet_2 .4.0 .v200706111738.jar????????????????????????org.eclipse.equinox.launcher_1 .0.1 .R33x_v20070828.jar
org.apache.commons.el_1 .0.0 .v200706111724.jar????????????????org.eclipse.equinox.launcher_1 .0.1 .R33x_v20080118.jar
org.apache.commons.logging_1 .0.4 .v200706111724.jar???????????org.eclipse.equinox.log_1 .0.100 .v20070226.jar
org.apache.jasper_5 .5.17 .v200706111724.jar???????????????????org.eclipse.equinox.metatype_1 .0.0 .v20070226.jar
org.eclipse.equinox.app_1 .0.1 .R33x_v20070828.jar?????????????org.eclipse.equinox.preferences_3 .2.100 .v20070522.jar
org.eclipse.equinox.common_3 .3.0 .v20070426.jar???????????????org.eclipse.equinox.preferences_3 .2.101 .R33x_v20080117.jar
org.eclipse.equinox.device_1 .0.0 .v20070226.jar???????????????org.eclipse.equinox.registry_3 .3.1 .R33x_v20070802.jar
org.eclipse.equinox.event_1 .0.100 .v20070516.jar??????????????org.eclipse.equinox.servletbridge_1 .0.1 .R33x_v20070816.jar
org.eclipse.equinox.http.jetty_1 .0.1 .R33x_v20070816.jar??????org.eclipse.equinox.source_3 .3.1 .R33x_r20070918-7n7LECgEKVsLIM1aGBO4b00
org.eclipse.equinox.http.registry_1 .0.0 .v20070608.jar????????org.eclipse.equinox.source_3 .3.1 .R33x_r20070918-7n7LEClEIdwb-bbP_z--EYAO
org.eclipse.equinox.http.registry_1 .0.1 .R33x_v20071231.jar???org.eclipse.equinox.useradmin_1 .0.0 .v20070226.jar
org.eclipse.equinox.http.servlet_1 .0.1 .R33x_v20070816.jar????org.eclipse.osgi.services_3 .1.200 .v20070605.jar
org.eclipse.equinox.http.servletbridge_1 .0.0 .v20070523.jar???org.eclipse.osgi.util_3 .1.200 .v20070605.jar
org.eclipse.equinox.http_1 .0.100 .v20070423.jar???????????????org.eclipse.osgi_3 .3.1 .R33x_v20070828.jar
org.eclipse.equinox.http_1 .0.101 .R33x_v20071016.jar??????????org.eclipse.osgi_3 .3.2 .R33x_v20080105.jar
org.eclipse.equinox.jsp.jasper.registry_1 .0.0 .v20070607.jar??org.mortbay.jetty_5 .1.11 .v200706111724.jar
啟動Equinox很簡單:在這個目錄中,執行java -jar org.eclipse.osgi_
這就是Equinox的OSGi控制臺。從這里開始,你可以安裝新的bundle并啟動、停止、卸載他們,檢查他們的依賴、注冊服務,或者其他東西。你嘗試到的第一個命令將是“ss”,是“short status”的縮寫。如果當前是全新安裝,這樣的交互看起來是這樣的:
Framework?is?launched.
id??????State???????Bundle
0 ???????ACTIVE??????org.eclipse.osgi_3 .3.2 .R33x_v20080105
osgi>
這樣的顯示證明容器是運行著的,并安裝了一個bundle。這個bundle的id為“0”,這個id有很大的作用,因為你可以使用它來控制bundle的生命周期。
那么,bundle有什么優點呢?bundle提供了生命周期和服務暴露,就像我們在24秒的總結中提到一樣。
將一個簡單的資料庫放入Bundle
讓我們創建一個bundle。我想利用資料庫存儲信息。我的最初接口看起來是這樣的:
public ? interface ?RepositoryService?{
????????Node?put(String?path,?Node?content);
????????Node?get(String?path);
}
不是太多,這是一個開始:我可以利用它來存放或提取信息。我的節點類是一個簡單的POJO,看起來是這樣的:
import ?java.util.ArrayList;
import ?java.util.Date;
import ?java.util.List;
public ? class ?Node?{
????????Date?created? = ? new ?Date();
????????String?source? = ? " unknown " ;
????????String?author? = ? " unknown " ;
????????List?contents? = ? new ?ArrayList < String > ();
????????String?path;
????????@Override
???????? public ?String?toString()?{
????????????????String?s? = ? " node:?path= " ? + ?getPath()? + ? " ,?author= " ? + ?getAuthor()
???????????????????????????????? + ? " ,?created= " ? + ?getCreated()? + ? " ,?source= " ? + ?getSource()
???????????????????????????????? + ? " ,?data=[ " ;
????????????????String?separator? = ? "" ;
???????????????? for ?(String?d?:?getContents())?{
????????????????????????s? += ?separator? + ?d;
????????????????????????separator? = ? " , " ;
????????????????}
????????????????s? += ? " ] " ;
???????????????? return ?s;
????????}
???????? public ?Node()?{
????????}
???????? public ?Node(String?content)?{
????????????????getContents().add(content);
????????}
???????? public ?Node(String?content,?String?context)?{
???????????????? this (content);
????????????????setSource(context);
????????}
???????? // ?..?accessors?and?mutators?go?here.
}
這是一個服務實現,它可以獨立于OSGi進行實現和測試;到此為止我們還沒有涉及到OSGi。要向OSGi邁進就應該有個服務的實現。我最初的代碼是以Map為基礎:
import ?java.util.Map;
import ?repository.Node;
import ?repository.RepositoryService;
public ? class ?MapRepositoryService? implements ?RepositoryService?{
????????Map < String,?Node > ?data = new ?HashMap < String,?Node > ();
???????? public ?Node?put(String?path,?Node?content)?{
???????????????? return ?data.put(path,?content);
????????}
???????? public ?Node?get(String?path)?{
???????????????? return ?data.get(path);
????????}
}
然而,一個Map不能很好的實現查詢功能,而且很有可能遇到有層次結構的信息。我想用DOM來代替它。所以我現在將引入XOM,并重新實現資源庫服務:
import ?java.util.Date;
import ?nu.xom.Attribute;
import ?nu.xom.Document;
import ?nu.xom.Element;
import ?nu.xom.Elements;
import ?nu.xom.Nodes;
import ?nu.xom.ParentNode;
import ?repository.Node;
import ?repository.RepositoryService;
public ? class ?XMLRepositoryService? implements ?RepositoryService?{
????????Document?document;
????????Element?data;
???????? public ?XMLRepositoryService()?{
????????????????data? = ? new ?Element( " data " );
????????????????document? = ? new ?Document(data);
????????}
????????Node?toNode(nu.xom.Element?node)?{
???????????????? if ?(node.getAttribute( " source " )? == ? null )?{
???????????????????????? return ? null ;
????????????????}
????????????????Node?n? = ? new ?Node();
????????????????n.setAuthor(node.getAttributeValue( " author " ));
????????????????n.setCreated( new ?Date(node.getAttributeValue( " created " )));
????????????????n.setSource(node.getAttributeValue( " source " ));
????????????????Elements?e? = ?node.getChildElements( " contents " );
????????????????n.setPath(node.getAttributeValue( " path " ));
???????????????? for ?( int ?i? = ? 0 ;?i? < ?e.size();?i ++ )?{
????????????????????????Element?elt? = ?e.get(i);
???????????????????????? for ?( int ?i1? = ? 0 ;?i1? < ?elt.getChildCount();?i1 ++ )?{
????????????????????????????????n.getContents().add(elt.getChild(i1).getValue());
????????????????????????}
????????????????}
???????????????? return ?n;
????????}
????????nu.xom.Element?getElement(String?path)?{
???????????????? while ?( ! path.startsWith( " // " ))?{
????????????????????????path? = ? " / " ? + ?path;
????????????????}
???????????????? while ?(path.endsWith( " / " ))?{
????????????????????????path? = ?path.substring( 0 ,?path.length()? - ? 1 );
????????????????}
????????????????Nodes?nodes? = ?document.query(path);
???????????????? if ?(nodes.size()? > ? 0 )?{
???????????????????????? return ?(Element)?nodes.get( 0 );
????????????????}
???????????????? return ? null ;
????????}
???????? public ?Node?get(String?path)?{
????????????????Element?e? = ?getElement(path);
???????????????? if ?(e? != ? null )?{
???????????????????????? return ?toNode(e);
????????????????}
???????????????? return ? null ;
????????}
???????? public ?Node?put(String?path,?Node?content)?{
????????????????Element?oldElt? = ?getElement(path);
???????????????? if ?(oldElt? != ? null )?{
???????????????????????? // ?need?to?remove?this?node!
????????????????????????ParentNode?p? = ?oldElt.getParent();
????????????????????????p.removeChild(oldElt);
????????????????}
????????????????StringBuilder?pathBuilder = new ?StringBuilder( " / " );
????????????????String[]?tree? = ?path.split( " / " );
????????????????Element?node? = ?data;
???????????????? for ?(String?t?:?tree)?{
???????????????????????? if ?(t.length()? != ? 0 )?{
????????????????????????????????Element?child? = ?node.getFirstChildElement(t);
???????????????????????????????? if ?(child? == ? null )?{
???????????????????????????????????????? // System.err.println("creating?new?"+t);
????????????????????????????????????????child? = ? new ?Element(t);
????????????????????????????????????????node.appendChild(child);
????????????????????????????????}
????????????????????????????????pathBuilder.append( ' / ' );
????????????????????????????????pathBuilder.append(t);
????????????????????????????????node? = ?child;
????????????????????????}
????????????????}
????????????????node.addAttribute( new ?Attribute( " created " ,?content.getCreated()
????????????????????????????????.toString()));
????????????????node.addAttribute( new ?Attribute( " source " ,?content.getSource()));
????????????????node.addAttribute( new ?Attribute( " author " ,?content.getAuthor()));
????????????????content.setPath(pathBuilder.toString());
????????????????node.addAttribute( new ?Attribute( " path " ,?content.getPath()));
????????????????Element?contents? = ? new ?Element( " contents " );
????????????????node.appendChild(contents);
???????????????? for ?(String?c?:?content.getContents())?{
????????????????????????Element?e? = ? new ?Element( " content " );
????????????????????????e.appendChild(c);
????????????????????????contents.appendChild(e);
????????????????}
???????????????? // System.out.println(data.toXML());
???????????????? return ? null ;
????????}
???????? public ? static ? void ?main(String[]?args)?{
????????????????XMLRepositoryService?s? = ? new ?XMLRepositoryService();
????????????????s.put( " /foo/bar/baz " ,? new ?Node( " stuff " ));
????????????????Node?n? = ? new ?Node( " bletch " );
????????????????n.setAuthor( " jottinger " );
????????????????s.put( " /foo/bar/bletch " ,?n);
????????????????System.out.println(s.get( " foo/bar/baz/ " ));
????????????????System.out.println(s.get( " //foo/bar/baz " ));
????????????????System.out.println(s.get( " //foo/bar/bletch " ));
????????????????System.out.println(s.get( " foo/bar/ " ));
????????????????System.out.println(s.get( " //*[@author='jottinger'] " ));
????????}
}
這離完美還有很長的距離,不過總算是個好的開始。然而,我們還是沒涉及到任何關于OSGi方面的東西—我們只有一個無關緊要的資源庫類。然后我們來看看OSGi模塊是怎樣被構建的。
轉移目標:根據依賴關系構造OSGi bundle
一個OSGi模塊是一個.jar文件,在這里,它應該遵循標準的.jar文件規范,除此之外還有一部分信息應該放進manifest文件中。
讓我們創建一個簡單的模塊,首先,最簡單的它應該能在啟動和結束的時候顯示消息,作為內部依賴將引入一個.jar文件。這個jar文件對其他bundle是不可見的-它只是作為我們的范例bundle的資源,在鄙文中這是一個很簡單和普通的需求。(是的,不怎么樣的例子-我在尋找更簡單的,但沒找到。)依賴將放在jar中,baselib.jar,這里面只有一個類:baselib.BaseService。
import ?java.util.logging.Logger;
public ? class ?BaseService?{
??Logger?log = Logger.getLogger( this .getClass().getName());
?? public ? void ?sayHello()?{
????log.info( " Hello,?world! " );
??}
}
接下來要做的是把編譯好的文件放進.jar中,這就是baselib.jar。
現在,一個OSGi bundle需要一個“activator”,是一個管理bundle生命周期的類。一個
import ?baselib.BaseService;
import ?org.osgi.framework.BundleActivator;
import ?org.osgi.framework.BundleContext;
import ?java.util.logging.Logger;
public ? class ?TutorialActivator? implements ?BundleActivator?{
??Logger?log = Logger.getLogger( this .getClass().getName());
?? public ? void ?start(BundleContext?bc)?{
????log.info( " started " );
???? new ?BaseService().sayHello();
??}
?? public ? void ?stop(BundleContext?bc)?{
????log.info( " stopped. " );
??}
}
我們不只是把這些東西放進一個.jar文件,然后就能工作了,真不巧(Spring-OSGi可以派上用場,但是這超出了本文的范圍。)我們還需要以一種特定的文件集和特定的結構來創建tutorialbundle.jar。首先,baselib.jar應該放在我們新的jar文件的根目錄。我們還需要一個MANIFEST.MF文件,它包含了一些OSGi配置和啟動信息:
Bundle-ManifestVersion:? 2
Bundle-SymbolicName:?com.theserverside.tutorial.osgi.TutorialBundle
Bundle-Version:? 1
Bundle-Activator:?tutorial.TutorialActivator
Import-Package:?org.osgi.framework ; version="1.3.0"
Bundle-ClassPath:?. , baselib.jar
這個manifest文件假設相應的jar像這樣:
????? 0 ?Thu?Apr? 17 ? 11 : 57 : 14 ?EDT? 2008 ?META-INF/
??? 391 ?Thu?Apr? 17 ? 11 : 57 : 12 ?EDT? 2008 ?META-INF/MANIFEST.MF
????? 0 ?Thu?Apr? 17 ? 11 : 29 : 56 ?EDT? 2008 ?tutorial/
??? 714 ?Thu?Apr? 17 ? 11 : 51 : 02 ?EDT? 2008 ?tutorial/TutorialActivator.class
??? 902 ?Thu?Apr? 17 ? 11 : 15 : 28 ?EDT? 2008 ?baselib.jar
這里的要點是我們的manifest文件引入了包(只是一些OSGi框架需要的包)、activator類文件名以及bundle類路徑-以逗號隔開的在jar中的資源集。如果我們能再次重復實現這個結構,說明我們已經準備好在Equinox中安全和運行bundle了。
osgi>?ss
Framework?is?launched.
id??????State???????Bundle
0 ???????ACTIVE??????org.eclipse.osgi_3 .3.2 .R33x_v20080105
osgi>?install?file:///workspaces/osgi/tutorial/tutorialbundle.jar
Bundle?id?is? 4
osgi>?start? 4
Apr? 17 , ? 2008 ? 11 : 57 : 29 ?AM?tutorial.TutorialActivator?start
INFO:?started
Apr? 17 , ? 2008 ? 11 : 57 : 29 ?AM?baselib.BaseService?sayHello
INFO:?Hello , ?world!
osgi>?stop? 4
Apr? 17 , ? 2008 ? 1 : 29 : 25 ?PM?tutorial.TutorialActivator?stop
INFO:?stopped.
osgi>
現在我們知道如何構建bundle了,并知道如何進行相關依賴jar的部署(PS:
后面的資源庫類將依賴XOM。)
構建我們的資源庫Bundle
現在我們可以搞定自己的資源庫bundle了,需要在Activator中添加些功能:把資源庫注冊成服務并發布它,所以其他bundle也可以使用我們的資源庫進行查詢。
import ?java.util.Hashtable;
import ?org.osgi.framework.BundleActivator;
import ?org.osgi.framework.BundleContext;
import ?org.osgi.util.tracker.ServiceTracker;
import ?repository.impl.XMLRepositoryService;
public ? class ?Activator? implements ?BundleActivator?{
???????? /**
?????????*? @see ?org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
????????? */
???????? public ? void ?start(BundleContext?context)? throws ?Exception?{
???????????????? // ?register?the?service
????????????????context.registerService(
????????????????????????????????RepositoryService. class .getName(),
???????????????????????????????? new ?XMLRepositoryService(),
???????????????????????????????? new ?Hashtable < Object,Object > ());
???????????????? // ?create?a?tracker?and?track?the?log?service
????????????????ServiceTracker?repositoryServiceTracker? =
???????????????????????? new ?ServiceTracker(context,?RepositoryService. class .getName(),? null );
????????????????repositoryServiceTracker.open();
???????????????? // ?grab?the?service
????????????????RepositoryService?repositoryService? = ?(RepositoryService)?repositoryServiceTracker.getService();
????????????????System.err.println( " RepositoryService?Activated " );
????????}
???????? /*
?????????*?(non-Javadoc)
?????????*?@see?org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
????????? */
???????? public ? void ?stop(BundleContext?context)? throws ?Exception?{
???????????????? // ?close?the?service?tracker
????????????????System.err.println( " RepositoryService?Deactivated " );
????????}
}
注意,上面的代碼把XMLRepositoryService硬編碼進去了,有點不爽。我們可以用Spring、或者Service Provider Interface、或者環境變量、或者—甚至是OSGi青睞的方式,不過這些都超出了本文的范圍。讓我們開始服務部署,然后我們將揭示如何在其他bundle中調用它。
根據我們的第一個范例bundle,我們的資源庫bundle的相關信息將組成manifest文件和形成目錄結構。下面是目錄結構:
????? 0 ?Thu?Apr? 17 ? 14 : 08 : 46 ?EDT? 2008 ?META-INF/
??? 553 ?Thu?Apr? 17 ? 14 : 08 : 44 ?EDT? 2008 ?META-INF/MANIFEST.MF
????? 0 ?Thu?Apr? 17 ? 13 : 57 : 12 ?EDT? 2008 ?repository/
????? 0 ?Thu?Apr? 17 ? 13 : 57 : 12 ?EDT? 2008 ?repository/impl/
?? 1383 ?Thu?Apr? 17 ? 13 : 57 : 12 ?EDT? 2008 ?repository/Activator.class
?? 2095 ?Thu?Apr? 17 ? 13 : 57 : 12 ?EDT? 2008 ?repository/Node.class
??? 205 ?Thu?Apr? 17 ? 13 : 57 : 12 ?EDT? 2008 ?repository/RepositoryService.class
??? 694 ?Thu?Apr? 17 ? 13 : 57 : 12 ?EDT? 2008 ?repository/impl/MapRepositoryService.class
?? 3823 ?Thu?Apr? 17 ? 13 : 57 : 12 ?EDT? 2008 ?repository/impl/XMLRepositoryService.class
895924 ?Thu?Apr? 17 ? 14 : 03 : 40 ?EDT? 2008 ?xerces- 2.4.0 .jar
109318 ?Thu?Apr? 17 ? 14 : 05 : 08 ?EDT? 2008 ?xml-apis- 1.0 .b2.jar
431568 ?Thu?Apr? 17 ? 13 : 54 : 06 ?EDT? 2008 ?xom- 1.1 .jar
And?the?manifest?file:
Manifest-Version:? 1.0
Bundle-ManifestVersion:? 2
Bundle-Name:?Repository?Plug-in
Bundle-SymbolicName:?repository
Bundle-Version:? 1.0.0
Bundle-Activator:?repository.Activator
Bundle-Vendor:?theserverside.com
Import-Package:?org.osgi.framework ; version="1.3.0",
?org.osgi.util.tracker ; version="1.3.1"
Export-Package:?repository ; uses:="org.osgi.framework"
Bundle-ClassPath:?. , xom- 1.1 .jar , xerces- 2.4.0 .jar , xml-apis- 1.0 .b2.jar
我們在干什么呢?我們在創建一個jar,使用我們之前寫好的Activator實現類,以及數個依賴包:Xerces的實現包XOM,以及ServiceTracker API。
我們也干了一件有趣的事情:我們把資源庫bundle暴露出來。這意味著在OSGi容器中的其他的bundle能引入那些bundle,也可以通過詳細的名稱來查詢服務(這樣的話,資源庫被注冊為“RepositoryService“接口,或者“repository.RepositoryService“。)接下來安裝bundle并啟動它:
Bundle?id?is? 11
osgi>?start? 11
RepositoryService?Activated
osgi>
目前為止一點都不令人興奮,但是我們已經在利用OSGi基礎部件工作了。需要注意的是,我們把接口和實現都放進去了。理想情況下,RepositoryService可以放在自己的jar中,所以我們能分離接口和實現。這并不困難,甚至從bundle的觀點看;在activator 中調用bundle接口,你不需要做任何事情,而在bundle實現中,你應該從其他地方導入接口bundle。我們在這里并沒有這樣干,因為這樣做會走很多彎路,相應的也會減緩開發速度。
在其他Bundle中調用我們的OSGi Bundle
最后步驟是構建例外一個bundle—但它將會找到資源庫服務并使用之。
讓我們先來看看Bundle Activator。的確很簡單,基本上沒什么功能:當bundle啟動時,它先查找RepositoryService,并在往這個服務中存入數據。它使用stop()機制來實際查找資源庫里面的數據并顯示在控制臺上。這不是一個嚴謹的測試,但這足以證明流程的行為:
import ?org.osgi.framework.BundleActivator;
import ?org.osgi.framework.BundleContext;
import ?org.osgi.framework.ServiceReference;
import ?java.util.logging.Logger;
import ?repository.Node;
import ?repository.RepositoryService;
public ? class ?SampleActivator? implements ?BundleActivator?{
????????Logger?log = Logger.getLogger( this .getClass().getName());
???????? /*
?????????*?(non-Javadoc)
?????????*?@see?org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
????????? */
???????? public ? void ?start(BundleContext?context)? throws ?Exception?{
????????????????ServiceReference?ref? = ?context.getServiceReference(
????????????????RepositoryService. class .getName());
????????????????RepositoryService?lookup? = ?(RepositoryService)?context.getService(ref);
????????????????Node?testNode = new ?Node( " this?is?some?content " );
????????????????lookup.put( " /foo/bar/baz " ,?testNode);
????????????????log.info( " /foo/bar/baz?stored. " );
????????}
???????? /*
?????????*?(non-Javadoc)
?????????*?@see?org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
????????? */
???????? public ? void ?stop(BundleContext?context)? throws ?Exception?{
????????????????ServiceReference?ref? = ?context.getServiceReference(
????????????????RepositoryService. class .getName());
????????????????RepositoryService?lookup? = ?(RepositoryService)?context.getService(ref);
????????????????log.info(lookup.get( " //*/baz " ));
????????}
}
The?MANIFEST.MF?file?looks?like? this :
Manifest - Version:? 1.0
Bundle - ManifestVersion:? 2
Bundle - Name:?Repository?Sample?Plug - in
Bundle - SymbolicName:?samplerepouser
Bundle - Version:? 1.0 . 0
Bundle - Activator:?sample.SampleActivator
Bundle - Vendor:?theserverside.com
Import - Package:?org.osgi.framework;version = " 1.3.0 "
Require - Bundle:?repository
在上面最后一行中。如果你檢查RepositoryService的manifest,字符名字是“repository.”,在這里我們說bundle應該能訪問任何被參考的bundle暴露的類,換句話說,一旦我們的資源庫bundle暴露了“repository”包,我們的SampleActivator就能夠直接導入這個資源庫類而不是自己再用包來組織。
我們構建了sample.jar bundle,結構是這樣的:
????? 0 ?Thu?Apr? 17 ? 14 : 47 : 48 ?EDT? 2008 ?META-INF/
??? 421 ?Thu?Apr? 17 ? 14 : 47 : 46 ?EDT? 2008 ?META-INF/MANIFEST.MF
????? 0 ?Thu?Apr? 17 ? 14 : 47 : 48 ?EDT? 2008 ?sample/
?? 1270 ?Thu?Apr? 17 ? 14 : 47 : 48 ?EDT? 2008 ?sample/SampleActivator.class
注意一個簡單的地方:這個bunlde除了activator沒什么其他內容了。沒有服務實現,甚至接口。
我們安裝并開啟這個bundle,然后再停止,是否和預期的效果一樣:
Bundle?id?is? 17
osgi>?start? 17
Apr? 17 , ? 2008 ? 2 : 49 : 07 ?PM?sample.SampleActivator?start
INFO:?/foo/bar/baz?stored.
osgi>?stop? 17
Apr? 17 , ? 2008 ? 2 : 49 : 09 ?PM?sample.SampleActivator?stop
INFO:?node:?path = //foo/bar/baz , ?author = unknown , ?created = Thu?Apr? 17 ? 14 : 49 : 07 ?EDT? 2008 , ?source = unknown , ?data = [ this?is?some?content ]
osgi>
在另一個OSGi容器中運行我們的Bundle
OSGi
的強大力量之一是容器的“平臺無關”,就像Java EE模塊能部署到任何兼容的容器中一樣?,F在我們花點時間來展示我們之前寫好的bundle部署到Felix上—Apache的OSGi容器,再看看在其他容器上bundle看起來是什么樣子的。Felix首先需要你對當前配置命名,如果你用一樣的名字的話它可以重新載入,在這里的范例,我們將把它叫做“tutorial
Welcome?to?Felix.
=================
Enter?profile?name:?tutorial01
DEBUG:?WIRE:? 1.0 ?->?org.ungoverned.osgi.service.shell?->? 1.0
DEBUG:?WIRE:? 1.0 ?->?org.osgi.service.startlevel?->? 0
DEBUG:?WIRE:? 1.0 ?->?org.apache.felix.shell?->? 1.0
DEBUG:?WIRE:? 1.0 ?->?org.osgi.framework?->? 0
DEBUG:?WIRE:? 1.0 ?->?org.osgi.service.packageadmin?->? 0
DEBUG:?WIRE:? 2.0 ?->?org.apache.felix.shell?->? 1.0
DEBUG:?WIRE:? 2.0 ?->?org.osgi.framework?->? 0
DEBUG:?WIRE:? 3.0 ?->?org.osgi.framework?->? 0
DEBUG:?WIRE:? 3.0 ?->?org.osgi.service.obr?->? 3.0
DEBUG:?WIRE:? 3.0 ?->?org.apache.felix.shell?->? 1.0
->?install?file:///workspaces/osgi/tutorial/tutorialbundle.jar
Bundle?ID:? 7
->?start? 7
DEBUG:?WIRE:? 7.0 ?->?org.osgi.framework?->? 0
Apr? 18 , ? 2008 ? 10 : 46 : 37 ?AM?tutorial.TutorialActivator?start
INFO:?started
Apr? 18 , ? 2008 ? 10 : 46 : 37 ?AM?baselib.BaseService?sayHello
INFO:?Hello , ?world!
->?install?file:///workspaces/osgi/tutorial/repositorybundle.jar
Bundle?ID:? 8
->?start? 8
DEBUG:?WIRE:? 8.0 ?->?org.osgi.util.tracker?->? 0
DEBUG:?WIRE:? 8.0 ?->?org.osgi.framework?->? 0
RepositoryService?Activated
->?install?file:///workspaces/osgi/tutorial/samplebundle.jar
Bundle?ID:? 9
->?start? 9
DEBUG:?WIRE:? 9.0 ?->?org.osgi.framework?->? 0
DEBUG:?WIRE:? 9.0 ?->?module ; bundle-symbolic-name="repository";bundle-version="1.0.0"?->?8.0
Apr? 18 , ? 2008 ? 10 : 47 : 08 ?AM?sample.SampleActivator?start
INFO:?/foo/bar/baz?stored.
->?stop? 9
Apr? 18 , ? 2008 ? 10 : 47 : 09 ?AM?sample.SampleActivator?stop
INFO:?node:?path = //foo/bar/baz , ?author = unknown , ?created = Fri?Apr? 18 ? 10 : 47 : 07 ?EDT? 2008 , ?source = unknown , ?data = [ this?is?some?content ]
->?shutdown
->?RepositoryService?Deactivated
Apr? 18 , ? 2008 ? 10 : 47 : 12 ?AM?tutorial.TutorialActivator?stop
INFO:?stopped.
一個實際的應用程序,類似IRC Bot
非常清晰的看到資源庫范例是如何運行的—但是測試是沒什么樂趣的。讓我們再把這個測試更進一步,引入一個IRC bot。我們的IRC bot將使用PircBot,因為學習它的API沒什么難度,IRC bot將加入某個IRC網絡(irc.freenode.net的"#pircbot"頻道)的頻道,將響應兩個外部命令:~set和~get。~set將獲取一個路徑和一些文字,并把文字加入到路徑中;而~get將從路徑中獲取信息。同時,這個例子極其簡單并且也不能達到infobot的水平;那就把這個例子留下來給讀者練習,并賦予它更多功能吧。
首先要做的事情是建立一個查詢服務的通用方式。的確還沒有最好的辦法實現!有多種不同模式可選;現在有個簡單而不是最好的方式來快速展開。我們先創建ServiceLookup接口,接著為OSGi實現ServiceLookup接口。
public ? interface ?ServiceLookup?{
????Object?getService(String?name);
}
package ?service.osgi;
import ?org.osgi.framework.BundleContext;
import ?org.osgi.framework.ServiceReference;
import ?service.ServiceLookup;
public ? class ?OSGIServiceLookupImpl? implements ?ServiceLookup?{
????BundleContext?ctx;
???? public ?OSGIServiceLookupImpl(BundleContext?ctx)?{
???????? this .ctx? = ?ctx;
????}
???? public ?Object?getService(String?name)?{
????????ServiceReference?ref? = ?ctx.getServiceReference(name);
???????? return ?ctx.getService(ref);
????}
}
創建OSGIServiceLookupImpl是很簡單的,只是它的構造函數傳入了Activator的 BundleContext:
import ?org.jibble.pircbot.IrcException;
import ?org.jibble.pircbot.NickAlreadyInUseException;
import ?org.osgi.framework.BundleActivator;
import ?org.osgi.framework.BundleContext;
import ?service.osgi.OSGIServiceLookupImpl;
import ?java.io.IOException;
public ? class ?BotActivator? implements ?BundleActivator?{
????IRCBot?bot? = ? null ;
???? public ? void ?start( final ?BundleContext?context)? throws ?Exception?{
???????? try ?{
????????????bot? = ? new ?IRCBot( new ?OSGIServiceLookupImpl(context));
????????????bot.setVerbose( true );
????????????bot.connect( " irc.freenode.net " );
????????????bot.joinChannel( " #pircbot " );
????????}? catch ?(NickAlreadyInUseException?e)?{
????????????e.printStackTrace();
????????}? catch ?(IOException?e)?{
????????????e.printStackTrace();
????????}? catch ?(IrcException?e)?{
????????????e.printStackTrace();
????????}
????}
???? public ? void ?stop(BundleContext?context)? throws ?Exception?{
????????bot.disconnect();
????????bot.dispose();
????}
}
下面的代碼全是在創建IRCBot實例,要用到ServiceLookup實現:
import ?org.jibble.pircbot.PircBot;
import ?service.ServiceLookup;
import ?repository.RepositoryService;
import ?repository.Node;
public ? class ?IRCBot? extends ?PircBot?{
????ServiceLookup?service;
???? public ?IRCBot(ServiceLookup?service)?{
???????? super ();
???????? this .service = service;
????????setName( " OSGIBot " );
????}
????@Override
???? protected ? void ?onMessage(String?channel,?String?sender,?String?login,?String?hostname,?String?message)?{
????????String[]?command = message.split( " ? " );
???????? if (command.length > 1 ? && ?( " ~set " .equals(command[ 0 ])? || ? " ~get " .equals(command[ 0 ])))?{
????????????String?path = command[ 1 ];
???????????? // ?we?should?use?a?tracker?for?this!
????????????RepositoryService?repository = ?(RepositoryService)?service.getService(RepositoryService. class .getName());
???????????? if ( " ~set " .equals(command[ 0 ]))?{
????????????????StringBuilder?content = new ?StringBuilder();
???????????????? for ( int ?i = 2 ;i < command.length;i ++ )?{
????????????????????content.append( " ? " );
????????????????????content.append(command[i]);
????????????????}
????????????????Node?node = repository.get(path);
???????????????? if (node == null )?{
????????????????????node = new ?Node();
????????????????????node.setAuthor(sender);
????????????????????node.setSource( " irc " );
????????????????}
????????????????node.getContents().add(content.toString().trim());
????????????????repository.put(path,?node);
????????????}
???????????? if ( " ~get " .equals(command[ 0 ]))?{
????????????????Node?node = repository.get(path);
???????????????? if (node != null )?{
???????????????????? int ?count = 0 ;? // ?will?only?do?two?at?most,?to?be?polite
???????????????????? for (String?content:node.getContents())?{
???????????????????????? if (count ++> 2 )?{
???????????????????????????? break ;
????????????????????????}
????????????????????????sendMessage(channel,?sender? + ? " :? " + content);
????????????????????}
????????????????}
????????????}
????????}
????}
}
最后,我們的MANIFEST.MF文件指明了類路徑(包含在pircbot.jar中),以及activator名字(“ircbot.BotActivator”),以及對“repository”的外部依賴:
Bundle-ManifestVersion:? 2
Bundle-Name:?IRCBot?Plug-in
Bundle-SymbolicName:?ircbot
Bundle-Version:? 1.0.0
Bundle-Activator:?ircbot.BotActivator
Bundle-Vendor:?theserverside.com
Import-Package:?org.osgi.framework ; version="1.3.0"
Require-Bundle:?repository
Bundle-ClassPath:?pircbot.jar , .
安裝并開啟這個bundle(廢話多),它將連接到Freenode并加入#pircbot。注意這里沒有對昵稱沖突進行處理;你可以自己寫點代碼搞定,或者修改默認的昵稱。這不是一些好代碼,人人都可以放任何東西進去,經不住測試…
這里并不是說只有IRCBot才能使用資源庫。理論上,一個jabber客戶端也可以用相同的資源庫(可以用相似的代碼來處理。)事實上,這就是OSGi的亮點: IRCBot中處理命令的代碼能在一個bundle內部獨立運行,并且如果需要的話,IRCBot也能很容易的調用適當的bundle管理命令,一旦要求訪問資源庫,它們能立刻查找資源庫服務。
結論
希望,本文能啟發讀者去探求OSGi的潛能,并能讓讀者知道如何開始使用它。
?請注意!引用、轉貼本文應注明原譯者:RosenJiang 以及出處:
http://www.aygfsteel.com/rosen