CXF系列之JAX-WS:SOAP服務
阿新 • • 發佈:2019-01-04
文章轉自:https://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 檔案進行配置:
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);
}
實現部分:注意:介面與實現類上都標註 javax.jws.WebService 註解,可在實現類的註解中新增一些關於 WS 的相關資訊,例如:serviceName、portName 等,當然這是可選的,為了讓生成的 WSDL 的可讀性更加強而已。<!-- 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; } }
第三步:在 WEB-INF 下新增 sun-jaxws.xml 檔案
就是在這個 sun-jaxws.xml 檔案裡配置需要釋出的 WS,其內容如下:
這裡僅釋出一個 endpoint,並配置三個屬性:WS 的名稱、實現類、URL 模式。正是通過這個“URL 模式”來訪問 WSDL 的,馬上您就可以看到。<!-- 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>
第四步:部署應用並啟動 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
如果不出意外的話,您現在應該可以看到如下介面了:
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 使用者,那麼下面這段配置相信一定不會陌生:
<!-- 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 容器中的呢?見證奇蹟的時候到了!
3. 在 Web 容器中使用 Spring + CXF 釋出 WS
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>
以上配置做了兩件事情:定義 IOC 容器掃描路徑,即這裡定義的 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 控制檯
通過以上過程,可以看出 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 客戶端解決方案,根據您的口味自行選擇吧!
4. 關於 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);
}
}
談不上那種方案更加優秀,建議根據您的實際場景選擇最為合適的方案。5. 總結
通過閱讀本文,相信您已經大致瞭解了 CXF 的基本用法。可獨立使用,也可與 Spring 整合;可面向 API 來程式設計,也可使用 Spring 配置;釋出 WS 的方式有多種,呼叫 WS 的方式同樣也有多種。
尤其是 Spring + CXF 這對搭檔,讓釋出 WS 更加簡單,只需以下四個步驟:
配置 web.xml
編寫 WS 介面及其實現
配置 CXF 的 endpoint
啟動 Web 容器