選框架猶如選媳婦,選來選去,最後我仍是選了「醜媳婦(CXF)」,爲何是它?由於 CXF 是 Apache 旗下的一款很是優秀的 WS 開源框架,具有輕量級的特性,並且能無縫整合到 Spring 中。html
其實 CXF 是兩個開源框架的整合,它們分別是:Celtix 與 XFire,前者是一款 ESB 框架,後者是一款 WS 框架。話說早在 2007 年 5 月,當 XFire 發展到了它的鼎盛時期(最終版本是 1.2.6),忽然對業界宣佈了一個使人震驚的消息:「XFire is now CXF」,隨後 CXF 2.0 誕生了,直到 2014 年 5 月,CXF 3.0 降臨了。真是 7 年磨一劍啊!CXF 終於長大了,相信在不久的未來,必定會取代 Java 界 WS 龍頭老大 Axis 的江湖地位,貌似 Axis 自從 2012 年 4 月之後就沒有升級了,這是要告別 Java 界的節奏嗎?仍是後面有更大的動做?java
如何使用 CXF 開發基於 SOAP 的 WS 呢?web
這就是我今天要與您分享的內容,重點是在 Web 容器中發佈與調用 WS,這樣也更加貼近咱們實際工做的場景。spring
在 CXF 這個主角正是登臺以前,我想先請出今天的配角 Oracle JAX-WS RI,簡稱:RI(日),全稱:Reference Implementation,它是 Java 官方提供的 JAX-WS 規範的具體實現。apache
先讓 RI 來跑跑龍套,先來看看如何使用 RI 發佈 WS 吧!編程
第一步:整合 Tomcat 與 RI瀏覽器
這一步稍微有一點點繁瑣,不過也很容易作到。首先您須要經過如下地址,下載一份 RI 的程序包:安全
https://jax-ws.java.net/2.2.8/app
下載完畢後,只需解壓便可,假設解壓到 D:/Tool/jaxws-ri 目錄下。隨後須要對 Tomcat 的 config/catalina.properties
文件進行配置:框架
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,D:/Tool/jaxws-ri/lib/*.jar
注意:以上配置中的最後一部分,其實就是在 Tomcat 中添加一系列關於 RI 的 jar 包。
看起來並不複雜哦,只是對現有的 Tomcat 有所改造而已,固然,您將這些 jar 包所有放入本身應用的 WEB-INF/lib 目錄中也是可行的。
第二步:編寫 WS 接口及其實現
接口部分:
<!-- lang: java --> package demo.ws.soap_jaxws; import javax.jws.WebService; @WebService public interface HelloService { String say(String name); }
實現部分:
<!-- lang: java --> package demo.ws.soap_jaxws; import javax.jws.WebService; @WebService( serviceName = "HelloService", portName = "HelloServicePort", endpointInterface = "demo.ws.soap_jaxws.HelloService" ) public class HelloServiceImpl implements HelloService { public String say(String name) { return "hello " + name; } }
注意:接口與實現類上都標註 javax.jws.WebService
註解,可在實現類的註解中添加一些關於 WS 的相關信息,例如:serviceName
、portName
等,固然這是可選的,爲了讓生成的 WSDL 的可讀性更增強而已。
第三步:在 WEB-INF 下添加 sun-jaxws.xml 文件
就是在這個 sun-jaxws.xml 文件裏配置須要發佈的 WS,其內容以下:
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0"> <endpoint name="HelloService" implementation="demo.ws.soap_jaxws.HelloServiceImpl" url-pattern="/ws/soap/hello"/> </endpoints>
這裏僅發佈一個 endpoint,並配置三個屬性:WS 的名稱、實現類、URL 模式。正是經過這個「URL 模式」來訪問 WSDL 的,立刻您就能夠看到。
第四步:部署應用並啓動 Tomcat
當 Tomcat 啓動成功後,會在控制檯上看到以下信息:
2014-7-2 13:39:31 com.sun.xml.ws.transport.http.servlet.WSServletDelegate <init> 信息: WSSERVLET14: JAX-WS servlet 正在初始化 2014-7-2 13:39:31 com.sun.xml.ws.transport.http.servlet.WSServletContextListener contextInitialized 信息: WSSERVLET12: JAX-WS 上下文監聽程序正在初始化
哎呦,不錯哦!仍是中文的。
隨後,立馬打開您的瀏覽器,輸入如下地址:
http://localhost:8080/ws/soap/hello
若是不出意外的話,您如今應該能夠看到以下界面了:
看起來這應該是一個 WS 控制檯,方便咱們查看發佈了哪些 WS,能夠點擊上面的 WSDL 連接可查看具體信息。
看起來 RI 確實挺好的!不只僅有一個控制檯,並且還能與 Tomcat 無縫整合。但 RI 彷佛與 Spring 的整合能力並非太強,也許是由於 Oracle 是 EJB 擁護者吧。
那麼,CXF 也具有 RI 這樣的特性嗎?而且可以與 Spring 很好地集成嗎?
CXF 不只能夠將 WS 發佈在任何的 Web 容器中,並且還提供了一個便於測試的 Web 環境,實際上它內置了一個 Jetty。
咱們先看看如何啓動 Jetty 發佈 WS,再來演示如何在 Spring 容器中整合 CXF。
第一步:配置 Maven 依賴
若是您是一位 Maven 用戶,那麼下面這段配置相信必定不會陌生:
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>demo.ws</groupId> <artifactId>soap_cxf</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <cxf.version>3.0.0</cxf.version> </properties> <dependencies> <!-- CXF --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>${cxf.version}</version> </dependency> </dependencies> </project>
若是您目前尚未使用 Maven,那麼就須要從如下地址下載 CXF 的相關 jar 包,並將其放入應用中。
http://cxf.apache.org/download.html
第二步:寫一個 WS 接口及其實現
接口部分:
<!-- lang: java --> package demo.ws.soap_cxf; import javax.jws.WebService; @WebService public interface HelloService { String say(String name); }
實現部分:
<!-- lang: java --> package demo.ws.soap_cxf; import javax.jws.WebService; @WebService public class HelloServiceImpl implements HelloService { public String say(String name) { return "hello " + name; } }
這裏簡化了實現類上的 WebService 註解的配置,讓 CXF 自動爲咱們取默認值便可。
第三步:寫一個 JaxWsServer 類來發布 WS
<!-- lang: java --> package demo.ws.soap_cxf; import org.apache.cxf.jaxws.JaxWsServerFactoryBean; public class JaxWsServer { public static void main(String[] args) { JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean(); factory.setAddress("http://localhost:8080/ws/soap/hello"); factory.setServiceClass(HelloService.class); factory.setServiceBean(new HelloServiceImpl()); factory.create(); System.out.println("soap ws is published"); } }
發佈 WS 除了以上這種基於 JAX-WS 的方式之外,CXF 還提供了另外一種選擇,名爲 simple
方式。
經過 simple 方式發佈 WS 的代碼以下:
<!-- lang: java --> package demo.ws.soap_cxf; import org.apache.cxf.frontend.ServerFactoryBean; public class SimpleServer { public static void main(String[] args) { ServerFactoryBean factory = new ServerFactoryBean(); factory.setAddress("http://localhost:8080/ws/soap/hello"); factory.setServiceClass(HelloService.class); factory.setServiceBean(new HelloServiceImpl()); factory.create(); System.out.println("soap ws is published"); } }
注意:以 simple 方式發佈的 WS,不能經過 JAX-WS 方式來調用,只能經過 simple 方式的客戶端來調用,下文會展現 simple 方式的客戶端代碼。
第四步:運行 JaxWsServer 類
當 JaxWsServer 啓動後,在控制檯中會看到打印出來的一句提示。隨後,在瀏覽器中輸入如下 WSDL 地址:
http://localhost:8080/ws/soap/hello?wsdl
注意:經過 CXF 內置的 Jetty 發佈的 WS,僅能查看 WSDL,卻沒有像 RI 那樣的 WS 控制檯。
可見,這種方式很是容易測試與調試,大大節省了咱們的開發效率,但這種方式並不適合於生產環境,咱們仍是須要依靠於 Tomcat 與 Spring。
那麼,CXF 在實戰中是如何集成在 Spring 容器中的呢?見證奇蹟的時候到了!
Tomcat + Spring + CXF,這個場景應該更加接近咱們的實際工做狀況,開發過程也是很是天然。
第一步:配置 Maven 依賴
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>demo.ws</groupId> <artifactId>soap_spring_cxf</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.0.5.RELEASE</spring.version> <cxf.version>3.0.0</cxf.version> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <!-- CXF --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> </dependencies> </project>
第二步:寫一個 WS 接口及其實現
接口部分:
<!-- lang: java --> package demo.ws.soap_spring_cxf; import javax.jws.WebService; @WebService public interface HelloService { String say(String name); }
實現部分:
<!-- lang: java --> package demo.ws.soap_spring_cxf; import javax.jws.WebService; import org.springframework.stereotype.Component; @WebService @Component public class HelloServiceImpl implements HelloService { public String say(String name) { return "hello " + name; } }
須要在實現類上添加 Spring 的 org.springframework.stereotype.Component
註解,這樣才能被 Spring IOC 容器掃描到,認爲它是一個 Spring Bean,能夠根據 Bean ID(這裏是 helloServiceImpl)來獲取 Bean 實例。
第三步:配置 web.xml
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <!-- Spring --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- CXF --> <servlet> <servlet-name>cxf</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>cxf</servlet-name> <url-pattern>/ws/*</url-pattern> </servlet-mapping> </web-app>
全部帶有 /ws
前綴的請求,將會交給被 CXFServlet
進行處理,也就是處理 WS 請求了。目前主要使用了 Spring IOC 的特性,利用了 ContextLoaderListener
加載 Spring 配置文件,即這裏定義的 spring.xml 文件。
第四步:配置 Spring
配置 spring.xml:
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="demo.ws"/> <import resource="spring-cxf.xml"/> </beans>
以上配置作了兩件事情:
demo.ws
,在這個包下面(包括全部子包)凡是帶有 Component
的類都會掃描到 Spring IOC 容器中。spring-cxf.xml
文件,用於編寫 CXF 相關配置。將配置文件分離,是一種很好的開發方式。第五步:配置 CXF
配置 spring-cxf.xml:
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <jaxws:server id="helloService" address="/soap/hello"> <jaxws:serviceBean> <ref bean="helloServiceImpl"/> </jaxws:serviceBean> </jaxws:server> </beans>
經過 CXF 提供的 Spring 命名空間,即 jaxws:server
,來發布 WS。其中,最重要的是 address
屬性,以及經過 jaxws:serviceBean
配置的 Spring Bean。
可見,在 Spring 中集成 CXF 比想象的更加簡單,此外,還有一種更簡單的配置方法,那就是使用 CXF 提供的 endpoint
方式,配置以下:
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <jaxws:endpoint id="helloService" implementor="#helloServiceImpl" address="/soap/hello"/> </beans>
使用 jaxws:endpoint
能夠簡化 WS 發佈的配置,與 jaxws:server
相比,確實是一種進步。
注意:這裏的 implementor
屬性值是 #helloServiceImpl
,這是 CXF 特有的簡寫方式,並不是是 Spring 的規範,意思是經過 Spring 的 Bean ID 獲取 Bean 實例。
一樣,也能夠在 Spring 中使用 simple 方式來發布 WS,配置以下:
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:simple="http://cxf.apache.org/simple" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://cxf.apache.org/simple http://cxf.apache.org/schemas/simple.xsd"> <simple:server id="helloService" serviceClass="#helloService" address="/soap/hello"> <simple:serviceBean> <ref bean="#helloServiceImpl"/> </simple:serviceBean> </simple:server> </beans>
可見,simple:server
與 jaxws:server
的配置方式相似,都須要配置一個 serviceBean
。
比較以上這三種方式,我我的更加喜歡第二種,也就是 endpoint 方式,由於它夠簡單!
至於爲何 CXF 要提供如此之多的 WS 發佈方式?我我的認爲,CXF 爲了知足廣大開發者的喜愛,也是爲了向前兼容,因此這些方案所有保留下來了。
第六步:啓動 Tomcat
將應用部署到 Tomcat 中,在瀏覽器中輸入如下地址可進入 CXF 控制檯:
http://localhost:8080/ws
經過以上過程,能夠看出 CXF 徹底具有 RI 的易用性,而且與 Spring 有很好的可集成性,並且配置也很是簡單。
一樣經過這個地址能夠查看 WSDL:
http://localhost:8080/ws/soap/hello?wsdl
注意:緊接在 /ws
前綴後面的 /soap/hello
,實際上是在 address="/soap/hello"
中配置的。
如今已經成功地經過 CXF 對外發布了 WS,下面要作的事情就是用 WS 客戶端來調用這些 endpoint 了。
您能夠再也不使用 JDK 內置的 WS 客戶端,也沒必要經過 WSDL 打客戶端 jar 包,由於 CXF 已經爲您提供了多種 WS 客戶端解決方案,根據您的口味自行選擇吧!
方案一:靜態代理客戶端
<!-- lang: java --> package demo.ws.soap_cxf; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; public class JaxWsClient { public static void main(String[] args) { JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setAddress("http://localhost:8080/ws/soap/hello"); factory.setServiceClass(HelloService.class); HelloService helloService = factory.create(HelloService.class); String result = helloService.say("world"); System.out.println(result); } }
這種方案須要自行經過 WSDL 打客戶端 jar 包,經過靜態代理的方式來調用 WS。這種作法最爲原始,下面的方案更有特點。
方案二:動態代理客戶端
<!-- lang: java --> package demo.ws.soap_cxf; import org.apache.cxf.endpoint.Client; import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory; public class JaxWsDynamicClient { public static void main(String[] args) { JaxWsDynamicClientFactory factory = JaxWsDynamicClientFactory.newInstance(); Client client = factory.createClient("http://localhost:8080/ws/soap/hello?wsdl"); try { Object[] results = client.invoke("say", "world"); System.out.println(results[0]); } catch (Exception e) { e.printStackTrace(); } } }
這種方案無需經過 WSDL 打客戶端 jar 包,底層實際上經過 JDK 的動態代理特性完成的,CXF 實際上作了一個簡單的封裝。與 JDK 動態客戶端不同的是,此時無需使用 HelloService 接口,能夠說是貨真價實的 WS 動態客戶端。
方案三:通用動態代理客戶端
<!-- lang: java --> package demo.ws.soap_cxf; import org.apache.cxf.endpoint.Client; import org.apache.cxf.endpoint.dynamic.DynamicClientFactory; public class DynamicClient { public static void main(String[] args) { DynamicClientFactory factory = DynamicClientFactory.newInstance(); Client client = factory.createClient("http://localhost:8080/ws/soap/hello?wsdl"); try { Object[] results = client.invoke("say", "world"); System.out.println(results[0]); } catch (Exception e) { e.printStackTrace(); } } }
這種方案與「方案三」相似,但不一樣的是,它不只用於調用 JAX-WS 方式發佈的 WS,也用於使用 simple 方式發佈的 WS,更加智能了。
方案四:基於 CXF simple 方式的客戶端
<!-- lang: java --> package demo.ws.soap_cxf; import org.apache.cxf.frontend.ClientProxyFactoryBean; public class SimpleClient { public static void main(String[] args) { ClientProxyFactoryBean factory = new ClientProxyFactoryBean(); factory.setAddress("http://localhost:8080/ws/soap/hello"); factory.setServiceClass(HelloService.class); HelloService helloService = factory.create(HelloService.class); String result = helloService.say("world"); System.out.println(result); } }
這種方式僅用於調用 simple 方式發佈的 WS,不能調用 JAX-WS 方式發佈的 WS,這是須要注意的。
方案五:基於 Spring 的客戶端
方法一:使用 JaxWsProxyFactoryBean
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"> <bean id="factoryBean" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean"> <property name="serviceClass" value="demo.ws.soap_spring_cxf.HelloService"/> <property name="address" value="http://localhost:8080/ws/soap/hello"/> </bean> <bean id="helloService" factory-bean="factoryBean" factory-method="create"/> </beans>
方法二:使用 jaxws:client(推薦)
<!-- lang: xml --> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <jaxws:client id="helloService" serviceClass="demo.ws.soap_spring_cxf.HelloService" address="http://localhost:8080/ws/soap/hello"/> </beans>
客戶端代碼:
<!-- lang: java --> package demo.ws.soap_spring_cxf; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-client.xml"); HelloService helloService = context.getBean("helloService", HelloService.class); String result = helloService.say("world"); System.out.println(result); } }
談不上那種方案更加優秀,建議根據您的實際場景選擇最爲合適的方案。
經過閱讀本文,相信您已經大體瞭解了 CXF 的基本用法。可獨立使用,也可與 Spring 集成;可面向 API 來編程,也可以使用 Spring 配置;發佈 WS 的方式有多種,調用 WS 的方式一樣也有多種。
尤爲是 Spring + CXF 這對搭檔,讓發佈 WS 更加簡單,只需如下四個步驟:
固然,目前您看到的都是 WS 的基礎特性,下期我將帶您走進 WS 的高級話題 —— 基於 WS 的 Security 解決方案,業界稱爲 WS-Security
規範,使用它可確保您的 SOAP 服務更加安全。感謝您閱讀本文,咱們下期再見!