為啥要玩
Apache CXF呢?簡單的說就是要從它跟
Jersey之間挑一個來用,目標是用它作為Web Application的後端跟GWT 開發的前端(透過
Resty GWT)溝通使用。
至於為什麼不直接用
GWT RPC呢?因為公司的Architects覺得GWT有點朝不保夕的感覺,他們希望如果哪天GWT死掉了,前端可以有機會一點一點的換成Javascript native implementation...我是覺得這有點異想天開啦,但是看他們好像有人有基於這種架構開發過還很舒服的樣子,我就不多說了,玩下去就知道。
Jersey 公司已經一堆人會了,也有人用過,於是我就自動請纓來Survey CXF了...雖然我覺得這玩意八成不會被接受(開玩笑,一堆人會就代表開發成本低、有默契,這玩意很多時候比架構重要)。
CXF的基本介紹
CXF 是 Apache Foundation 底下開發SOAP、JAX-WS、JAX-RS應用的一個較為現代化(?)的應用框架。
它支援了幾個重要的Feature:
JAX-WS Support
基於
JSR 224的實現,不多廢話,這玩意很老,overhead 很重,但跟異質系統做資料交換且要確保資料型態的正確性還是得靠這個。
Spring Integration
CXF的核心在2.4版以前跟Spring緊密的接合在一起,2.4版後將Spring作為configuration的一種選項(但這是一個強力建議的選項,不用Spring...你得搞懂它的核心是由哪些部件組成然後自己組一個才行)。
Aegis & JAXB Data Binding
CXF 提供在"Databinding"層提供多種選擇,它所謂的Databinding指的是Object to Structured Text(XML, Json, etc...)之間的轉換。
JAX-RS(RESTful Services) Support
基於
JSR 339的實現,這篇文章主要想要玩想要討論的東西。JSR 339還蠻新的(final release:
24 May, 2013),CXF還沒有很好的實現它,想單純玩JAX-RS2.0的建議用Jersey,畢竟Jersey是reference implementation,沒做好Oracle就該打屁股了。
Apache 2.0 Licensed
Apache 出品,當然是Apache 2.0 License。
開始一個新的CXF JAX-RS專案
學一個東西,當然先從它的文件與Sample Code開始(
...這是指好的情況,有時候人品爆發,就會從看Code 開始)。CXF如果完全照它最簡單的設定走,其實還蠻簡單的,首先,讓我們用CXF所提供的maven archetype來generate 一個Eclipse Project。
如果用的是Eclipse + M2E來開發的,可以很簡單的用m2e的new maven project 來開始新專案,當中在選擇archetype時,建議用 catalog: http://repo1.maven.org/maven2/archetype-catalog.xml 下的cxf-jaxrs-service,來生成project,generate 出來的結果最有趣的部份在build區塊內,讓我們分段的來看一下它的內容:
<plugin><!--get a random port from system -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>reserve-network-port</id>
<goals>
<goal>reserve-network-port</goal>
</goals>
<phase>process-test-resources</phase>
<configuration>
<portNames>
<portName>test.server.port</portName>
</portNames>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<executions>
<execution>
<id>start-tomcat</id>
<goals>
<goal>run-war</goal>
</goals>
<phase>pre-integration-test</phase>
<configuration>
<port>${test.server.port}</port>
<path>/jaxrs-service</path>
<fork>true</fork>
<useSeparateTomcatClassLoader>true</useSeparateTomcatClassLoader>
</configuration>
</execution>
<execution>
<id>stop-tomcat</id>
<goals>
<goal>shutdown</goal>
</goals>
<phase>post-integration-test</phase>
<configuration>
<path>/jaxrs-service</path>
</configuration>
</execution>
</executions>
</plugin>
可以看見這段是先利用build-helper-maven-plugin 先去取得一個OS上沒有用到的IP,然後存到變數test.server.port上,這樣之後tomcat7-maven-plugin要啟動tomcat7時,就有了一個適當的IP可以用了。
而Tomcat7-maven-plugin會在pre-integration-test phase做start up,然後在post-integration-test phase做shut down,這樣在integration-test phase 期間,unit test case執行的時候就有正確的環境可以執行,底下是maven跑integration-test 的設定:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.8.1</version>
<executions>
<execution>
<id>integration-test</id>
<goals>
<goal>integration-test</goal>
</goals>
<configuration>
<systemPropertyVariables>
<service.url>http://localhost:${test.server.port}/cxf_demo2</service.url>
</systemPropertyVariables>
</configuration>
</execution>
<execution>
<id>verify</id>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
像這樣的對continuous integration的友善支持,對以現代化開發流程為前提要選用framework來說,是很重要的。
像我一樣,跟Maven不很熟的朋友,建議看一下
Maven - Introduction to the Build Life-cycle,了解一下Maven會跑哪些步驟,你可以做些什麼。
CXF在Container裡面的設定
CXF 在Application configuration上,在2.4版以前是與Spring Framework耦合的,也就是說,你得給一份Spring的xml configuration。
在web.xml上,你有兩個方式可以apply這個設定:
宣告一個Spring的ContextListener
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
或者,在CXFServlet 的宣告裡,透過init param 指定
<servlet>
<servlet-name>CXFServlet1</servlet-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
<init-param>
<param-name>config-location</param-name>
<param-value>/WEB-INF/beans1.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
我並不是很喜歡採用一個web service方案的時候,還要被強迫在專案指定特定的DI framework,但我可以理解基於CXF的龐大與複雜度,一個適當的DI framework可以幫忙做很多骯髒活。
不過在Configuration DI 的Solution這個方面的選擇,個人認為Jersey做了一個比較漂亮的決定 - HK2 (HK2是一個相容於CDI、非常的輕量、一旦把作為一個組裝器的事情給做完,可以把後續移交給其他DI Container的DI framework)。
在2.4版之後,CXF開始將Spring從核心切割出去,想要開始一個不基於Spring做組裝的CXF核心,你可以在web.xml裡下這樣宣告:
<servlet>
<servlet-name>NonSpringServlet</servlet-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFNonSpringServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
簡單的說,不想要Spring就宣告個
CXFNonSpringServlet(...這真的是很醜),這個Servlet 提供了一缸子的<init-param>給使用者客製化,大部分的值接受以換行符號或逗點分隔來給予多個參數(...這真的有夠醜,誰想要自己的web.xml被搞成這樣啊?)不喜歡這個作法的朋友,請create一個java class 去繼承CXFNonSpringServlet,事實上,目前的CXFServlet就是透過繼承它來實作的(也就是有地方可以給你挖Code、抄Code啦)。
於是,為了實現Google Guice + CXF,我自己實作了一個MyGuiceCXFJaxrsServlet.java:
public class MyGuiceCXFJaxrsServlet extends CXFNonSpringServlet {
private static final long serialVersionUID = 4798627144134171969L;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
Bus bus = this.getBus();
BusFactory.setDefaultBus(bus);
JAXRSServerFactoryBean jaxrsFacBean = new JAXRSServerFactoryBean();
jaxrsFacBean.setBus(getBus());
// config OUT interceptors...
configInInterceptors( jaxrsFacBean);
// config IN interceptors...
configOutInterceptors(jaxrsFacBean);
// determine target jax-rs services
configResources(jaxrsFacBean);
jaxrsFacBean.setProvider(new JacksonJsonProvider());
jaxrsFacBean.create();
}
/**
*
* @param jaxrsFacBean
*/
protected void configInInterceptors(JAXRSServerFactoryBean jaxrsFacBean) {
List<Interceptor<? extends Message>> interceptors =
new ArrayList<Interceptor<? extends Message>>();
interceptors.add(new JsonpInInterceptor());
jaxrsFacBean.setInInterceptors(interceptors);
}
/**
*
* @param jaxrsFacBean
*/
protected void configOutInterceptors(JAXRSServerFactoryBean jaxrsFacBean) {
List<Interceptor<? extends Message>> interceptors =
new ArrayList<Interceptor<? extends Message>>();
interceptors.add(new JsonpPreStreamInterceptor());
interceptors.add(new JsonpPostStreamInterceptor());
jaxrsFacBean.setOutInterceptors(interceptors);
// interceptors = new ArrayList<Interceptor<? extends Message>>();
// interceptors.add(new FaultOutInterceptor());
// jaxrsFacBean.setOutFaultInterceptors(interceptors);
}
/**
*
* @param jaxrsFacBean
*/
protected void configResources(JAXRSServerFactoryBean jaxrsFacBean){
//TODO make this configurable...
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(User.class).toInstance(new User("Zanyking"));
bind(CustomerService.class);
}
});
jaxrsFacBean.setResourceClasses(CustomerService.class);
jaxrsFacBean.setResourceProvider(CustomerService.class,
new SingletonResourceProvider(
injector.getInstance(CustomerService.class)));
}
}
最主要將Guice與CXF接合起來的地方是在
ConfigResource(JAXRSServerFactoryBean)
這個method 的實作裡,我用Guice生成了一個基於jax-rs的CustomerService物件,給CXF去用。
對於想要直接custom一個CXF系統來用的情境,這個method的傳入參數:
JAXRSServerFactoryBean
的API就是關鍵,今天如果有人想要搞一個GuiceCXF還是WeldCXF,那需要的就是從這顆bean開始去挖CXF的架構來設計。
PS:其實,我還另外挖到一支
org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet
,它的介紹竟然只有
這樣...這讓我感到很悲傷啊。
結論
我對於CXF感到不滿意的地方:
- JAX-RS 2.0的支援並不完全。應該說它還沒做完,在目前version 2.7.6,CXF並不支援
javax.ws.rs.client.Client
(我下了3.0.0-SNAPSHOT來看,看來未來它會把API都接好,但目前這個snapshot玩下去功能整個不會動是爛的...悲劇),你得用它的專屬WebClient API來撰寫測試。
- Documentation關於NonSpring這段的使用不完整且不友善,沒doc也就算了,沒sample code就很糟糕了,大部分的使用案例得要開發者看著Spring bean.xml去自己腦內翻譯成none spring version。
我對於CXF感到滿意的地方:
- 如果你喜歡XML configuration、Spring就是你的菜,那CXF太棒了。
- 出了很久,所以會發生的問題、範例、效能調整之類的東西,該解的都解了,如果用出問題,比較高的機會是開發者自己的問題。
- Maven有給archetype,這不是什麼了不起的玩意,但它有做。
雖然我想在目前跑的專案上,因為這玩意還不完全支援JAX-RS2.0,而且XML configuration對於專案成員來說也不是特別偏愛(都在用Guice了,你可以想見有很高的機率有人是XML hater)所以大概要出局,但若要我給這個產品一個分數,我想她還蠻OK的,基礎架構設計非常的java classic,當初開架構的沒有為了跟風亂搞什麼太fancy的東西結果四不像,一旦搞懂它的模型,設定上就四平八穩。
可見的未來它可能得遭遇一段陣痛期,我認為它得做好的工作有:
- JAX-RS2.0 Compliant:沒啥好說,越快支援越好,還不支援的每天它都正在流失客群。
- 去把跟其他DI的部份整好:不是誰都想用Spring的,而照顧其他DI framework使用者的需求不是一句:『自己去看Code』就結束的,做solution survey會真的挖下去看code的開發者可沒想像中那麼多。
- 網站本身Doc的水準有待加強:我知道Apache是Open Source Java界的丐幫(專案武林最多、組織最大、什麼千奇百怪的需求都照顧),但一個向上爬、積極爭取眼球的專案最重要的就是doc與sample code,有很多外人幫你試出好的用法不代表你就不用去整理。
不曉得有多少人在用CXF?有在用的、或有什麼其他關於CXF的問題的朋友,歡迎回應。