Web Service 那點事兒 —— 使用 CXF 開發 SOAP 服務

原文地址:http://my.oschina.net/huangyong/blog/286439


選框架猶如選媳婦,選來選去,最後我還是選了“醜媳婦(CXF)”,爲什麼是它?因爲 CXF 是 Apache 旗下的一款非常優秀的 WS 開源框架,具備輕量級的特性,而且能無縫整合到 Spring 中。

其實 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 界的節奏嗎?還是後面有更大的動作?

如何使用 CXF 開發基於 SOAP 的 WS 呢?

這就是我今天要與您分享的內容,重點是在 Web 容器中發佈與調用 WS,這樣也更加貼近我們實際工作的場景。

在 CXF 這個主角正是登臺之前,我想先請出今天的配角 Oracle JAX-WS RI,簡稱:RI(日),全稱:Reference Implementation,它是 Java 官方提供的 JAX-WS 規範的具體實現。

先讓 RI 來跑跑龍套,先來看看如何使用 RI 發佈 WS 吧!

1. 使用 RI 發佈 WS

第一步:整合 Tomcat 與 RI

這一步稍微有一點點繁瑣,不過也很容易做到。首先您需要通過以下地址,下載一份 RI 的程序包:

https://jax-ws.java.net/2.2.8/

下載完畢後,只需解壓即可,假設解壓到 D:/Tool/jaxws-ri 目錄下。隨後需要對 Tomcat 的 config/catalina.properties 文件進行配置:

1 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 接口及其實現

接口部分:

1 package demo.ws.soap_jaxws;
2  
3 import javax.jws.WebService;
4  
5 @WebService
6 public interface HelloService {
7  
8     String say(String name);
9 }

實現部分:

01 package demo.ws.soap_jaxws;
02  
03 import javax.jws.WebService;
04  
05 @WebService(
06     serviceName = "HelloService",
07     portName = "HelloServicePort",
08     endpointInterface = "demo.ws.soap_jaxws.HelloService"
09 )
10 public class HelloServiceImpl implements HelloService {
11  
12     public String say(String name) {
13         return "hello " + name;
14     }
15 }

注意:接口與實現類上都標註 javax.jws.WebService 註解,可在實現類的註解中添加一些關於 WS 的相關信息,例如:serviceNameportName 等,當然這是可選的,爲了讓生成的 WSDL 的可讀性更加強而已。

第三步:在 WEB-INF 下添加 sun-jaxws.xml 文件

就是在這個 sun-jaxws.xml 文件裏配置需要發佈的 WS,其內容如下:

1 <?xml version="1.0" encoding="UTF-8"?>
2 <endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
3  
4     <endpoint name="HelloService"
5               implementation="demo.ws.soap_jaxws.HelloServiceImpl"
6               url-pattern="/ws/soap/hello"/>
7  
8 </endpoints>

這裏僅發佈一個 endpoint,並配置三個屬性:WS 的名稱、實現類、URL 模式。正是通過這個“URL 模式”來訪問 WSDL 的,馬上您就可以看到。

第四步:部署應用並啓動 Tomcat

當 Tomcat 啓動成功後,會在控制檯上看到如下信息:

1 2014-7-2 13:39:31 com.sun.xml.ws.transport.http.servlet.WSServletDelegate <init>
2 信息: WSSERVLET14: JAX-WS servlet 正在初始化
3 2014-7-2 13:39:31 com.sun.xml.ws.transport.http.servlet.WSServletContextListener contextInitialized
4 信息: WSSERVLET12: JAX-WS 上下文監聽程序正在初始化

哎呦,不錯哦!還是中文的。

隨後,立馬打開您的瀏覽器,輸入以下地址:

1 http://localhost:8080/ws/soap/hello

如果不出意外的話,您現在應該可以看到如下界面了:

RI 控制檯

看起來這應該是一個 WS 控制檯,方便我們查看發佈了哪些 WS,可以點擊上面的 WSDL 鏈接可查看具體信息。

看起來 RI 確實挺好的!不僅僅有一個控制檯,而且還能與 Tomcat 無縫整合。但 RI 似乎與 Spring 的整合能力並不是太強,也許是因爲 Oracle 是 EJB 擁護者吧。

那麼,CXF 也具備 RI 這樣的特性嗎?並且能夠與 Spring 很好地集成嗎?

CXF 不僅可以將 WS 發佈在任何的 Web 容器中,而且還提供了一個便於測試的 Web 環境,實際上它內置了一個 Jetty。

我們先看看如何啓動 Jetty 發佈 WS,再來演示如何在 Spring 容器中整合 CXF。

2. 使用 CXF 內置的 Jetty 發佈 WS

第一步:配置 Maven 依賴

如果您是一位 Maven 用戶,那麼下面這段配置相信一定不會陌生:

01 <?xml version="1.0" encoding="UTF-8"?>
02 <project xmlns="http://maven.apache.org/POM/4.0.0"
03          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
05          http://maven.apache.org/xsd/maven-4.0.0.xsd">
06  
07     <modelVersion>4.0.0</modelVersion>
08  
09     <groupId>demo.ws</groupId>
10     <artifactId>soap_cxf</artifactId>
11     <version>1.0-SNAPSHOT</version>
12  
13     <properties>
14         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15         <cxf.version>3.0.0</cxf.version>
16     </properties>
17  
18     <dependencies>
19         <!-- CXF -->
20         <dependency>
21             <groupId>org.apache.cxf</groupId>
22             <artifactId>cxf-rt-frontend-jaxws</artifactId>
23             <version>${cxf.version}</version>
24         </dependency>
25         <dependency>
26             <groupId>org.apache.cxf</groupId>
27             <artifactId>cxf-rt-transports-http-jetty</artifactId>
28             <version>${cxf.version}</version>
29         </dependency>
30     </dependencies>
31  
32 </project>

如果您目前還沒有使用 Maven,那麼就需要從以下地址下載 CXF 的相關 jar 包,並將其放入應用中。

http://cxf.apache.org/download.html

第二步:寫一個 WS 接口及其實現

接口部分:

1 package demo.ws.soap_cxf;
2  
3 import javax.jws.WebService;
4  
5 @WebService
6 public interface HelloService {
7  
8     String say(String name);
9 }

實現部分:

01 package demo.ws.soap_cxf;
02  
03 import javax.jws.WebService;
04  
05 @WebService
06 public class HelloServiceImpl implements HelloService {
07  
08     public String say(String name) {
09         return "hello " + name;
10     }
11 }

這裏簡化了實現類上的 WebService 註解的配置,讓 CXF 自動爲我們取默認值即可。

第三步:寫一個 JaxWsServer 類來發布 WS

01 package demo.ws.soap_cxf;
02  
03 import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
04  
05 public class JaxWsServer {
06  
07     public static void main(String[] args) {
08         JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
09         factory.setAddress("http://localhost:8080/ws/soap/hello");
10         factory.setServiceClass(HelloService.class);
11         factory.setServiceBean(new HelloServiceImpl());
12         factory.create();
13         System.out.println("soap ws is published");
14     }
15 }

發佈 WS 除了以上這種基於 JAX-WS 的方式以外,CXF 還提供了另一種選擇,名爲 simple 方式。

通過 simple 方式發佈 WS 的代碼如下:

01 package demo.ws.soap_cxf;
02  
03 import org.apache.cxf.frontend.ServerFactoryBean;
04  
05 public class SimpleServer {
06  
07     public static void main(String[] args) {
08         ServerFactoryBean factory = new ServerFactoryBean();
09         factory.setAddress("http://localhost:8080/ws/soap/hello");
10         factory.setServiceClass(HelloService.class);
11         factory.setServiceBean(new HelloServiceImpl());
12         factory.create();
13         System.out.println("soap ws is published");
14     }
15 }

注意:以 simple 方式發佈的 WS,不能通過 JAX-WS 方式來調用,只能通過 simple 方式的客戶端來調用,下文會展示 simple 方式的客戶端代碼。

第四步:運行 JaxWsServer 類

當 JaxWsServer 啓動後,在控制檯中會看到打印出來的一句提示。隨後,在瀏覽器中輸入以下 WSDL 地址:

1 http://localhost:8080/ws/soap/hello?wsdl

注意:通過 CXF 內置的 Jetty 發佈的 WS,僅能查看 WSDL,卻沒有像 RI 那樣的 WS 控制檯。

可見,這種方式非常容易測試與調試,大大節省了我們的開發效率,但這種方式並不適合於生產環境,我們還是需要依靠於 Tomcat 與 Spring。

那麼,CXF 在實戰中是如何集成在 Spring 容器中的呢?見證奇蹟的時候到了!

3. 在 Web 容器中使用 Spring + CXF 發佈 WS

Tomcat + Spring + CXF,這個場景應該更加接近我們的實際工作情況,開發過程也是非常自然。

第一步:配置 Maven 依賴

01 <?xml version="1.0" encoding="UTF-8"?>
02 <project xmlns="http://maven.apache.org/POM/4.0.0"
03          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
05          http://maven.apache.org/xsd/maven-4.0.0.xsd">
06  
07     <modelVersion>4.0.0</modelVersion>
08  
09     <groupId>demo.ws</groupId>
10     <artifactId>soap_spring_cxf</artifactId>
11     <version>1.0-SNAPSHOT</version>
12     <packaging>war</packaging>
13  
14     <properties>
15         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16         <spring.version>4.0.5.RELEASE</spring.version>
17         <cxf.version>3.0.0</cxf.version>
18     </properties>
19  
20     <dependencies>
21         <!-- Spring -->
22         <dependency>
23             <groupId>org.springframework</groupId>
24             <artifactId>spring-context</artifactId>
25             <version>${spring.version}</version>
26         </dependency>
27         <dependency>
28             <groupId>org.springframework</groupId>
29             <artifactId>spring-web</artifactId>
30             <version>${spring.version}</version>
31         </dependency>
32         <!-- CXF -->
33         <dependency>
34             <groupId>org.apache.cxf</groupId>
35             <artifactId>cxf-rt-frontend-jaxws</artifactId>
36             <version>${cxf.version}</version>
37         </dependency>
38         <dependency>
39             <groupId>org.apache.cxf</groupId>
40             <artifactId>cxf-rt-transports-http</artifactId>
41             <version>${cxf.version}</version>
42         </dependency>
43     </dependencies>
44  
45 </project>

第二步:寫一個 WS 接口及其實現

接口部分:

1 package demo.ws.soap_spring_cxf;
2  
3 import javax.jws.WebService;
4  
5 @WebService
6 public interface HelloService {
7  
8     String say(String name);
9 }

實現部分:

01 package demo.ws.soap_spring_cxf;
02  
03 import javax.jws.WebService;
04 import org.springframework.stereotype.Component;
05  
06 @WebService
07 @Component
08 public class HelloServiceImpl implements HelloService {
09  
10     public String say(String name) {
11         return "hello " + name;
12     }
13 }

需要在實現類上添加 Spring 的 org.springframework.stereotype.Component 註解,這樣才能被 Spring IOC 容器掃描到,認爲它是一個 Spring Bean,可以根據 Bean ID(這裏是 helloServiceImpl)來獲取 Bean 實例。

第三步:配置 web.xml

01 <?xml version="1.0" encoding="UTF-8"?>
02 <web-app xmlns="http://java.sun.com/xml/ns/javaee"
03          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
05          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
06          version="3.0">
07  
08     <!-- Spring -->
09     <context-param>
10         <param-name>contextConfigLocation</param-name>
11         <param-value>classpath:spring.xml</param-value>
12     </context-param>
13     <listener>
14         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
15     </listener>
16  
17     <!-- CXF -->
18     <servlet>
19         <servlet-name>cxf</servlet-name>
20         <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
21     </servlet>
22     <servlet-mapping>
23         <servlet-name>cxf</servlet-name>
24         <url-pattern>/ws/*</url-pattern>
25     </servlet-mapping>
26  
27 </web-app>

所有帶有 /ws 前綴的請求,將會交給被 CXFServlet 進行處理,也就是處理 WS 請求了。目前主要使用了 Spring IOC 的特性,利用了 ContextLoaderListener 加載 Spring 配置文件,即這裏定義的 spring.xml 文件。

第四步:配置 Spring

配置 spring.xml:

01 <?xml version="1.0" encoding="UTF-8"?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04        xmlns:context="http://www.springframework.org/schema/context"
05        xsi:schemaLocation="http://www.springframework.org/schema/beans
06        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
07        http://www.springframework.org/schema/context
08        http://www.springframework.org/schema/context/spring-context-4.0.xsd">
09  
10     <context:component-scan base-package="demo.ws"/>
11  
12     <import resource="spring-cxf.xml"/>
13  
14 </beans>

以上配置做了兩件事情:

  1. 定義 IOC 容器掃描路徑,即這裏定義的 demo.ws,在這個包下面(包括所有子包)凡是帶有 Component 的類都會掃描到 Spring IOC 容器中。
  2. 引入 spring-cxf.xml 文件,用於編寫 CXF 相關配置。將配置文件分離,是一種很好的開發方式。

第五步:配置 CXF

配置 spring-cxf.xml:

01 <?xml version="1.0" encoding="UTF-8"?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04        xmlns:jaxws="http://cxf.apache.org/jaxws"
05        xsi:schemaLocation="http://www.springframework.org/schema/beans
06        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
07        http://cxf.apache.org/jaxws
08        http://cxf.apache.org/schemas/jaxws.xsd">
09  
10     <jaxws:server id="helloService" address="/soap/hello">
11         <jaxws:serviceBean>
12             <ref bean="helloServiceImpl"/>
13         </jaxws:serviceBean>
14     </jaxws:server>
15  
16 </beans>

通過 CXF 提供的 Spring 命名空間,即 jaxws:server,來發布 WS。其中,最重要的是 address 屬性,以及通過 jaxws:serviceBean 配置的 Spring Bean。

可見,在 Spring 中集成 CXF 比想象的更加簡單,此外,還有一種更簡單的配置方法,那就是使用 CXF 提供的 endpoint 方式,配置如下:

01 <?xml version="1.0" encoding="UTF-8"?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04        xmlns:jaxws="http://cxf.apache.org/jaxws"
05        xsi:schemaLocation="http://www.springframework.org/schema/beans
06        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
07        http://cxf.apache.org/jaxws
08        http://cxf.apache.org/schemas/jaxws.xsd">
09  
10     <jaxws:endpoint id="helloService" implementor="#helloServiceImpl" address="/soap/hello"/>
11  
12 </beans>

使用 jaxws:endpoint 可以簡化 WS 發佈的配置,與 jaxws:server 相比,確實是一種進步。

注意:這裏的 implementor 屬性值是 #helloServiceImpl,這是 CXF 特有的簡寫方式,並非是 Spring 的規範,意思是通過 Spring 的 Bean ID 獲取 Bean 實例。

同樣,也可以在 Spring 中使用 simple 方式來發布 WS,配置如下:

01 <?xml version="1.0" encoding="UTF-8"?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04        xmlns:simple="http://cxf.apache.org/simple"
05        xsi:schemaLocation="http://www.springframework.org/schema/beans
06        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
07        http://cxf.apache.org/simple
08        http://cxf.apache.org/schemas/simple.xsd">
09  
10     <simple:server id="helloService" serviceClass="#helloService" address="/soap/hello">
11         <simple:serviceBean>
12             <ref bean="#helloServiceImpl"/>
13         </simple:serviceBean>
14     </simple:server>
15  
16 </beans>

可見,simple:server 與 jaxws:server 的配置方式類似,都需要配置一個 serviceBean

比較以上這三種方式,我個人更加喜歡第二種,也就是 endpoint 方式,因爲它夠簡單!

至於爲什麼 CXF 要提供如此之多的 WS 發佈方式?我個人認爲,CXF 爲了滿足廣大開發者的喜好,也是爲了向前兼容,所以這些方案全部保留下來了。

第六步:啓動 Tomcat

將應用部署到 Tomcat 中,在瀏覽器中輸入以下地址可進入 CXF 控制檯:

1 http://localhost:8080/ws

CXF 控制檯

通過以上過程,可以看出 CXF 完全具備 RI 的易用性,並且與 Spring 有很好的可集成性,而且配置也非常簡單。

同樣通過這個地址可以查看 WSDL:

1 http://localhost:8080/ws/soap/hello?wsdl

注意:緊接在 /ws 前綴後面的 /soap/hello,其實是在 address="/soap/hello" 中配置的。

現在已經成功地通過 CXF 對外發布了 WS,下面要做的事情就是用 WS 客戶端來調用這些 endpoint 了。

您可以不再使用 JDK 內置的 WS 客戶端,也不必通過 WSDL 打客戶端 jar 包,因爲 CXF 已經爲您提供了多種 WS 客戶端解決方案,根據您的口味自行選擇吧!

4. 關於 CXF 提供的 WS 客戶端

方案一:靜態代理客戶端

01 package demo.ws.soap_cxf;
02  
03 import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
04  
05 public class JaxWsClient {
06  
07     public static void main(String[] args) {
08         JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
09         factory.setAddress("http://localhost:8080/ws/soap/hello");
10         factory.setServiceClass(HelloService.class);
11  
12         HelloService helloService = factory.create(HelloService.class);
13         String result = helloService.say("world");
14         System.out.println(result);
15     }
16 }

這種方案需要自行通過 WSDL 打客戶端 jar 包,通過靜態代理的方式來調用 WS。這種做法最爲原始,下面的方案更有特色。

方案二:動態代理客戶端

01 package demo.ws.soap_cxf;
02  
03 import org.apache.cxf.endpoint.Client;
04 import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
05  
06 public class JaxWsDynamicClient {
07  
08     public static void main(String[] args) {
09         JaxWsDynamicClientFactory factory = JaxWsDynamicClientFactory.newInstance();
10         Client client = factory.createClient("http://localhost:8080/ws/soap/hello?wsdl");
11  
12         try {
13             Object[] results = client.invoke("say""world");
14             System.out.println(results[0]);
15         catch (Exception e) {
16             e.printStackTrace();
17         }
18     }
19 }

這種方案無需通過 WSDL 打客戶端 jar 包,底層實際上通過 JDK 的動態代理特性完成的,CXF 實際上做了一個簡單的封裝。與 JDK 動態客戶端不一樣的是,此時無需使用 HelloService 接口,可以說是貨真價實的 WS 動態客戶端。

方案三:通用動態代理客戶端

01 package demo.ws.soap_cxf;
02  
03 import org.apache.cxf.endpoint.Client;
04 import org.apache.cxf.endpoint.dynamic.DynamicClientFactory;
05  
06 public class DynamicClient {
07  
08     public static void main(String[] args) {
09         DynamicClientFactory factory = DynamicClientFactory.newInstance();
10         Client client = factory.createClient("http://localhost:8080/ws/soap/hello?wsdl");
11  
12         try {
13             Object[] results = client.invoke("say""world");
14             System.out.println(results[0]);
15         catch (Exception e) {
16             e.printStackTrace();
17         }
18     }
19 }

這種方案與“方案三”類似,但不同的是,它不僅用於調用 JAX-WS 方式發佈的 WS,也用於使用 simple 方式發佈的 WS,更加智能了。

方案四:基於 CXF simple 方式的客戶端

01 package demo.ws.soap_cxf;
02  
03 import org.apache.cxf.frontend.ClientProxyFactoryBean;
04  
05 public class SimpleClient {
06  
07     public static void main(String[] args) {
08         ClientProxyFactoryBean factory = new ClientProxyFactoryBean();
09         factory.setAddress("http://localhost:8080/ws/soap/hello");
10         factory.setServiceClass(HelloService.class);
11         HelloService helloService = factory.create(HelloService.class);
12         String result = helloService.say("world");
13         System.out.println(result);
14     }
15 }

這種方式僅用於調用 simple 方式發佈的 WS,不能調用 JAX-WS 方式發佈的 WS,這是需要注意的。

談不上那種方案更加優秀,建議根據您的實際場景選擇最爲合適的方案。

5. 總結

通過閱讀本文,相信您已經大致瞭解了 CXF 的基本用法。可獨立使用,也可與 Spring 集成;可面向 API 來編程,也可使用 Spring 配置;發佈 WS 的方式有多種,調用 WS 的方式同樣也有多種。

尤其是 Spring + CXF 這對搭檔,讓發佈 WS 更加簡單,只需以下四個步驟:

  1. 配置 web.xml
  2. 編寫 WS 接口及其實現
  3. 配置 CXF 的 endpoint
  4. 啓動 Web 容器

當然,目前您看到的都是 CXF 在 SOAP 方面的特性,下期您將會體驗到輕量級的 WS —— RESTful Web Services,相信 CXF 在 REST 方面的能力一定不會讓您失望!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章