至於為什麼不直接用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開始(
如果用的是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>
<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 Compliant:沒啥好說,越快支援越好,還不支援的每天它都正在流失客群。
- 去把跟其他DI的部份整好:不是誰都想用Spring的,而照顧其他DI framework使用者的需求不是一句:『自己去看Code』就結束的,做solution survey會真的挖下去看code的開發者可沒想像中那麼多。
- 網站本身Doc的水準有待加強:我知道Apache是Open Source Java界的丐幫(專案武林最多、組織最大、什麼千奇百怪的需求都照顧),但一個向上爬、積極爭取眼球的專案最重要的就是doc與sample code,有很多外人幫你試出好的用法不代表你就不用去整理。
不曉得有多少人在用CXF?有在用的、或有什麼其他關於CXF的問題的朋友,歡迎回應。