Web Service 那點事兒(4)—— 使用 CXF 開發 REST 服務 原 薦

現在您已經學會了如何使用 CXF 開發基於 SOAP 的 Web 服務,也領略了 Spring + CXF 這個強大的組合,如果您錯過了這精彩的一幕,請回頭看看這篇吧:

Web Service 那點事兒(2) —— 使用 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 依賴

<!-- 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>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 服務接口

<!-- lang: java -->
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 服務

<!-- lang: java -->
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 依賴:

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

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

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

<!-- lang: java -->
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 時代的客戶端

<!-- lang: java -->
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>,那麼可以使用以下代碼片段:

<!-- lang: java -->
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 客戶端

<!-- lang: java -->
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>,那麼可以使用以下代碼片段:

<!-- lang: java -->
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 依賴

<!-- 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>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

<!-- 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>

使用 Spring 提供的 ContextLoaderListener 去加載 Spring 配置文件 spring.xml;使用 CXF 提供的 CXFServlet 去處理前綴爲 /ws/ 的 REST 請求。

第三步:將接口的實現類發佈 SpringBean

<!-- lang: java -->
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 的配置:

<!-- 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 這個基礎包路徑,Spring 可訪問該包中的所有 Spring Bean,比如,上面使用 @Component 註解發佈的 ProductServiceImpl。此外,加載了另一個配置文件 spring-cxf.xml,其中包括了關於 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: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 服務

<!-- lang: html -->
<!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}}</td>
                        <td>{{name}}</td>
                        <td>{{price}}</td>
                    </tr>
                {{/data}}
            </tbody>
        </table>
    {{else}}
        <div class="alert alert-warning">Can not find any data!</div>
    {{/if}}
</script>

<script>
    $(function() {
        $.ajax({
            type: 'get',
            url: 'http://localhost:8080/ws/rest/products',
            dataType: 'json',
            success: function(data) {
                var template = $("#product_table_template").html();
                var render = Handlebars.compile(template);
                var html = render({
                    data: data
                });
                $('#product').html(html);
            }
        });
    });
</script>

</body>
</html>

使用一個簡單的 HTML 頁面來調用 REST 服務,也就是說,前端發送 AJAX 請求來調用後端發佈的 REST 服務。這裏使用了 jQuery、Bootstrap、Handlebars.js 等技術。

3. 關於 AJAX 的跨域問題

如果服務端部署在 foo.com 域名下,而客戶端部署在 bar.com 域名下,此時從 bar.com 發出一個 AJAX 的 REST 請求到 foo.com,就會出現報錯:

No 'Access-Control-Allow-Origin' header is present on the requested resource.

要想解決以上這個 AJAX 跨域問題,有以下兩種解決方案:

方案一:使用 JSONP 解決 AJAX 跨域問題

JSONP 的全稱是 JSON with Padding,實際上是在需要返回的 JSON 數據外,用一個 JS 函數進行封裝。

可以這樣來理解,服務器返回一個 JS 函數,參數是一個 JSON 數據,例如:callback({您的 JSON 數據}),雖然 AJAX 不能跨域訪問,但 JS 腳本是可以跨域執行的,因此客戶端將執行這個 callback 函數,並獲取其中的 JSON 數據。

如果需要返回的 JSON 數據是:

{"id":2,"name":"ipad mini","price":2500},{"id":1,"name":"iphone 5s","price":5000}

那麼對應的 JSONP 格式是:

callback([{"id":2,"name":"ipad mini","price":2500},{"id":1,"name":"iphone 5s","price":5000}]);

CXF 已經提供了對 JSONP 的支持,只需要通過簡單的配置即可實現。

首先,添加 Maven 依賴:

<!-- lang: xml -->
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-rs-extension-providers</artifactId>
    <version>${cxf.version}</version>
</dependency>

然後,添加 CXF 配置:

<!-- lang: xml -->
<jaxrs:server address="/rest">
    <jaxrs:serviceBeans>
        <ref bean="productServiceImpl"/>
    </jaxrs:serviceBeans>
    <jaxrs:providers>
        <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
        <bean class="org.apache.cxf.jaxrs.provider.jsonp.JsonpPreStreamInterceptor"/>
    </jaxrs:providers>
    <jaxrs:inInterceptors>
        <bean class="org.apache.cxf.jaxrs.provider.jsonp.JsonpInInterceptor"/>
    </jaxrs:inInterceptors>
    <jaxrs:outInterceptors>
        <bean class="org.apache.cxf.jaxrs.provider.jsonp.JsonpPostStreamInterceptor"/>
    </jaxrs:outInterceptors>
</jaxrs:server>

注意:JsonpPreStreamInterceptor 一定要放在 <jaxrs:providers> 中,而不是 <jaxrs:inInterceptors> 中,這也許是 CXF 的一個 Bug,可以點擊以下鏈接查看具體原因:

http://cxf.547215.n5.nabble.com/JSONP-is-not-works-td5739858.html

最後,使用 jQuery 發送基於 JSONP 的 AJAX 請求:

<!-- lang: js -->
$.ajax({
    type: 'get',
    url: 'http://localhost:8080/ws/rest/products',
    dataType: 'jsonp',
    jsonp: '_jsonp',
    jsonpCallback: 'callback',
    success: function(data) {
        var template = $("#product_table_template").html();
        var render = Handlebars.compile(template);
        var html = render({
            data: data
        });
        $('#product').html(html);
    }
});

以上代碼中有三個選項需要加以說明:

  1. dataType:必須爲“jsonp”,表示返回的數據類型爲 JSONP 格式
  2. jsonp:表示 URL 中 JSONP 回調函數的參數名,CXF 默認接收的參數名是“_jsonp”,也可以在 JsonpInInterceptor 中配置
  3. jsonpCallback:表示回調函數的名稱,若未指定,則由 jQuery 自動生成

方案二:使用 CORS 解決 AJAX 跨域問題

CORS 的全稱是 Cross-Origin Resource Sharing(跨域資源共享),它是 W3C 提出的一個 AJAX 跨域訪問規範,可以從以下地址瞭解此規範:

http://www.w3.org/TR/cors/

相比 JSONP 而言,CORS 更爲強大,因爲它彌補了 JSONP 只能處理 GET 請求的侷限性,但是隻有較爲先進的瀏覽器才能全面支持 CORS。

CXF 同樣也提供了對 CORS 的支持,通過簡單的配置就能實現。

首先,添加 Maven 依賴:

<!-- lang: xml -->
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-rs-security-cors</artifactId>
    <version>${cxf.version}</version>
</dependency>

然後,添加 CXF 配置:

<!-- lang: xml -->
<jaxrs:server address="/rest">
    <jaxrs:serviceBeans>
        <ref bean="productServiceImpl"/>
    </jaxrs:serviceBeans>
    <jaxrs:providers>
        <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
        <bean class="org.apache.cxf.rs.security.cors.CrossOriginResourceSharingFilter">
            <property name="allowOrigins" value="http://localhost"/>
        </bean>
    </jaxrs:providers>
</jaxrs:server>

CrossOriginResourceSharingFilter 中配置 allowOrigins 屬性,將其設置爲客戶端的域名,示例中爲“http://localhost”,需根據實際情況進行設置。

最後,使用 jQuery 發送 AJAX 請求:

就像在相同域名下訪問一樣,無需做任何配置。

注意:在 IE8 中使用 jQuery 發送 AJAX 請求時,需要配置 $.support.cors = true,才能開啓 CORS 特性。

4. 總結

本文讓您學會了如何使用 CXF 發佈 REST 服務,可以獨立使用 CXF,也可以與 Spring 集成。此外,CXF 也提供了一些解決方案,用於實現跨域 AJAX 請求,比如:JSONP 或 CORS。CXF 3.0 以全面支持 JAX-RS 2.0 規範,有很多實用的功能需要您進一步學習,可以點擊以下地址:

http://cxf.apache.org/docs/jax-rs.html

目前您所看到的 REST 請求沒有任何的身份認證,這樣是很不安全的,也就意味着任何人只要知道了 REST 地址就能調用。我們知道 SOAP 裏有 WS-Security 規範,可以使用 WSS4J 來做 SOAP 安全,那麼關於 REST 安全我們應該如何保證呢?下一篇將爲您揭曉,敬請期待!

源碼地址:http://git.oschina.net/huangyong/cxf_demo

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