1. 程式人生 > >使用 CXF 開發 SOAP 服務

使用 CXF 開發 SOAP 服務

轉自其它網頁,用來記下學習。

今天我們將視角集中在 REST 上,它是繼 SOAP 以後,另一種廣泛使用的 Web 服務。與 SOAP 不同,REST 並沒有 WSDL 的概念,也沒有叫做“信封”的東西,因為 REST 主張用一種簡單粗暴的方式來表達資料,傳遞的資料格式可以是 JSON 格式,也可以是 XML 格式,這完全由您來決定。

REST 全稱是 Representational State Transfer(表述性狀態轉移),它是 Roy Fielding 博士在 2000 年寫的一篇關於軟體架構風格的論文,此文一出,震撼四方!許多知名網際網路公司開始採用這種輕量級 Web 服務,大家習慣將其稱為 RESTful Web Services

 ,或簡稱 REST 服務 。

那麼 REST 到底是什麼呢?

REST 本質上是使用 URL 來訪問資源的一種方式。總所周知,URL 就是我們平常使用的請求地址了,其中包括兩部分: 請求方式 與 請求路徑 ,比較常見的請求方式是 GET 與 POST,但在 REST 中又提出了其它幾種其它型別的請求方式,彙總起來有六種:GET、POST、PUT、DELETE、HEAD、OPTIONS。尤其是前四種,正好與 CRUD(增刪改查)四種操作相對應:GET(查)、POST(增)、PUT(改)、DELETE(刪),這正是 REST 的奧妙所在!

實際上,REST 是一個“無狀態”的架構模式,因為在任何時候都可以由客戶端發出請求到服務端,最終返回自己想要的資料。也就是說,服務端將內部資源釋出 REST 服務,客戶端通過 URL 來訪問這些資源,這不就是 SOA 所提倡的“面向服務”的思想嗎?所以,REST 也被人們看做是一種輕量級的 SOA 實現技術,因此在企業級應用與網際網路應用中都得到了廣泛使用。

在 Java 的世界裡,有一個名為 JAX-RS 的規範,它就是用來實現 REST 服務的,目前已經發展到了 2.0 版本,也就是 JSR-339 規範,如果您想深入研究 REST,請深入閱讀此規範。

JAX-RS 規範目前有以下幾種比較流行的實現技術:

本文以 CXF 為例,我努力用最精煉的文字,讓您快速學會如何使用 CXF 開發 REST 服務,此外還會將 Spring 與 CXF 做一個整合,讓開發更加高效!

那麼還等什麼呢?咱們一起出發吧!

1. 使用 CXF 釋出與呼叫 REST 服務

第一步:新增 Maven 依賴

<?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>rest_cxf</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <cxf.version>3.0.0</cxf.version> <jackson.version>2.4.1</jackson.version> </properties> <dependencies> <!-- CXF --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxrs</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>${cxf.version}</version> </dependency> <!-- Jackson --> <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>${jackson.version}</version> </dependency> </dependencies> </project>

以上添加了 CXF 關於 REST 的依賴包,並使用了 Jackson 來實現 JSON 資料的轉換。

第二步:定義一個 REST 服務介面

package demo.ws.rest_cxf;

import java.util.List;
import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

public interface ProductService {

@GET
@Path("/products")
@Produces(MediaType.APPLICATION_JSON)
List<Product> retrieveAllProducts();

@GET
@Path("/product/{id}")
@Produces(MediaType.APPLICATION_JSON)
Product retrieveProductById(@PathParam("id") long id);

@POST
@Path("/products")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
List<Product> retrieveProductsByName(@FormParam("name") String name);

@POST
@Path("/product")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Product createProduct(Product product);

@PUT
@Path("/product/{id}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
Product updateProductById(@PathParam("id") long id, Map<String, Object> fieldMap);

@DELETE
@Path("/product/{id}")
@Produces(MediaType.APPLICATION_JSON)
Product deleteProductById(@PathParam("id") long id);
}

以上 ProductService 介面中提供了一系列的方法,在每個方法上都使用了 JAX-RS 提供的註解,主要包括以下三類:

  1. 請求方式註解,包括:@GET、@POST、@PUT、@DELETE
  2. 請求路徑註解,包括:@Path,其中包括一個路徑引數
  3. 資料格式註解,包括:@Consumes(輸入)、@Produces(輸出),可使用 MediaType 常量
  4. 相關引數註解,包括:@PathParam(路徑引數)、@FormParam(表單引數),此外還有 @QueryParam(請求引數)

針對 updateProductById 方法,簡單解釋一下:

該方法將被 PUT:/product/{id} 請求來呼叫,請求路徑中的 id 引數將對映到 long id 引數上,請求體中的資料將自動轉換為 JSON 格式並對映到 Map<String, Object> fieldMap 引數上,返回的 Product 型別的資料將自動轉換為 JSON 格式並返回到客戶端。

注意:由於 Product 類與 ProductService 介面的實現類並不是本文的重點,因此省略了,本文結尾處會給出原始碼連結。

第三步:使用 CXF 釋出 REST 服務

package demo.ws.rest_cxf;

import java.util.ArrayList;
import java.util.List;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.lifecycle.ResourceProvider;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;

public class Server {

    public static void main(String[] args) {
// 新增 ResourceClass
List<Class<?>> resourceClassList = new ArrayList<Class<?>>();
resourceClassList.add(ProductServiceImpl.class);

// 新增 ResourceProvider
List<ResourceProvider> resourceProviderList = new ArrayList<ResourceProvider>();
resourceProviderList.add(new SingletonResourceProvider(new ProductServiceImpl()));

// 新增 Provider
List<Object> providerList = new ArrayList<Object>();
providerList.add(new JacksonJsonProvider());

// 釋出 REST 服務
JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean();
factory.setAddress("http://localhost:8080/ws/rest");
factory.setResourceClasses(resourceClassList);
factory.setResourceProviders(resourceProviderList);
factory.setProviders(providerList);
factory.create();
System.out.println("rest ws is published");
    }
}

CXF 提供了一個名為 org.apache.cxf.jaxrs.JAXRSServerFactoryBean 的類,專用於釋出 REST 服務,只需為該類的例項物件指定四個屬性即可:

  1. Address:REST 基礎地址
  2. ResourceClasses:一個或一組相關的資源類,即介面對應的實現類(注意:REST 規範允許資源類沒有介面)
  3. ResourceProviders:資源類對應的 Provider,此時使用 CXF 提供的 org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider 進行裝飾
  4. Providers:REST 服務所需的 Provider,此時使用了 Jackson 提供的 org.codehaus.jackson.jaxrs.JacksonJsonProvider ,用於實現 JSON 資料的序列化與反序列化

執行以上 Server 類,將以 standalone 方式釋出 REST 服務,下面我們通過客戶端來呼叫以釋出的 REST 服務。

第四步:使用 CXF 呼叫 REST 服務

首先新增如下 Maven 依賴:

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-rs-client</artifactId>
    <version>${cxf.version}</version>
</dependency>

CXF 提供了三種 REST 客戶端,下面將分別進行展示。

第一種:JAX-RS 1.0 時代的客戶端

package demo.ws.rest_cxf;

import java.util.ArrayList;
import java.util.List;
import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;

public class JAXRSClient {

    public static void main(String[] args) {
String baseAddress = "http://localhost:8080/ws/rest";

List<Object> providerList = new ArrayList<Object>();
providerList.add(new JacksonJsonProvider());

ProductService productService = JAXRSClientFactory.create(baseAddress, ProductService.class, providerList);
List<Product> productList = productService.retrieveAllProducts();
for (Product product : productList) {
    System.out.println(product);
}
    }
}

本質是使用 CXF 提供的 org.apache.cxf.jaxrs.client.JAXRSClientFactory工廠類來建立 ProductService 代理物件,通過代理物件呼叫目標物件上的方法。客戶端同樣也需要使用 Provider,此時仍然使用了 Jackson 提供的 org.codehaus.jackson.jaxrs.JacksonJsonProvider 。

第二種:JAX-RS 2.0 時代的客戶端

package demo.ws.rest_cxf;

import java.util.List;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;

public class JAXRS20Client {

public static void main(String[] args) {
String baseAddress = "http://localhost:8080/ws/rest";

JacksonJsonProvider jsonProvider = new JacksonJsonProvider();

List productList = ClientBuilder.newClient()
.register(jsonProvider)
.target(baseAddress)
.path("/products")
.request(MediaType.APPLICATION_JSON)
.get(List.class);
for (Object product : productList) {
System.out.println(product);
}
}
}

在 JAX-RS 2.0 中提供了一個名為 javax.ws.rs.client.ClientBuilder 的工具類,可用於建立客戶端並呼叫 REST 服務,顯然這種方式比前一種要先進,因為在程式碼中不再依賴 CXF API 了。

如果想返回帶有泛型的 List<Product> ,那麼可以使用以下程式碼片段:

List<Product> productList = ClientBuilder.newClient()
.register(jsonProvider)
.target(baseAddress)
.path("/products")
.request(MediaType.APPLICATION_JSON)
.get(new GenericType<List<Product>>() {});
for (Product product : productList) {
System.out.println(product);
}

第三種:通用的 WebClient 客戶端

package demo.ws.rest_cxf;

import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import org.apache.cxf.jaxrs.client.WebClient;
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;

public class CXFWebClient {

public static void main(String[] args) {
String baseAddress = "http://localhost:8080/ws/rest";

List<Object> providerList = new ArrayList<Object>();
providerList.add(new JacksonJsonProvider());

List productList = WebClient.create(baseAddress, providerList)
.path("/products")
.accept(MediaType.APPLICATION_JSON)
.get(List.class);
for (Object product : productList) {
System.out.println(product);
}
}
}

CXF 還提供了一種更為簡潔的方式,使用 org.apache.cxf.jaxrs.client.WebClient 來呼叫 REST 服務,這種方式在程式碼層面上還是相當簡潔的。

如果想返回帶有泛型的 List<Product> ,那麼可以使用以下程式碼片段:

List<Product> productList = WebClient.create(baseAddress, providerList)
    .path("/products")
    .accept(MediaType.APPLICATION_JSON)
    .get(new GenericType<List<Product>>() {});
for (Product product : productList) {
    System.out.println(product);
}

2. 使用 Spring + CXF 釋出 REST 服務

第一步:新增 Maven 依賴

<?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>rest_spring_cxf</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.0.6.RELEASE</spring.version>
<cxf.version>3.0.0</cxf.version>
<jackson.version>2.4.1</jackson.version>
</properties>

<dependencies>
<!-- Spring -->
<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-jaxrs</artifactId>
<version>${cxf.version}</version>
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>

</project>

這裡僅依賴 Spring Web 模組(無需 MVC 模組),此外就是 CXF 與 Jackson 了。

第二步:配置 web.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>

使用 Spring 提供的 ContextLoaderListener 去載入 Spring 配置檔案 spring.xml;使用 CXF 提供的 CXFServlet 去處理字首為 /ws/ 的 REST 請求。

第三步:將介面的實現類釋出 SpringBean

package demo.ws.rest_spring_cxf;

import org.springframework.stereotype.Component;

@Component
public class ProductServiceImpl implements ProductService {
    ...
}

使用 Spring 提供的 @Component 註解,將 ProductServiceImpl 釋出為 Spring Bean,交給 Spring IOC 容器管理,無需再進行 Spring XML 配置。

第四步:配置 Spring

以下是 spring.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 這個基礎包路徑,Spring 可訪問該包中的所有 Spring Bean,比如,上面使用 @Component 註解釋出的 ProductServiceImpl 。此外,載入了另一個配置檔案 spring-cxf.xml,其中包括了關於 CXF 的相關配置。

以下是 spring-cxf.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:jaxrs="http://cxf.apache.org/jaxrs"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd">

    <jaxrs:server address="/rest">
 <jaxrs:serviceBeans>
     <ref bean="productServiceImpl"/>
 </jaxrs:serviceBeans>
 <jaxrs:providers>
     <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
 </jaxrs:providers>
    </jaxrs:server>

</beans>

使用 CXF 提供的 Spring 名稱空間來配置 Service Bean(即上文提到的 Resource Class)與 Provider。注意,這裡配置了一個 address 屬性為“/rest”,表示 REST 請求的相對路徑,與 web.xml 中配置的“/ws/*”結合起來,最終的 REST 請求根路徑是“/ws/rest”,在 ProductService 介面方法上@Path註解所配置的路徑只是一個相對路徑。

第五步:呼叫 REST 服務

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Demo</title>
<link href="http://cdn.bootcss.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<div class="container">
<div class="page-header">
<h1>Product</h1>
</div>
<div class="panel panel-default">
<div class="panel-heading">Product List</div>
<div class="panel-body">
<div id="product"></div>
</div>
</div>
</div>

<script src="http://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<script src="http://cdn.bootcss.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="http://cdn.bootcss.com/handlebars.js/1.3.0/handlebars.min.js"></script>

<script type="text/x-handlebars-template" id="product_table_template">
{{#if data}}
<table class="table table-hover" id="product_table">
<thead>
<tr>
<th>ID</th>
<th>Product Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{{#data}}
<tr data-id="{{id}}" data-name="{{name}}">
<td>{{id}}</<