A Chain of Spring-OSGi Services

0

源起

使用 TargetSourceLifecycleListener 可以讓客端在服務發生變動時加以因應,這是一對一的場景, 如果是一連串的相依服務串在一起的場景,要如何因應某個 bundle 的變化。

參考

  1. Hello TargetSourceLifeCycleListener

場景

下面為練習場景,假設 http 啟動的 port 由 conf bundle 控制。

  1. Conf Bundle(ConfService)
  2. Jetty Bundle(ConfListener/JettyService)
  3. WebApp Bundle(JettyListener)

修改 Conf Bundle 後先停止。

Stop ConfBundle

  1. [Jetty Bundle] ConfListener.unbind
  2. [Conf Bundle] ConfService.destroy

然後啟動,這時候會在 bind 中如果發現 port 不同會重新啟動

Start ConfBundle

  1. [Conf Bundle] ConfService.init
  2. [Jetty Bundle] ConfListener.bind (restart http service)

這時候 WebAppBundle 一無所知,因為 JettyBundle 沒有被 stop/start, 而是 ConfBundle stop/start。

這時要這些 WebAppBundle 重新註冊,需要 JettyBundle 的 stop/start, 來觸動 JettyListener。

Stop JettyBundle

  1. [WebApp Bundle] JettyListener.unbind
  2. [Jetty Bundle] JettyService.destroy (stop http service)
  3. [Jetty Bundle] ConfListener.unbind

Start JettyBundle

  1. [Jetty Bundle] JettyService.init (start http service)
  2. [Jetty Bundle] ConfListener.bind (restart service)
  3. [WebApp Bundle] JettyListener.bind

這時候因為整個 JettyBundle 的重新啟用,導致屬於這個 bundle 的 ApplicationContext 再來一次,這讓 ConfListener.bind 又被呼叫一次。

bind 被呼叫兩次的原因。

  1. service 端重新啟動觸發 bind method。
  2. bundle 本端重新啟動,為了觸發下游 bundle 的 bind method。

setter method to inject ConfService

為了避免呼叫兩次,一開始將 bind 的 restart http service 去掉,只讓 init 啟動,不過發現 bind 的方式必須在 bean 實體化之後,也就是說在 init 之後才會呼叫 bind,如此一來要設定讓 http service 使用修改後的 port 會有 問題。

另外可以在 init 之前完成的是 setter 注入 ConfService 的方式,這樣一來, ConfBundle 的重新啟動,並不會對 JettyService 有任何作用,改 port 的行為 必須直到 JettyBundle 重新啟動才會生效。

觀察

  1. ConfBundle 一改,下面的相關 Bundle 也需要跟著重新啟動,來觸發下游的 bind method, 以修改 port 為例,需要 ConfBundle,JettyBundl 兩個 bundle 先後依順序重新啟動。
  2. ConfBundle 修改導致所有相關 Bundle 都需各別再啟動的方式也許有其他更好做法,有待繼續觀察。
  3. bind/unbind 需要由可由輸出服務的 bundle start/stop 觸發,也會由自己的 bundle start/stop 觸發。
  4. stop bundle 會先呼叫 bean unbind method 後呼叫 bean destroy method。
  5. start bundle 會先呼叫 bena init method 後呼叫 bean bind method。

Hello Java DB and JDK6

0

源起

JDK 6 內建一個小型內嵌資料庫,改自 Apache Derby 專案,有機會變成 JavaSE 內建的資料庫,值得看看。

參考連結

  1. 之前的 Dreby bundle 包裝紀錄。
  1. 必看的 Java DB 官方網頁。

classpath 仍需要設定

一開始聽到的會以為現在只要下載 JDK 就可以直接寫個 Class.forName( “org.apache.derby.jdbc.EmbeddedDriver” ) 來用, 不過目前還不行,由於還不是 JavaSE 內建 API,屬於延伸的東西, 還是需要去設定 classpath 才能運作,只是換個名稱並包在 JDK 下載之中而已。

export CLASSPATH=.:${DERBY_INSTALL}/lib/derby.jar

這樣做的好處是只要指定 JDK6 的目錄為 JAVA_HOME,就代表可以找到 相對位置的 JAVA_HOME/db/lib/derby.jar。

export CLASSPATH=.:${JAVA_HOME}/db/lib/derby.jar

很類似之前 Tomcat 需要 JDK 編譯的時代,那時需要指定 JAVA_HOME/lib/tools.jar 才能運作,後來 Tomcat 改用 eclipse jdt 編譯器,就不再需要 JDK 部署,只要 JRE 就能跑。

觀察

  1. 功能和 Apache Derby 無差異,只是差在名稱變成 Java DB,並可以由 sun 提供 支援服務,目前手冊等資料還是連到 Apache Derby 取得。
  2. 就使用者簡易性來說,Java DB 需要綁在 java -classpath 的做法比較麻煩, OSGi 動態載入 Apache Derby bundle 會比較方便。

Hello ant1.7 and junit4

0

源起

現在慢慢習慣使用 Junit4 寫測試,發現目前 eclipse 3.2 支援的 ant 1.6.5 的 junit task 不支援 junit4,所以想試一下新的 ant1.7。

junit 4

除了 @Test 之外,目前測試 spring 的 bean 往往都是一次取得 application context 來測,這時候 @BeforeClass 也是很好用。

ant 1.7

目前是 RC 版,先下載 apache-ant-1.7.0RC1-bin.zip,解開為目錄。 然後開 eclipse 的 ant 設定,將其中的 Ant Home 換成 1.7 的目錄即可。

<target name="test" depends="compile">
  <junit printsummary="yes">
    <classpath refid="supportlib.classpath"/>
    <formatter type="xml" />
    <batchtest fork="yes" todir="${reports.tests}">
      <fileset dir="${src.test}">
        <include name="**/*Test*.java" />
      </fileset>
    </batchtest>
  </junit>
</target>

<target name="report" depends="test">
  <junitreport todir="${reports}"> 
    <fileset dir="${reports.tests}"> 
      <include name="TEST-*.xml"/> 
    </fileset> 
    <report 
        format="frames" todir="${reports}/html"/> 
  </junitreport> 
</target>

觀察

  1. 雖然 junit 3 的支援比較完整,不過新東西還是要試一下。

Hello JAXB2 and JavaSE6

0

源起

幾個專案用過 XMLBeans,還不錯用,之前評估過 JAXB 1.0 版本,有兩個問題, 一是 XML Schema 支援有點問題,一是 JAXB 當時有商業授權等議題。

這些專案是採 XML Schema 為主的做法,也就是需要先由客戶端決定商業領域的語法, 交付寫好的 schema,那時 JAXB 1 沒辦法很方便的foo.xsd 轉 foo.java, 而 foo.xsd 一直改到後期會越來越複雜,一堆的 complex type,所以那時找到能 符合專案 schema 與授權的方案是 BEA 釋出的 Apache XMLBeans。

不過時過境遷,現在 JAXB 2 授權也改了,支援 XML schema 應該也 比較好,同時 JavaSE6 已經內建支援,於是來玩看看。

http://download.java.net/jdk6/docs/technotes/guides/xml/jaxb/index.html

scheam-first or code-first

如果先寫 schema 的話,需要使用 xjc,就 eclipse 來說,使用 ant 比較方便。 不過 com.sun.tools.xjc.XJCTask 並不存在 JRE 中,需要下載 jaxb-xjc.jar 來用。

https://jaxb.dev.java.net/nonav/2.0.2/docs/xjcTask.html

https://jaxb.dev.java.net/

用 xjc 產生 java 碼後,就可以開始用。

ProjectType pj = new ProjectType();
...[SKIP]
ObjectFactory objFactory = new ObjectFactory();
JAXBElement<ProjectType> je = objFactory.createProject(pj);

JAXBContext jc = JAXBContext.newInstance(ProjectType.class);
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

簡單 marshaller.marshal(jaxbElement, System.out) 的結果。

<ns2:project xmlns:ns2="http://config.service.haha/ns/project">
    <profile>
        <name>FOO PROJECT</name>
    </profile>
</ns2:project>

更換 ns2

指定預設或是更動命名空間的 prefix 並不在 JAXB2 的規格 (JSR-222 spec) 中, 所以 JAVA6 平台無法提供這類的 marshaller 機制,要用 JAXB2 RI 才可以, 因為有的 schema 的 prefix 名稱是定死的,不支援這個會比較麻煩。

//  RI 才有支援,需要 jaxb-impl.jar,JAVA6  不包含
marshller.setProperty("com.sun.xml.bind.namespacePrefixMapper",
  new NamespacePrefixMapper() {
    @Override
    public String getPreferredPrefix(String nsuri,
               String suggestion, boolean requirePrefix) {
        // 無法調成 default 只能改掉。用 "" 無效 ?
        if ("http://config.service.haha/ns/project".equals(nsuri))
            return "pj";
        return suggestion;
      }
    });

結果輸出

<pj:project xmlns:pj="http://config.service.haha/ns/project">
    <profile>
        <name>FOO PROJECT</name>
    </profile>
</pj:project>

取部份 XML 輸出

之前用 XMLBeans 都用複製 copy 模式建立另一個文件來修改取出需要的部份, JAXB 似乎沒有這類方式,需要建立兩個物件複製。

haha.remix.jaxb

基本上就是轉包 jaxb-api.jar, jaxb-impl.jar, jsr173_1.0_api.jar 供 OSGi 平台使用, JAVA6 雖然有支援 JAXB2,但是需要 com.sun.xml.bind.namespacePrefixMapper 的情況下, 還是要使用附加的 jaxb-impl.jar。

另外使用 haha.remix.jaxb 讓 JAVA5 也一樣可以用。

JAVA6

根據 Java 6 Leads Out of the Box Server Performance 的說法,這個快上很多的出場預設性能蠻吸引人的。

觀察

  1. 因為 JAXB 已經是個標準,關於 JAXB 的用法還要再深入觀察。
  2. 個人目前用起來 XMLBeans 比較直覺,也有可能是用習慣的關係。
  3. 簡單的綁定, JAVA 6 是個不錯的現成平台,如果要更細部的支援,無可避免還是要加 jar 到平台上,這時 JAXB2 RI 或是 XMLBeans 就看哪個用的順手,功能也都可以提供為主。

Hello TargetSourceLifecycleListener

0

源起

啟動 jetty bundle 後,如果想要修改 jetty 由 spring 控制的 init() 啟動 port 為 8080, 這時在 osgi console 下 update 指令,其他 bundle 註冊的 webapp 是否會改由新的 8080 進入,需要哪些設定,想要觀察一下。

jetty 發佈服務設定

服務端的設定如下 :

<bean id="httpServiceImpl" 
  class="haha.service.jetty.HttpServiceImpl" 
  init-method="init" 
  destroy-method="destroy" 
  p:port="80" 
  p:debugEnable="true"/>
<osgi:service ref="httpServiceImpl" 
  interface="haha.service.publishing.WebPublishingService"/>

使用發布服務並將資料交給發布者

之前的做法如下 :

<osgi:reference id="webPublishingService" 
  interface="haha.service.publishing.WebPublishingService" />
<bean id="publisher" class="haha.service.jetty.PublisherImpl" 
  init-method="init" destroy-method="destroy" p:webappDir="webapp" 
  p:contextPath="/foo" autowire="byType" />

結果 update jetty 後,該發佈服務並沒有跟著改為新的 port, 因為之前的做法綁定在 init(),並無法反映服務的中斷與重新啟用, ,所以需要類似 OSGi DS 規範的服務綁定功能。

TargetSourceLifecycleListener

之前的做法是利用 setter 將服務注入,並在 init() 發布,現在改用 spring-osgi 提供的 TargetSourceLifecycleListener 介面加以實做,在 bind/unbind 中寫下使用 的服務內容,用 osgi:listener 來注入這個服務。

<bean id="publisher" class="haha.service.jetty.PublisherImpl" 
  p:webappDir="webapp" p:contextPath="/foo" />
<osgi:reference id="webPublishingService" 
  interface="haha.service.publishing.WebPublishingService">
  <osgi:listener ref="publisher" />
</osgi:reference>

因為由 bind 注入因素,可以省掉 autowire=”byType” 的注入方式。

觀察

  1. osgi:listener 可以設定 bind/unbind 的 method,幫助現有 API 轉用,不一定要用 TargetSourceLifecycleListener。
  2. 這裡用內含新 p:port 的 bundle 來並下載更新來達到目的,如果要由客戶端 改自己改要如何做 ?

Older posts: 1 2 3