Hello Xfire Web Services and OSGi
Nov
源起
除了直接接到 spring MVC 來展現網頁之外,對於外部的程式也需要連結,這裡測試 XFire 掛 Spring 的模式來做。
做法
xfire 有支援 spring ,他將一些預設定義放在 classpath:org/codehaus/xfire/spring/xfire.xml, 不過它是 spring 1.x 版本,即使設定 eclipse buddy 機制取得這個檔,也需要修改,索性直接將這個 檔放到需要的包中。
主要實做下面幾包 bundle 來用。
- haha.remix.xfire
- haha.hello.ws.userdao
- haha.hello.ws.client
haha.remix.xfire
這是個大轉包,沒有加程式,只是改 MANIFEST.MF 來協助。
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: haha.remix.xfire Bundle-SymbolicName: haha.remix.xfire Bundle-Version: 1.0.0 Bundle-Activator: haha.remix.xfire.Activator Bundle-Localization: plugin Import-Package: javax.servlet;version="2.4.0", javax.servlet.http;version="2.4.0", org.apache.commons.logging, org.osgi.framework;version="1.3.0" Bundle-ClassPath: lib/wstx-asl-3.1.0.jar, lib/stax-api-1.0.1.jar, lib/wsdl4j-1.5.2.jar, lib/xfire-all-1.2.2.jar, lib/xfire-jsr181-api-1.0-M1.jar, ., lib/activation-1.1.jar, lib/jdom-1.0.jar, lib/XmlSchema-1.1.jar Export-Package: com.ctc.wstx.stax, javax.jws, javax.xml.stream, org.apache.ws.commons.schema, org.codehaus.stax2, ........[skip] Require-Bundle: org.apache.commons.httpclient;resolution:=optional, org.apache.commons.codec;resolution:=optional, haha.remix.spring;resolution:=optional
這裡需注意 require bundle 都是選擇性的,那是因為有些特定需求要到客端包要求的時候,才會需要, 也不一定會有這些客端包,所以一直掛著讓他一定要這個附屬包也說不過去,所以用選擇性掛上。
haha.hello.ws.userdao
META-INF/spring/bean.xml
<osgi:reference id="httpService"
interface="org.osgi.service.http.HttpService"/>
<bean name="servletRegister"
class="haha.hello.ws.userdao.ServletRegister" init-method="init">
<property name="httpService" ref="httpService"/>
</bean>
haha.hello.ws.userdao.ServletRegister 負責啟動 WEB-INF/xx.xml 與註冊服務到 osgi:reference 的 org.osgi.service.http.HttpService
System.setProperty("javax.xml.stream.XMLInputFactory",
"com.ctc.wstx.stax.WstxInputFactory");
System.setProperty("javax.xml.stream.XMLOutputFactory",
"com.ctc.wstx.stax.WstxOutputFactory");
System.setProperty("javax.xml.stream.XMLEventFactory",
"com.ctc.wstx.stax.WstxEventFactory");
DispatcherServlet servlet = new DispatcherServlet();
servlet.setNamespace("hello-servlet");
OsgiWebApplicationContext owac = new OsgiWebApplicationContext();
servlet.setContextClass(owac.getClass());
httpService.registerServlet("/services", servlet, null ,null);
WEB-INF/hello-servlet.xml
<import resource="xfire.xml"/>
<osgi:reference id="userDao"
interface="haha.hello.jpa.userdao.UserDao"/>
<bean name="helloImpl" class="haha.hello.ws.userdao.HelloImpl">
<property name="userDao" ref="userDao"/>
</bean>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>/hello=wsController</value>
</property>
</bean>
<bean id="wsController"
class="org.codehaus.xfire.spring.remoting.XFireExporter">
<property name="serviceFactory">
<ref bean="xfire.annotationServiceFactory"/>
</property>
<property name="xfire">
<ref bean="xfire"/>
</property>
<property name="serviceBean">
<ref bean="helloImpl"/>
</property>
</bean>
WEB-INF/xfire.xml 取自 xfire classpath:org/codehaus/xfire/spring/xfire.xml 修改一下 singleton 設定而已。
這一包主要將 haha.hello.ws.userdao.Hello 放到 WebServic 去。
haha.hello.ws.userdao.Hello
package haha.hello.ws.userdao;
import javax.jws.WebMethod;
import javax.jws.WebResult;
import javax.jws.WebService;
@WebService(name = "HelloService", targetNamespace = "http://www.openuri.org/2004/04/HelloWorld")
public interface Hello {
@WebMethod
@WebResult
String getName();
}
haha.hello.ws.userdao.HelloImpl 會注入 osgi:reference 的 haha.hello.jpa.userdao.UserDao 來用。
package haha.hello.ws.userdao;
import haha.hello.jpa.userdao.UserDao;
import javax.jws.WebService;
@WebService(endpointInterface = "haha.hello.ws.userdao.Hello")
public class HelloImpl implements Hello {
private UserDao userDao;
private String name = "foo";
public String getName() {
return name + userDao.loadUsers().size();
}
....[SKIP].....
MANIFEST.MF
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: haha.hello.ws.userdao Bundle-SymbolicName: haha.hello.ws.userdao Bundle-Version: 1.0.0 Bundle-Activator: haha.hello.ws.userdao.Activator Bundle-Localization: plugin Import-Package: com.ctc.wstx.stax, javax.jws, javax.servlet;version="2.4.0", javax.servlet.http;version="2.4.0", org.osgi.framework;version="1.3.0", org.osgi.service.http;version="1.2.0", org.springframework.osgi.context.support Require-Bundle: haha.remix.spring, org.eclipse.equinox.http.jetty, haha.hello.jpa.userdao, haha.remix.xfire Export-Package: haha.hello.ws.userdao
啟動後可以直接開瀏覽器到 http://127.0.0.1/services/hello?wsdl 觀察 WSDL。
haha.hello.ws.client
陽春型客戶端,只是取用 getName 輸出看看而已,沒有用到 WSDL2JAVA 來轉成 java。
package haha.hello.ws.client;
import java.net.URL;
import org.codehaus.xfire.client.Client;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
Client client = new Client(
new URL("http://127.0.0.1/services/hello?wsdl"));
Object[] results = client.invoke("getName",null);
System.out.println(results[0]);
}
...[SKIP]....
MANIFEST.MF
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: haha.hello.ws.client Bundle-SymbolicName: haha.hello.ws.client Bundle-Version: 1.0.0 Bundle-Activator: haha.hello.ws.client.Activator Bundle-Localization: plugin Import-Package: org.apache.commons.logging, org.codehaus.xfire.client, org.osgi.framework;version="1.3.0" Bundle-ClassPath: . Require-Bundle: org.apache.commons.httpclient, org.apache.commons.codec, haha.hello.ws.userdao, haha.remix.xfire
觀察
- 越深入企業層面的應用,越發覺既有程式套件之間的相依性很高,之前用一個 classpath 可以找到, 現在轉 OSGi 變成要考慮匯出匯入問題,變的更為複雜,有時現有套件無法滿足,只好直接轉 remix 大包 來實現或是用 eclipse buddy 機制。
- 如果使用 import-package 方式,似乎無法保證某些服務會先啟動,於是將 ws.client 中設定 Require-Bundle 來讓某些服務先滿足。