CXF系列之JAX-RS:CXF发布与调用REST服务

来源:互联网 发布:徐州淘宝打包员招聘 编辑:程序博客网 时间:2024/06/05 05:37

今天我们将视角集中在 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 规范目前有以下几种比较流行的实现技术:

  • Jersey:https://jersey.java.net/
  • Restlet:http://restlet.com/
  • RESTEasy:http://resteasy.jboss.org/
  • CXF:http://cxf.apache.org/

第一步、添加相关jar包

 <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>
以上添加了 CXF 关于 REST 的依赖包,并使用了 Jackson 来实现 JSON 数据的转换。

第二步、定义REEST服务接口以及具体实现类

package com.test.rest.service.inter;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.QueryParam;import javax.ws.rs.core.MediaType;import com.test.rest.bean.Product;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);/** * 参数查询 * @param name * @return */@GET@Path("/products/name")@Produces(MediaType.APPLICATION_JSON)List<Product> retrieveProductsByName_param(@QueryParam("name")String name);/** * 提交表单查询 * @param name * @return */@POST@Path("/products/form/name")@Consumes(MediaType.APPLICATION_FORM_URLENCODED)@Produces(MediaType.APPLICATION_JSON)List<Product> retrieveProductsByName_form(@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);@PUT@Path("/product")@Consumes(MediaType.APPLICATION_JSON)@Produces(MediaType.APPLICATION_JSON)Product updateProduct(Product product);@DELETE@Path("/product/{id}")@Produces(MediaType.APPLICATION_JSON)Product deleteProductById(@PathParam("id")long id);@DELETE@Path("/product")@Produces(MediaType.APPLICATION_JSON)Product deleteProductById_param(@QueryParam("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 格式并返回到客户端。

具体实现类:
package com.test.rest.service.impl;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.Collections;import java.util.Comparator;import java.util.Date;import java.util.Iterator;import java.util.List;import java.util.Map;import com.test.rest.bean.Product;import com.test.rest.service.inter.ProductService;public class ProductServiceImpl implements ProductService {private static final List<Product> productList = new ArrayList<Product>();static {productList.add(new Product(1, "iphone63", 5000));productList.add(new Product(2, "ipad mini", 2500));}@Overridepublic List<Product> retrieveAllProducts() {Collections.sort(productList, new Comparator<Product>() {@Overridepublic int compare(Product p1, Product p2) {return (p1.getId() > p2.getId()) ? -1 : 1;}});return productList;}@Overridepublic Product retrieveProductById(long id) {Product targetProduct = null;for (Product product : productList) {if (product.getId() == id) {targetProduct = product;break;}}return targetProduct;}@Overridepublic List<Product> retrieveProductsByName_param(String name){List<Product> targetList = new ArrayList<Product>();for (Product product : productList) {if (product.getName().equals(name)) {targetList.add(product);}}return targetList;}@Overridepublic List<Product> retrieveProductsByName_form(String name) {List<Product> targetList = new ArrayList<Product>();for (Product product : productList) {if (product.getName().equals(name)) {targetList.add(product);}}return targetList;}@Overridepublic Product createProduct(Product product) {product.setId(new Date().getTime());productList.add(product);return product;}@Overridepublic Product updateProductById(long id, Map<String, Object> fieldMap) {Product product = retrieveProductById(id);if (product != null) {try {for (Map.Entry<String, Object> fieldEntry : fieldMap.entrySet()) {Field field = Product.class.getDeclaredField(fieldEntry.getKey());field.setAccessible(true);field.set(product, fieldEntry.getValue());}} catch (Exception e) {e.printStackTrace();}}return product;}@Overridepublic Product updateProduct(Product product){if(product != null){Product targetProduct = retrieveProductById(product.getId());if(targetProduct != null){targetProduct.setName(product.getName());targetProduct.setPrice(product.getPrice());}return targetProduct;}return null;}@Overridepublic Product deleteProductById(long id) {Product targetProduct = null;Iterator<Product> it = productList.iterator();while (it.hasNext()) {Product product = it.next();if (product.getId() == id) {targetProduct = product;it.remove();break;}}return targetProduct;}@Overridepublic Product deleteProductById_param(long id){Product targetProduct = null;Iterator<Product> it = productList.iterator();while (it.hasNext()) {Product product = it.next();if (product.getId() == id) {targetProduct = product;it.remove();break;}}return targetProduct;}}

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

package com.test.rest.server;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;import com.test.rest.service.impl.ProductServiceImpl;public class Server {public static void main(String[] args) {// 添加ResourceClassList<Class<?>> resourceClassList = new ArrayList<Class<?>>();resourceClassList.add(ProductServiceImpl.class);// 添加ResourceProviderList<ResourceProvider> resourceProviderList = new ArrayList<ResourceProvider>();resourceProviderList.add(new SingletonResourceProvider(new ProductServiceImpl()));//添加providerList<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 com.test.rest.client;import java.util.List;import javax.ws.rs.client.ClientBuilder;import javax.ws.rs.core.GenericType;import javax.ws.rs.core.MediaType;import org.codehaus.jackson.jaxrs.JacksonJsonProvider;public class Client {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>,那么就该使用如下方式。
JAX-RS 2.0 时代带有泛型的客户端
package com.test.rest.client;import java.util.HashMap;import java.util.List;import java.util.Map;import javax.ws.rs.client.ClientBuilder;import javax.ws.rs.client.Entity;import javax.ws.rs.core.GenericType;import javax.ws.rs.core.MediaType;import org.codehaus.jackson.jaxrs.JacksonJsonProvider;import org.junit.Test;import com.test.rest.bean.Product;/** * 返回带有泛型的数据 * @author Administrator * */public class Client1 {String baseAddress = "http://localhost:8080/ws/rest";JacksonJsonProvider jsonProvider = new JacksonJsonProvider();@Testpublic void retrieveAll() {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);}}/** * 该方法测试参数查询, * 如果测试表单参数,则需要提交表单 */@Testpublic void retrieveByName_param(){List<Product> productList = ClientBuilder.newClient().register(jsonProvider).target(baseAddress).path("/products/name").queryParam("name", "iphone63").request(MediaType.APPLICATION_JSON).get(new GenericType<List<Product>>(){});for (Product product : productList) {System.out.println(product);}}/** * 添加数据 */@Testpublic void create(){Product product = new Product();product.setName("iphone7");product.setPrice(6000);Product resultProduct = ClientBuilder.newClient().register(jsonProvider).target(baseAddress).path("/product").request(MediaType.APPLICATION_JSON).post(Entity.entity(product, MediaType.APPLICATION_JSON), new GenericType<Product>(){});System.out.println(resultProduct);retrieveAll();}/** * 修改数据1 */@Testpublic void update(){Product product = new Product();product.setId(1L);product.setName("iphone6s");product.setPrice(4000);Product resultTarget = ClientBuilder.newClient().register(jsonProvider).target(baseAddress).path("/product").request(MediaType.APPLICATION_JSON).put(Entity.entity(product, MediaType.APPLICATION_JSON), new GenericType<Product>(){});System.out.println(resultTarget);System.out.println("==================");retrieveAll();}/** * 修改数据2 */@Testpublic void update2(){Map<String,Object> fieldParam = new HashMap<>();fieldParam.put("name", "ipad mini");fieldParam.put("price", 1999);Product product = ClientBuilder.newClient().register(jsonProvider).target(baseAddress).path("/product/2").request(MediaType.APPLICATION_JSON).put(Entity.entity(fieldParam, MediaType.APPLICATION_JSON), new GenericType<Product>(){});System.out.println(product);System.out.println("===========================");retrieveAll();}/** * 路径参数 * 删除数据:根据id删除 */@Testpublic void delete(){Product product = ClientBuilder.newClient().register(jsonProvider).target(baseAddress).path("/product/1").request(MediaType.APPLICATION_JSON).delete(new GenericType<Product>(){});System.out.println(product);System.out.println("===============");retrieveAll();}/** * 查询参数 * 删除数据:根据id删除 */@Testpublic void delete_param(){Product product = ClientBuilder.newClient().register(jsonProvider).target(baseAddress).path("/product").queryParam("id", 2L).request(MediaType.APPLICATION_JSON).delete(new GenericType<Product>(){});System.out.println(product);System.out.println("=================");retrieveAll();}}

第三种:通用的WebClient客户端
package com.test.rest.client;import java.util.ArrayList;import java.util.List;import javax.ws.rs.core.MediaType;import org.apache.cxf.jaxrs.client.WebClient;import org.codehaus.jackson.jaxrs.JacksonJsonProvider;/** * 通用客户端WebClient的实现 *  * @author Administrator *  */public class Client3 {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>>(){});














0 0