使用 CXF 开发 REST 服务

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 规范目前有以下几种比较流行的实现技术:

Jersey:https://jersey.java.net/

Restlet:http://restlet.com/

RESTEasy:http://resteasy.jboss.org/

CXF:http://cxf.apache.org/

本文以 CXF 为例,我努力用最精炼的文字,让您快速学会如何使用 CXF 开发 REST 服务,此外还会将 Spring 与 CXF 做一个整合,让开发更加高效!

那么还等什么呢?咱们一起出发吧!

1. 使用 CXF 发布与调用 REST 服务

第一步:添加 Maven 依赖




    4.0.0

    demo.ws
    rest_cxf
    1.0-SNAPSHOT

    
        UTF-8
        3.0.0
        2.4.1
    

    
        
        
            org.apache.cxf
            cxf-rt-frontend-jaxrs
            ${cxf.version}
        
        
            org.apache.cxf
            cxf-rt-transports-http-jetty
            ${cxf.version}
        
        
        
            com.fasterxml.jackson.jaxrs
            jackson-jaxrs-json-provider
            ${jackson.version}
        
    

以上添加了 CXF 关于 REST 的依赖包,并使用了 Jackson 来实现 JSON 数据的转换。

第二步:定义一个 REST 服务接口

package demo.cxf.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 static javax.ws.rs.core.MediaType.*;

public interface ProductService {

    @GET
    @Path("/products")
    @Produces(APPLICATION_JSON)
    List retrieveAllProducts();

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

    @POST
    @Path("/products")
    @Consumes(APPLICATION_FORM_URLENCODED)
    @Produces(APPLICATION_JSON)
    List retrieveProductsByName(@FormParam("name") String name);

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

    @PUT
    @Path("/product/{id}")
    @Consumes(APPLICATION_JSON)
    @Produces(APPLICATION_JSON)
    Product updateProductById(@PathParam("id") long id, Map fieldMap);

    @DELETE
    @Path("/product/{id}")
    @Produces(APPLICATION_JSON)
    Product deleteProductById(@PathParam("id") long id);
}
以上 ProductService 接口中提供了一系列的方法,在每个方法上都使用了 JAX-RS 提供的注解,主要包括以下三类:

请求方式注解,包括:@GET、@POST、@PUT、@DELETE

请求路径注解,包括:@Path,其中包括一个路径参数

数据格式注解,包括:@Consumes(输入)、@Produces(输出),可使用 MediaType 常量

相关参数注解,包括:@PathParam(路径参数)、@FormParam(表单参数),此外还有 @QueryParam(请求参数)

针对 updateProductById 方法,简单解释一下:

该方法将被 PUT:/product/{id} 请求来调用,请求路径中的 id 参数将映射到 long id 参数上,请求体中的数据将自动转换为 JSON 格式并映射到 Map fieldMap 参数上,返回的 Product 类型的数据将自动转换为 JSON 格式并返回到客户端。

注意:由于 Product 类与 ProductService 接口的实现类并不是本文的重点,因此省略了,本文结尾处会给出源码链接。

第三步:使用 CXF 发布 REST 服务

package demo.cxf.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> resourceClassList = new ArrayList>();
        resourceClassList.add(HelloServiceImpl.class);
        resourceClassList.add(ProductServiceImpl.class);

        // 添加 ResourceProvider
        List resourceProviderList = new ArrayList();
        resourceProviderList.add(new SingletonResourceProvider(new HelloServiceImpl()));
        resourceProviderList.add(new SingletonResourceProvider(new ProductServiceImpl()));

        // 添加 Provider
        List providerList = new ArrayList();
        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 服务,只需为该类的实例对象指定四个属性即可: 
  

Address:REST 基础地址

ResourceClasses:一个或一组相关的资源类,即接口对应的实现类(注意:REST 规范允许资源类没有接口)

ResourceProviders:资源类对应的 Provider,此时使用 CXF 提供的 org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider 进行装饰

Providers:REST 服务所需的 Provider,此时使用了 Jackson 提供的 org.codehaus.jackson.jaxrs.JacksonJsonProvider,用于实现 JSON 数据的序列化与反序列化

运行以上 Server 类,将以 standalone 方式发布 REST 服务,下面我们通过客户端来调用以发布的 REST 服务。

第四步:使用 CXF 调用 REST 服务

首先添加如下 Maven 依赖:


    org.apache.cxf
    cxf-rt-rs-client
    ${cxf.version}
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 providerList = new ArrayList();
        providerList.add(new JacksonJsonProvider());

        ProductService productService = JAXRSClientFactory.create(baseAddress, ProductService.class, providerList);
        List 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,那么可以使用以下代码片段:
List productList = ClientBuilder.newClient()
    .register(jsonProvider)
    .target(baseAddress)
    .path("/products")
    .request(MediaType.APPLICATION_JSON)
    .get(new GenericType>() {});
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 providerList = new ArrayList();
        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,那么可以使用以下代码片段:

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

2. 使用 Spring + CXF 发布 REST 服务

第一步:添加 Maven 依赖



    4.0.0

    demo.ws
    rest_spring_cxf
    1.0-SNAPSHOT
    war

    
        UTF-8
        4.0.6.RELEASE
        3.0.0
        2.4.1
    

    
        
        
            org.springframework
            spring-web
            ${spring.version}
        
        
        
            org.apache.cxf
            cxf-rt-frontend-jaxrs
            ${cxf.version}
        
        
        
            com.fasterxml.jackson.jaxrs
            jackson-jaxrs-json-provider
            ${jackson.version}
        
    

这里仅依赖 Spring Web 模块(无需 MVC 模块),此外就是 CXF 与 Jackson 了。

第二步:配置 web.xml




    
    
        contextConfigLocation
        classpath:spring.xml
    
    
        org.springframework.web.context.ContextLoaderListener
    

    
    
        cxf
        org.apache.cxf.transport.servlet.CXFServlet
    
    
        cxf
        /ws/*
    

使用 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 的配置:



    

    

在以上配置中扫描 demo.ws 这个基础包路径,Spring 可访问该包中的所有 Spring Bean,比如,上面使用 @Component 注解发布的 ProductServiceImpl。此外,加载了另一个配置文件 spring-cxf.xml,其中包括了关于 CXF 的相关配置。
以下是 spring-cxf.xml 的配置:



    
        
            
        
        
            
        
    

使用 CXF 提供的 Spring 命名空间来配置 Service Bean(即上文提到的 Resource Class)与 Provider。注意,这里配置了一个 address 属性为“/rest”,表示 REST 请求的相对路径,与 web.xml 中配置的“/ws/*”结合起来,最终的 REST 请求根路径是“/ws/rest”,在 ProductService 接口方法上 @Path 注解所配置的路径只是一个相对路径。
第五步:调用 REST 服务


    
    Demo
    



Product List
使用一个简单的 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 依赖:

    org.apache.cxf
    cxf-rt-rs-extension-providers
    ${cxf.version}

然后,添加 CXF 配置:

    
        
    
    
        
        
    
    
        
    
    
        
    

注意:JsonpPreStreamInterceptor 一定要放在 中,而不是 中,这也许是 CXF 的一个 Bug,可以点击以下链接查看具体原因:

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

最后,使用 jQuery 发送基于 JSONP 的 AJAX 请求:

$.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);
    }
});
以上代码中有三个选项需要加以说明:
dataType:必须为“jsonp”,表示返回的数据类型为 JSONP 格式
jsonp:表示 URL 中 JSONP 回调函数的参数名,CXF 默认接收的参数名是“_jsonp”,也可以在 JsonpInInterceptor 中配置
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 依赖:

    org.apache.cxf
    cxf-rt-rs-security-cors
    ${cxf.version}
然后,添加 CXF 配置:

    
        
    
    
        
        
            
        
    
在 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

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

原文地址:https://my.oschina.net/huangyong/blog/294324

你可能感兴趣的:(Spring)