基於 Equinox 的 OSGi Console 的研究和探索

自定製 OSGi Console 進行組建和服務生命週期管理
模塊化編程的好處已經很好地被理解了約 40 年,但在 OSGi 以前,開發人員不得不本身發明模塊化設計和系統。隨着軟件應用體系結構的不斷髮展,OSGi 已經成爲了一種很好的軟件體系模型。基於 OSGi 框架的應用程序由於其模塊化、動態性和麪向服務的特性而受到普遍歡迎,然而傳統的 OSGi 框架的控制檯在適用性和功能擴展性等諸多方面都有不少限制。爲此,本文探討一種自定製 OSGi 控制檯來進行服務和組件管理的方法。html


 

概述
一、OSGi 的現狀和發展趨勢
模塊化編程的好處已經很好地被理解了約 40 年,但在 OSGi 以前,開發人員不得不本身發明模塊化設計和系統。OSGi 標準爲軟件提供了模塊化、動態性、面向組件和服務的特性,所以愈來愈受到軟件開發商以及開源社區的關注。在網絡計算和雲計算體系高速發展的帶動下,單一節點的傳統應用程序正逐漸被分佈式多節點應用程序所取代。OSGi 標準正向着分佈式、跨虛擬機的方向發展,跨虛擬機的組件和服務訪問成爲了構建分佈式 OSGi 應用程序的重要方式。
二、OSGi 當前控制檯的限制和不足
OSGi 控制檯對於 OSGi 組件和服務生命週期管理以及模塊的調試和診斷的做用是相當重要的。組件和服務的管理者能夠經過控制檯向 OSGi 框架提交控制命令,由控制檯傳遞到命令解釋器,而後解釋器再去命令提供者那裏查找執行命令,並返回執行結果到控制檯,從而完成管理過程的交互。
然而,在 OSGi 應用中使用控制檯並非十分方便的,在有些狀況下,OSGi 實現的默認控制檯是沒法使用的,這一點在文中會有詳細分析。所以,咱們須要研究探索 OSGi 的控制檯的內部機制,尋找更加具備適用性和擴展性的控制檯解決方案。
三、本文在 OSGi 控制檯方面的研究和探索方向
OSGi 標準僅僅規定了命令定製和解釋的接口,命令列表的具體提供和解釋,以及控制檯的實現,並不在 OSGi 標準的規範範圍內。每一個遵循 OSGi 標準的實現均可以提供本身的命令和控制檯。這就爲咱們自定製 OSGi 控制檯提供了方便。
本文使用 Equinox 做爲 OSGi 標準的實現來研究和探索控制檯。Equinox 是 eclipse 採用的一種 OSGi 標準的實現框架。藉助 eclipse 的知名度,Equinox 也獲得了很大程度的推廣普及,目前不少公司的大型軟件產品都是構建在這一框架之上。
Equinox 框架提供了一個默認控制檯實現,這個 FrameworkConsole 以經過 System.in 接受用戶的命令請求,查詢並執行命令,經過 System.out 將結果反饋給用戶,完成交互過程。本文在分析在具體應用場景中現有控制檯實現的不足的基礎上,研究並挖掘了 Equinox 控制檯的擴展功能,並對本身實現控制檯從而知足更多自定製需求進行了探索,但願可以拋磚引玉,爲更多 OSGi 的使用者提供一個參考。java


 

 

OSGi 控制檯的研究和探索web


一、Equinox 控制檯實如今具體應用環境中的侷限性分析
在一般狀況下,Equinox 提供的默認控制檯能夠提供對組件和服務的生命週期控制和管理的功能。但在有些狀況下,若是不對 Equinox 系統作任何修改,是不能直接使用控制檯進行管理的。
這裏先對 Equinox 控制檯在具體應用環境中可能遇到的限制和不足作出分析,而後在接下來的部分,會對這些限制和不足提供嘗試性解決方案。
場景一,在不作任何修改時 Equinox 控制檯沒法使用的狀況。這種狀況在 OSGi 應用程序的平常開發中很常見的。舉例來講:若是使用 equinox servletbridge 經過外部的 servlet 容器來啓動 OSGi 應用,若不添加任何啓動參數則沒法使用 OSGi 控制檯;經過 windows 服務來啓動的 Websphere Application Server 一樣也沒法使用默認的控制檯;若是用戶但願從遠程訪問控制檯,對系統的組件和服務進行調試和控制,這種需求也是沒法知足的。
場景二,Equinox 控制檯的默認行爲沒法知足用戶需求的狀況。舉例來講:有些狀況下,須要對控制檯的添加訪問控制機制,那麼傳統的控制檯就沒法提供這樣的附加功能,由於他的行爲已經固定了。
場景三,須要控制檯服務徹底和應用程序解耦合的狀況。在某些狀況下,須要對原有系統軟件不作任何改變,而同時提供可嵌入式的控制檯。控制檯和應用程序徹底解耦,具備高度的可複用性。
下面就針對這三種場景,給出通過嘗試性解決方案。spring


二、對 Equinox 默認控制檯服務內部機制的研究
Equinox 實際上是一個相對完善的 OSGi 實現框架,在充分了解它的內部實現機制以後,場景一所提到的限制就能夠很容易的解決。
首先來分析一下 Equinox 框架啓動過程當中是如何加載控制檯的。
因爲 eclipse3.4(3.0 以上版本)是徹底構建在 Equinox 框架之上的,因此能夠從 eclipse3.4 的啓動過程入手。org.eclipse.osgi_3.4.3.R34x_v20081215-1030.jar 是 eclipse3.4 啓動的插件,也是 Equinox 框架的核心所在。這個包裏面既包含 Equinox 對於 OSGi 標準的實現,也包含了 eclipse 啓動所須要的類。org.eclipse.core.runtime.adaptor.EclipseStarter 這個類是負責啓動 eclipse 的,裏面有不少和配置相關的參數。其中:編程

public static final String PROP_CONSOLE = "osgi.console";
public static final String PROP_CONSOLE_CLASS = "osgi.consoleClass";

 

這兩個屬性是控制檯相關的,PROP_CONSOLE_CLASS用來指定控制檯的實現類,PROP_CONSOLE 用來指定控制檯所使用的 socket 通訊端口。原來 Equinox 默認實現的控制檯也是有經過 socket 進行遠程通訊的能力的,只不過要受到不少限制,這一點後面會詳細分析。而經過設定控制檯實現類,Equinox 爲開發者提供了一個自定製控制檯的切入點。
先從最簡單的狀況入手。Equinox 能夠經過添加系統屬性激活基於 socket 通訊的遠程控制檯。控制檯類是在 org.eclipse.osgi.framework.launcher 中被加載的。在 org.eclipse.core.runtime.adaptor.EclipseStart 類中也有相似的 startConsole() 方法,一樣用來加載控制檯,如 清單 1 所示。windows

清單 1. 加載控制檯緩存

 1 private void doConsole(OSGi osgi, String[] consoleArgs) { 
 2  Constructor consoleConstructor;   
 3     Object osgiconsole; 
 4     Class[] parameterTypes; 
 5     Object[] parameters; 
 6     try { 
 7         Class osgiconsoleClass = Class.forName(osgiConsoleClazz); 
 8         if (consolePort.length() == 0) { 
 9             parameterTypes = new Class[] {OSGi.class, String[].class}; 
10             parameters = new Object[] {osgi, consoleArgs}; 
11         } else { 
12             parameterTypes = new Class[] {OSGi.class, int.class, String[].class}; 
13             parameters = new Object[] {osgi, new Integer(consolePort), consoleArgs}; 
14         } 
15         consoleConstructor = osgiconsoleClass.getConstructor(parameterTypes); 
16         osgiconsole = consoleConstructor.newInstance(parameters); 
17         Thread t = new Thread(((Runnable) osgiconsole), OSGI_CONSOLE_COMPONENT_NAME); 
18         t.start(); 
19     } catch (NumberFormatException nfe) { 
20         System.err.println(NLS.bind(Msg.LAUNCHER_INVALID_PORT, consolePort)); 
21     } catch (Exception ex) { 
22         informAboutMissingComponent(OSGI_CONSOLE_COMPONENT_NAME, OSGI_CONSOLE_COMPONENT); 
23     } 
24  }

 

在 launcher 類的 doConsole()方法中,先檢查啓動參數中是否有指定了控制檯使用的端口號,構造相應的參數列表,經過反射方式調用控制檯類的構造方法來建立控制檯對象。若是指定了特定端口號,則構造控制檯對象時使用的是 socket 獲取的輸入輸出流;若是沒有指定端口號,則使用默認的標準輸入輸出流來構造控制檯。安全

經過指定控制檯使用 socket 方式創建通訊,就能夠徹底解決場景一提到的問題。指定控制檯端口號的方法並不複雜,針對不一樣的狀況,能夠有下面三種種方法:服務器

  • 在開發環境(以 eclipse 爲例)中,須要在運行 OSGi 應用的配置中添加啓動參數。這種狀況有兩種方法:其一,eclipse 中選擇 Run -> Run Configurations,選擇你將要運行的應用點擊 Arguments 選項卡,在上半部分的 Program arguments 中輸入 -console 8090,這樣就能夠打開使用 8090 socket 通訊的控制檯,若是不填寫端口號,則會默認爲使用標準輸入輸出的控制檯。其二,也能夠經過添加 VM arguments 來設定端口,在 VM arguments 中添加 -Dosgi.console=8090,注意若是添加這一項,須要把上面 Program arguments 中的 -console 參數刪去。
  • 若是在外部經過自定義 bat 文件來啓動 OSGi 框架,則 bat 文件中能夠這樣寫:java -Dorg.osgi.service.http.port=8080 -jar equinox.jar -console 8090 這與上面提到的狀況相似。
  • 若是使用 equinox servletbridge 經過外部的 servlet 容器來啓動 OSGi 應用,因爲這種方式下,OSGi 應用以普通的 web 應用程序的形式存在。啓動控制檯須要在 web.xml 文件中的 org.eclipse.equinox.servletbridge.BridgeServlet添加啓動參數,如 清單 2 所示。

清單 2. 添加啓動參數網絡

 <init-param> 
    <param-name>commandline</param-name> 
    <param-value>-console 8090</param-value> 
</init-param>

這種方式不但可讓 OSGi 應用的部署本地暢通無阻的訪問 OSGi 控制檯,還可讓遠端計算機經過 socket 客戶端訪問 OSGi 控制檯。好比使用 windows 系統的可使用 telnet 命令,例如:telnet 192.168.1.1 8090。
經過上面的嘗試,咱們已經能夠經過 socket 創建與控制檯的通訊,使得本地和遠程訪問控制檯執行 OSGi 命令來管理組件和服務的生命週期變得可行,解決了場景一提出的問題。可是這種方式下,控制檯的訪問者使用的仍是 equinox 提供的默認控制檯的功能,而沒法實現必要的自定製功能。所以在實際應用中的做用仍是有限的。這就如同場景二提到的狀況。

 

三、自定義 OSGi 標準的控制檯實現類
若是但願本身實現自定製功能的控制檯,解決場景二遇到的問題,須要本身實現一個控制檯類,而且添加另一個啓動參數。
equinox 提供一個默認的控制檯實現,org.eclipse.osgi.framework.internal.core.FrameworkConsole,能夠參照這個類來實現本身的控制檯。好比你的控制檯一般也須要實現 socket 通訊,就能夠借用 getSocketStream()方法,但若是你想使用的不是 socket 流,那麼也可使其餘流或者其餘通訊方式。創建通訊以後,控制檯的執行過程都在 console() 這個方法中,執行過程也能夠自定製。例如 清單 3 就是自定製的控制檯過程,在用戶接入以後要求用戶輸入用戶名和密碼進行認證。經過認證的用戶將會看到 OSGi 提示符,並能夠執行 OSGi 的相關操做。
清單 3. 自定製的控制檯過程

      protected void console() {    
 // wait to receive commands from console and handle them 
    BufferedReader br = in; 
    //cache the console prompt String 
    String consolePrompt = "\r\n" + ConsoleMsg.CONSOLE_PROMPT; //$NON-NLS-1$ 
    for(int i=0;i<3;i++) 
    { 
        try { 
            out.print("Input your username:"); 
            out.flush(); 
            String user=br.readLine(); 
            out.print("Input your password:"); 
            out.flush(); 
            String pass=br.readLine(); 
            if(!user.contentEquals("admin") || !pass.contentEquals("admin")) 
            { 
                out.print("Your username or password is invalid.Please input again"); 
                out.flush(); 
            } 
            else 
            { 
                break; 
            } 
        } catch (IOException e) { 
        // TODO Auto-generated catch block 
        e.printStackTrace(); 
        } 
 } 
    while (!disconnected()) { 
        out.print(consolePrompt); 
        out.flush(); 

        String cmdline = null; 
        try { 
            if (blockOnready && !useSocketStream) { 
    // bug 40066: avoid waiting on input stream - apparently generates contention with 
    //other native calls 
                try { 
                    while (!br.ready()) 
                    Thread.sleep(300); 
                    cmdline = br.readLine(); 
                } catch (InterruptedException e) { 
        // do nothing; probably got disconnected 
                } 
            } else 
                cmdline = br.readLine(); 
        } catch (IOException ioe) { 
            if (!shutdown) 
                ioe.printStackTrace(out); 
        } 
        if (cmdline == null) { 
            if (!useSocketStream) 
        // better shutdown; the standard console has failed us.  Don't want to get in an 
        //endless loop (bug 212313) (bug 226053) 
                shutdown(); 
            break; 
        } 
        if (!shutdown) 
            docommand(cmdline); 
    } 
 }

控制檯類編寫好以後,如何告訴 equinox 加載這個類做爲控制檯的實現呢?能夠經過設置 osgi.consoleClass 這個參數來指定控制檯類。這個參數的內容就是你自定義的控制檯類的路徑。這個啓動參數的添加方法和前面提到過的 osgi.console 的三種設定方式基本相同,除了不能在 web.xml 文件的 servlet 啓動參數中添加外,其餘的兩種方式均可以用來添加這個參數。
例如: osgi.consoleClass=org.myConsole.myFrameworkConsole
添加這個參數以後,還有最後一個問題。控制檯的實現類是須要和 OSGi 的核心實現 bundle 即 org.eclipse.osgi_3.4.0.v20080605-1900.jar啓動時一塊兒加載到系統中的,當核心 bundle 啓動的時候,其餘全部 bundle 都沒有加載,只有等核心 bundle 啓動以後纔會去加載其餘 bundle。這樣一來,若是本身的控制檯實現類打包在一個普通的 bundle 中,那核心 bundle 啓動的時候是加載不到這個類的。解決方法就是建立一個 fragment 的 bundle,此 bundle 的 Fragment-Host設置爲 org.eclipse.osgi;bundle-version="3.4.2",即做爲核心 bundle 的附屬,這樣此 bundle 將和核心 bundle 一塊兒啓動,這樣自定義的類就能夠被核心 bundle 加載到了。

注意:須要把包含控制檯實現類的包導出以便其餘 bundle 來引用。
fragment bundle 的 manifest.mf 文件中重要的幾個屬性:

Fragment-Host: org.eclipse.osgi;bundle-version="3.4.2"
Export-Package: myconsole

注意,自定義的控制檯實現類所在的包須要添加到 Export-Package中,才能在啓動過程當中被加載。
對於 fragment bundle 的開發就再也不給出詳細步驟了,能夠參考本文提供的範例程序,或者聯繫做者進行討論。


四、自定義 OSGi 標準的命令執行器
至此咱們已經討論了場景一和場景二中提到的限制的解決方案。但這些方式使用的控制檯也存在很明顯的不利。首先,遠程管理和控制中相當重要的一個環節是安全方面的訪問控制,經過 socket 通訊來進行訪問控制將引入附加的安全機制,這種安全機制和系統自己固有的安全機制沒法聯繫,致使系統冗餘;其次,不管使用上面描述的任何一種方式,都須要添加 OSGi 啓動參數,須要對原有配置作修改,這對於在既有項目中嵌入控制檯服務來講,無疑會形成不便,就如同場景三所描述的狀況。
咱們能夠換一種角度來看控制檯。控制檯的本質,就是接受用戶的輸入,解析輸入,執行命令,而後返回操做結果,完成於用戶的一次交互。所以咱們須要的僅僅是一個通訊方式和一個命令解析器。
爲了不使用 socket 通訊帶來的安全問題以及添加啓動參數的不便,須要採用其餘的遠程通訊方式。而 http 協議時目前 web 應用程序中最經常使用的通訊協議,基於通訊 http 協議的 servlet 技術也比較成熟,所以咱們這裏使用基於 HTTP 協議的 servlet 請求方式來傳遞用戶請求。用戶請求到達以後,servlet 要作的就是解釋並執行請求。OSGi 標準提供了兩個接口,org.eclipse.osgi.framework.console.CommandInterpreterorg.eclipse.osgi.framework.console.CommandProvider。實現 CommandProvider 接口的類負責提供 OSGi 可執行的命令,實現這個接口必須實現的方法是 public String getHelp(),此方法用來提供命令幫助,此外還能夠自定義一系列如下劃線「_」開頭的方法,用來提供具體的命令和命令的具體操做,下劃線後面即爲命令名稱。命令解釋器 CommandInterpreter 就是把用戶輸入和這裏的名稱匹配從而找到命令相對應的方法並執行。
equinox 默認提供了這兩個接口的實現類,分別是 org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider以及 org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter。 爲了實現自定製的功能,用戶也能夠本身編寫這兩個實現類。 FrameworkCommandProvider 提供了大部分經常使用的操做,因此通常狀況下能夠直接使用這個類,若是須要添加自定義操做,能夠經過繼承這個類來擴展;但因爲 FrameworkCommandInterpreter 中命令執行結果輸出的流已經綁定爲 FrameworkConsole 中的輸出流了,因此要本身實現一個 CommandInterpreter。
首先,編寫用於和用戶交互的 ConsoleServlet,負責接收用戶輸入和將響應結果返回給用戶。在這個應用場景中,用戶經過 web 應用程序經行組件和服務的生命週期管理,所提交的一系列命令都會被提交到這個 servlet 中進行處理,如清單 4 所示。
清單 4. 提交命令

 1  public class ConsoleServlet extends HttpServlet { 
 2      BundleContext bc; 
 3      Bundle[] b; 
 4 
 5      public ConsoleServlet() 
 6      {        
 7          bc=InternalPlatform.getDefault().getBundleContext(); 
 8      } 
 9      public void doGet(HttpServletRequest request, HttpServletResponse response)   
10         throws ServletException, IOException 
11      {   
12          this.doPost(request, response);   
13      }   
14      public void doPost(HttpServletRequest request, HttpServletResponse response) 
15      throws ServletException, IOException   
16      { 
17           BufferedReader br=request.getReader(); 
18           String command=br.readLine(); 
19           System.out.println(command); 
20           response.setContentType("text/html");   
21           PrintWriter out = response.getWriter();   
22           HttpServletConsole.setOutStream(out);// 設定 HttpServletConsole 使用的輸出流
23           HttpServletConsole.docmd(command); 
24           for(EventObject eo:Activator.eventInfoMap.keySet()) 
25           { 
26              out.println(Activator.eventInfoMap.get(eo)+"\n"); 
27              System.out.println(Activator.eventInfoMap.get(eo)+"\n"); 
28           } 
29           Activator.eventInfoMap.clear(); 
30      } 
31  }

而後,編寫控制檯類 HttpServletConsole。這個類中最關鍵的方法是如何將用戶的輸入交給命令解釋器去解釋,這個方法就是 docmd(),如清單 5 所示。
清單 5. docmd() 方法

 1       /** The OSGi Command Provider */ 
 2  protected final CommandProvider osgicp = new FrameworkCommandProvider(osgi).intialize(); 
 3  /** A tracker containing the service object of all registered command providers */ 
 4  protected static final ServiceTracker cptracker 
 5            = new ServiceTracker(context, CommandProvider.class.getName(), null); 
 6  /** 
 7  *  Process the args on the command line. 
 8  *  This method invokes a CommandInterpreter to do the actual work. 
 9  * 
10  *  @param cmdline a string containing the command line arguments 
11  */ 
12  public static void docmd(String cmdline) { 
13      cptracker.open();    
14      if (cmdline != null && cmdline.length() > 0) { 
15           CommandInterpreter intcp 
16                  = new ConsoleCommandInterpreter(cmdline, myGetServices(), out); 
17           String command = intcp.nextArgument(); 
18          if (command != null) { 
19               intcp.execute(command); 
20           } 
21      } 
22  } 
23 
24  /** 
25  * Return an array of service objects for all services 
26  * being tracked by this ServiceTracker object. 
27  * 
28  * The array is sorted primarily by descending Service Ranking and 
29  * secondarily by ascending Service ID. 
30  * 
31  * @return Array of service objects; if no service 
32  * are being tracked then an empty array is returned 
33  */ 
34  public static Object[] myGetServices() { 
35      ServiceReference[] serviceRefs = cptracker.getServiceReferences(); 
36      if (serviceRefs == null) 
37          return new Object[0]; 
38      Util.dsort(serviceRefs, 0, serviceRefs.length); 
39 
40      Object[] serviceObjects = new Object[serviceRefs.length]; 
41      for (int i = 0; i < serviceRefs.length; i++) 
42          serviceObjects[i] = context.getService(serviceRefs[i]); 
43      return serviceObjects; 
44  } 
45 
46  // 
47  public ConsoleCommandInterpreter(String cmdline,
48                                  Object[] commandProviders,PrintWriter out) 
49  { 
50      tok = new StringTokenizer(cmdline); 
51      this.commandProviders = commandProviders; 
52      this.out = out; 
53  }

ConsoleCommandInterpreter 是自定義的 CommandInterpreter 實現,在這個類的構造方法中,須要將輸出流做爲構造方法的參數傳入,這樣命令的執行結果纔可以返回到用戶那裏。同時,經過 osgi 的 API 獲取到了提供命令的服務的類,這些類都是 CommandProvider 的實現,均可以提供執行特定操做的命令,這些類的對象也做爲構造方法的參數傳入命令解釋器。自定義命令解釋器的其餘部分與 ConsoleCommandInterpreter 相似,能夠參考本文的範例代碼。 整個過程的流程能夠歸納成:

  • 用戶提交命令請求
  • ConsoleServlet 接受請求,解析命令文本
  • 把命令文本和提供命令的類交給 ConsoleCommandInterpreter
  • 執行命令而且把結果返回指定輸出流

至此,全部由用戶發起的請求均可以獲得響應了。可是因爲 http 協議的「請求 - 應答」模型的限制,通常來講服務器端沒法主動的吧信息「推送」到客戶端呈現給用戶。爲了保證服務器端的狀態能夠儘可能及時的反映到客戶端上,一般採用 http 請求「輪詢」的方法,即每隔固定的時間段就發送一次請求,更新一次狀態,形成客戶端狀態和服務器端實時同步的用戶體驗。那麼若是服務器端系統內部發生了某些事件,這些信息又如何讓用戶獲知呢?咱們能夠經過 OSGi 的事件監聽機制來獲取系統內部事件信息,再經過輪詢方式讓這些信息儘可能及時的呈現給用戶。
OSGi 提供了三種基本 listener 接口,分別是 BundleListener,,FrameworkListenerServiceListener。當這幾種 listener 監聽的事件發生時,它們會執行相應的方法來處理事件信息。咱們能夠本身編寫一個類實現這三個接口,如 清單 6 所示:
清單 6. 實現接口

  1 public class Activator implements BundleActivator, 
  2          BundleListener, FrameworkListener, ServiceListener 
  3  { 
  4  public static BundleContext context; 
  5  static Vector vec; 
  6  public static boolean isBundleChanged=true; 
  7  public static Hashtable<EventObject,String> eventInfoMap 
  8                            = new Hashtable<EventObject,String>(); 
  9 
 10  public void start(BundleContext context) throws Exception 
 11  { 
 12      Activator.context=context; 
 13      Bundle[] bundles=context.getBundles(); 
 14      context.addBundleListener(this); 
 15      context.addFrameworkListener(this); 
 16      context.addServiceListener(this); 
 17 
 18      public void bundleChanged(BundleEvent event) 
 19      { 
 20          isBundleChanged=true; 
 21          int causedBy=event.getType(); 
 22          String CausedBy=""; 
 23          switch(causedBy) 
 24          { 
 25               case 1: 
 26                   CausedBy="has been installed."; 
 27                   break; 
 28               case 2: 
 29                   CausedBy="has been started."; 
 30                   break; 
 31               case 4: 
 32                   CausedBy="has been stopped."; 
 33                   break; 
 34               case 8: 
 35                   CausedBy="has been updated."; 
 36                   break; 
 37               case 16: 
 38                   CausedBy="has been uninstalled."; 
 39                   break; 
 40               case 32: 
 41                   CausedBy="has been resolved."; 
 42                   break; 
 43               case 64: 
 44                   CausedBy="has been unresolved."; 
 45                   break; 
 46               case 128: 
 47                   CausedBy="is about to be activated."; 
 48                   break; 
 49               case 256: 
 50                   CausedBy="is about to deactivated."; 
 51                   break; 
 52               case 512: 
 53                   CausedBy="will be lazily activated."; 
 54                   break; 
 55          } 
 56          Bundle eventBundle=event.getBundle(); 
 57          String bundleEventInfo=String.valueOf(eventBundle.getBundleId())+": "+ 
 58          eventBundle.getSymbolicName()+" "+CausedBy; 
 59          eventInfoMap.put(event , bundleEventInfo); 
 60      } 
 61 
 62      public void frameworkEvent(FrameworkEvent event) 
 63      { 
 64          int causedBy=event.getType(); 
 65           String CausedBy=""; 
 66           switch(causedBy) 
 67           { 
 68               case 1: 
 69                   CausedBy="The Framework has started. "; 
 70                   break; 
 71               case 2: 
 72                   CausedBy="An error has occurred. "; 
 73                   break; 
 74               case 4: 
 75                   CausedBy="A PackageAdmin.refreshPackage operation has completed. "; 
 76                   break; 
 77               case 8: 
 78                   CausedBy="A StartLevel.setStartLevel operation has completed. "; 
 79                   break; 
 80               case 16: 
 81                   CausedBy="A warning has occurred."; 
 82                   break; 
 83               case 32: 
 84                   CausedBy="An informational event has occurred. "; 
 85                   break; 
 86           } 
 87           Bundle eventBundle=event.getBundle(); 
 88           String bundleEventInfo=CausedBy+" Caused by bundle "+ 
 89      String.valueOf(eventBundle.getBundleId())+": "+ 
 90      eventBundle.getSymbolicName(); 
 91      eventInfoMap.put(event, event.toString()); 
 92      } 
 93      public void serviceChanged(ServiceEvent event) 
 94      { 
 95          int causedBy=event.getType(); 
 96          String CausedBy=""; 
 97          switch(causedBy) 
 98          { 
 99              case 1: 
100                   CausedBy=" has been registered. "; 
101                   break; 
102               case 2: 
103                   CausedBy="'s properties have been modified. "; 
104                   break; 
105               case 4: 
106                   CausedBy=" is in the process of being unregistered. "; 
107                   break; 
108          } 
109           String bundleEventInfo=event.toString()+CausedBy; 
110           eventInfoMap.put(event, bundleEventInfo); 
111      } 
112  /* 
113  * (non-Javadoc) 
114  * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) 
115  */ 
116      public void stop(BundleContext context) throws Exception 
117      { 
118          System.out .print(eventInfoMap); 
119      } 
120  }

這樣,全部的時間的信息都會被緩存在 eventInfoMap 這個 map 中,客戶端能夠設定間隔固定的時間段發起一次請求,獲取此 map 中的信息呈現給客戶。
至此,控制檯的基本功能就都實現了。
這種方式實現的控制檯有不少優良特性:首先,基於 servlet 的通訊機制,有很成熟的安全訪問控制機制,好比 spring security 等;其次,servlet 基於 http 協議,能夠方便的集成到 web 應用程序中;第三,此控制檯自己也是插件,能夠不添加任何啓動參數,無縫的集成到系統中提供遠程控制檯訪問,有效的管理 OSGi 系統中的組件和服務。這些特性能夠很好的解決場景三中提到的限制和不足。
然而,這種方式的控制檯也存在明顯的不足。受到 HTTP 通訊協議請求應答模型的限制,這種插件式控制檯只是一個命令執行器,雖然可以經過各類事件來反應系統信息,但沒法把系統的日誌信息完整的呈現給用戶。這也是這種方式的最大不足,是和原生控制檯的最大差距所在


總結
探索自定製 OSGi 控制檯的意義
經過上面的探索,咱們針對三種應用場景由淺入深的提出了三種解決方案。三種實現方式中,與控制檯原有系統的耦合度是依次遞減的:第一種方式直接經過添加啓動參數激活系統默認控制檯的 socket 通訊功能;第二種方式是經過啓動參數添加自定義的控制檯實現類;第三種則是乾脆拋開系統控制檯規則,本身接收、解釋和執行命令,並反饋執行結果給用戶。
這三種方式中各自有針對的應用場景,都是爲了知足某一應用場景的需求,絕非原生控制檯的代替品,也不是 OSGi 控制檯的最佳實踐。所以在選擇這三種控制檯的實現方式以前須要認真分析系統的應用場景,選擇出最合適的解決方案


以上內容引自http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-equnxc/

相關文章
相關標籤/搜索