2014-03-12

OSGi簡易實作於Eclipse



某些因緣際會,最近稍微研究一下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也沒什麼用)
  1. 下載並安裝Eclipse,https://www.eclipse.org/downloads/
  2. 建立workspace,點選File -> New -> Other -> Plug-in Project
  3. 進入對話框Plug-in Project,記得把下面的Target Plateform換成an OSGi framework,點選Next
  4. 進入對話框Content,Execution Environment不要動,記得還是選JavaSE即可,不要換成OSGi/Minimum,否則等專案跑起來會沒有osgi.jar檔,然後Next
  5. 進入對話框Templates,點選Hello OSGi Bundle,然後Finish. 專案應該就建立完成了。(Eclipse好棒啊)
  6. 執行專案,沒有問題可以執行,Console很開心的跟你說Hello World! 
  7. 悲劇來了,接著馬上噴出一堆錯誤訊息...一句話,別花時間去解了,聽說是專案自己initial其他沒用的jar,然後那一拖拉庫的jar也不知道要關掉哪些才有用,因為關了一些,又噴出其他的錯誤訊息...
那我們該怎麼辦
,簡單的說就是我們只能includ最基本的OSGi.jar
  1. 推薦使用Equinox,他實作了OSGi,並且有定期的update,官方連結http://download.eclipse.org/equinox
  2. 個人基於穩定為理由,先抓了ver 3.7.2進行試用,點進去後有整包的SDK、Framework Only,基本上我們只需要org.eclipse.osgi_3.7.2.v20120110-1415.jar,請下載到Local端開發環境
  3. 其實有了這個jar檔之後就可以用cmd呼叫java來試用OSGi了,但要了解車子跟輪胎的關係,還是從程式來說明會比較清楚
  4. 再來是設置Eclipse的Target platform,一定要設!用IDE的預設值會噴錯,由於個人有點懶,直接用一張圖說明步驟,完成IDE所需的環境建立
實作Hello World:(第一個Bundle)
  1. 首先按照上方的先來提提Eclipse內建的OSGi專案 那我們該怎麼辦完成基本的OSGi專案建立,但這個只是幫我們建立一些專案需要的.mf檔、目錄結構,而不是我們主要要跑的程式。建完了應該會看到這樣
  2. 左上方的目錄結構會長這樣,/META-INF/MANIFEST.MF紀錄的是整個Bundle的啟動資訊,目前都是Eclipse幫我們建的,等一下會講怎麼改它
  3. 實作BundleActivator後,必須要去實作start()、stop()等method,這就是整個Bundle的進入點,啟動後會自動叫用start(),關閉時會叫用stop()
  4. 底下是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();
}

[新增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運行結果如下:
  1. Package Explorer -> OSGi-HelloWorld是第一個Bundle
  2. Package Explorer -> OSGi-Client是第二個Bundle
  3. Console上可以透過ss指令查到有三個Bundle正在run,包含Equinox本身,然後我們下了star + id、stop + id、 refresh + id 等指令,可以發現生命週期很正常的在運行,嗯!!成功
  4. 透過stop我們可以把輪子(OSGi-Client)換掉,但是車子(OSGi-HelloWorld)還在跑,換完輪子可以透過start跟軸承無縫接上,簡單解釋


2 則留言: