某些因緣際會,最近稍微研究一下OSGi Framework,但對於這個技術,網路上較多的都是一些框架概念,實作分享不多,就連Eclipse本身直接拿內建的Plug-in建立專案也沒辦法跑(奇怪...Eclipse不是基於OSGi去開發的嗎?) 以下是個人簡易實作分享。
什麼是OSGi:
網路上很多介紹,這裡僅做簡介:OSGi是一個Framework(框架)、是一個概念,框架往往就是好幾個Design pattern構成,用來幫助開發者統一建構系統的標準架構;面臨一個大型系統建置,絕對不會把所有程式碼都放在同一個Project(專案),因為這樣不好維護、分包撰寫,所以OSGi提供一個切割子Project的方法,每一個子專案在OSGi之上視為一個Bundle,每個Bundle可以擁有自我獨立的生命週期,如果撇開Dependency(相依性)不管,每個Bundle可以自由地透過OSGi統一的Command line進行管理(啟動/關閉/重啟...等等),而不影響其他Bundle的運作。
換句話說,假設,假設今天你開了一台基於OSGi框架製造出來的車子上路,你開到路上車子還在跑,但是你卻突然發現路旁有家新開的輪胎行,德國H牌輪胎之類的,看起來很厲害,然後你臨時想換個輪胎但又不想停下來,只要今天你的輪子也是基於OSGi製造,轉動輪子的軸承也是,那你就可以在車子還在跑的情況之下把輪胎換成心愛的H牌輪胎(什麼爛比喻?!)
先來提提Eclipse內建的OSGi專案:(這個實作並沒有完全成功,網路上找到的bug shooting也沒什麼用)
- 下載並安裝Eclipse,https://www.eclipse.org/downloads/
- 建立workspace,點選File -> New -> Other -> Plug-in Project
- 進入對話框Plug-in Project,記得把下面的Target Plateform換成an OSGi framework,點選Next
- 進入對話框Content,Execution Environment不要動,記得還是選JavaSE即可,不要換成OSGi/Minimum,否則等專案跑起來會沒有osgi.jar檔,然後Next
- 進入對話框Templates,點選Hello OSGi Bundle,然後Finish. 專案應該就建立完成了。(Eclipse好棒啊)
- 執行專案,沒有問題可以執行,Console很開心的跟你說Hello World!
- 悲劇來了,接著馬上噴出一堆錯誤訊息...一句話,別花時間去解了,聽說是專案自己initial其他沒用的jar,然後那一拖拉庫的jar也不知道要關掉哪些才有用,因為關了一些,又噴出其他的錯誤訊息...
- 推薦使用Equinox,他實作了OSGi,並且有定期的update,官方連結http://download.eclipse.org/equinox
- 個人基於穩定為理由,先抓了ver 3.7.2進行試用,點進去後有整包的SDK、Framework Only,基本上我們只需要org.eclipse.osgi_3.7.2.v20120110-1415.jar,請下載到Local端開發環境
- 其實有了這個jar檔之後就可以用cmd呼叫java來試用OSGi了,但要了解車子跟輪胎的關係,還是從程式來說明會比較清楚
- 再來是設置Eclipse的Target platform,一定要設!用IDE的預設值會噴錯,由於個人有點懶,直接用一張圖說明步驟,完成IDE所需的環境建立
實作Hello World:(第一個Bundle)
- 首先按照上方的先來提提Eclipse內建的OSGi專案 + 那我們該怎麼辦完成基本的OSGi專案建立,但這個只是幫我們建立一些專案需要的.mf檔、目錄結構,而不是我們主要要跑的程式。建完了應該會看到這樣
- 左上方的目錄結構會長這樣,/META-INF/MANIFEST.MF紀錄的是整個Bundle的啟動資訊,目前都是Eclipse幫我們建的,等一下會講怎麼改它
- 實作BundleActivator後,必須要去實作start()、stop()等method,這就是整個Bundle的進入點,啟動後會自動叫用start(),關閉時會叫用stop()
- 底下是Console,啟動之後會看到osgi> 的命令聽,下了ss(即時狀態列表)命令可以傳回目前有兩個Bundles正在被執行(Active)
修改第一個自己的Bundle,好讓第二個Bundle來用它:
[修改Activator.java]
public class Activator implements BundleActivator {
private List<ServiceRegistration> registrations = new ArrayList<ServiceRegistration>();
public void start(BundleContext ctx) {
registrations.add(ctx.registerService(Hello.class.getName(),
new HelloImpl("Hello, OSGi"), null));
}
public void stop(BundleContext ctx) {
for (ServiceRegistration registration : registrations) {
System.out.println("unregistering: " + registration);
registration.unregister();
}
}
}
[新增Hello.java的interface]
package aho.osgi.helloworld;
public interface Hello {
void sayHello();
}
[修改Activator.java]
public class Activator implements BundleActivator {
private List<ServiceRegistration> registrations = new ArrayList<ServiceRegistration>();
public void start(BundleContext ctx) {
registrations.add(ctx.registerService(Hello.class.getName(),
new HelloImpl("Hello, OSGi"), null));
}
public void stop(BundleContext ctx) {
for (ServiceRegistration registration : registrations) {
System.out.println("unregistering: " + registration);
registration.unregister();
}
}
}
[新增Hello.java的interface]
package aho.osgi.helloworld;
public interface Hello {
void sayHello();
}
[新增HelloImle.java來實作Hello]
package aho.osgi.helloworld.impl;
import aho.osgi.helloworld.Hello;
public class HelloImpl implements Hello {
final String helloString;
public HelloImpl(String helloString){
this.helloString = helloString;
}
public void sayHello() {
System.out.println(this.helloString);
}
}
[再來修改MANIFEST.MF,確保啟動對象正確]
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: aho.osgi.helloworld
Bundle-Version: 1.0
Bundle-Activator: aho.osgi.helloworld.activator.Activator
Import-Package: org.osgi.framework
Export-Package: aho.osgi.helloworld;version="1.0"
再來是做出第二個Bundle,並利用第一個Bundle做另一個服務:
[新建OSGi Project,過程略]
[新增HelloUser.java]
package aho.osgi.helloworld.client;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import aho.osgi.helloworld.Hello;
public class HelloUser implements BundleActivator {
public void start(BundleContext ctx) throws Exception {
ServiceReference ref = ctx.getServiceReference(Hello.class.getName());
if (ref != null) {
Hello hello = null;
try {
hello = (Hello) ctx.getService(ref);
if (null != hello) {
hello.sayHello();
} else {
System.out.println("Service:Hello---object null");
}
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
ctx.ungetService(ref);
hello = null;
}
} else {
System.out.println("Service:Hello---not exists");
}
}
public void stop(BundleContext arg0) throws Exception {
System.out.println("HelloUser.stop()");
}
}
[再來修改MANIFEST.MF,確保啟動對象正確]
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: aho.osgi.helloworld.client
Bundle-Version: 1.0
Bundle-Activator: aho.osgi.helloworld.client.HelloUser
Import-Package: aho.osgi.helloworld;version="1.0.0", org.osgi.framework
兩個Bundle運行結果如下:
- Package Explorer -> OSGi-HelloWorld是第一個Bundle
- Package Explorer -> OSGi-Client是第二個Bundle
- Console上可以透過ss指令查到有三個Bundle正在run,包含Equinox本身,然後我們下了star + id、stop + id、 refresh + id 等指令,可以發現生命週期很正常的在運行,嗯!!成功
- 透過stop我們可以把輪子(OSGi-Client)換掉,但是車子(OSGi-HelloWorld)還在跑,換完輪子可以透過start跟軸承無縫接上,簡單解釋
寫得很輕楚,感謝!
回覆刪除交流分享
回覆刪除