今天我们将视角集中在 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 规范目前有以下几种比较流行的实现技术:
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 数据的转换。
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 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 retrieveProductsByName_param(@QueryParam("name")String name);
/**
* 提交表单查询
* @param name
* @return
*/
@POST
@Path("/products/form/name")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
List 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 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 提供的注解,主要包括以下三类:
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 productList = new ArrayList();
static {
productList.add(new Product(1, "iphone63", 5000));
productList.add(new Product(2, "ipad mini", 2500));
}
@Override
public List retrieveAllProducts() {
Collections.sort(productList, new Comparator() {
@Override
public int compare(Product p1, Product p2) {
return (p1.getId() > p2.getId()) ? -1 : 1;
}
});
return productList;
}
@Override
public Product retrieveProductById(long id) {
Product targetProduct = null;
for (Product product : productList) {
if (product.getId() == id) {
targetProduct = product;
break;
}
}
return targetProduct;
}
@Override
public List retrieveProductsByName_param(String name){
List targetList = new ArrayList();
for (Product product : productList) {
if (product.getName().equals(name)) {
targetList.add(product);
}
}
return targetList;
}
@Override
public List retrieveProductsByName_form(String name) {
List targetList = new ArrayList();
for (Product product : productList) {
if (product.getName().equals(name)) {
targetList.add(product);
}
}
return targetList;
}
@Override
public Product createProduct(Product product) {
product.setId(new Date().getTime());
productList.add(product);
return product;
}
@Override
public Product updateProductById(long id, Map fieldMap) {
Product product = retrieveProductById(id);
if (product != null) {
try {
for (Map.Entry 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;
}
@Override
public 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;
}
@Override
public Product deleteProductById(long id) {
Product targetProduct = null;
Iterator it = productList.iterator();
while (it.hasNext()) {
Product product = it.next();
if (product.getId() == id) {
targetProduct = product;
it.remove();
break;
}
}
return targetProduct;
}
@Override
public Product deleteProductById_param(long id){
Product targetProduct = null;
Iterator it = productList.iterator();
while (it.hasNext()) {
Product product = it.next();
if (product.getId() == id) {
targetProduct = product;
it.remove();
break;
}
}
return targetProduct;
}
}
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) {
// 添加ResourceClass
List> resourceClassList = new ArrayList>();
resourceClassList.add(ProductServiceImpl.class);
// 添加ResourceProvider
List resourceProviderList = new ArrayList();
resourceProviderList.add(new SingletonResourceProvider(
new ProductServiceImpl()));
//添加provider
List
CXF 提供了一个名为 org.apache.cxf.jaxrs.JAXRSServerFactoryBean 的类,专用于发布 REST 服务,只需为该类的实例对象指定四个属性即可:
org.apache.cxf
cxf-rt-rs-client
${cxf.version}
CXF 提供了三种 REST 客户端,下面将分别进行展示。
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。
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 了。
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();
@Test
public void retrieveAll() {
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);
}
}
/**
* 该方法测试参数查询,
* 如果测试表单参数,则需要提交表单
*/
@Test
public void retrieveByName_param(){
List productList = ClientBuilder.newClient()
.register(jsonProvider)
.target(baseAddress)
.path("/products/name")
.queryParam("name", "iphone63")
.request(MediaType.APPLICATION_JSON)
.get(new GenericType>(){});
for (Product product : productList) {
System.out.println(product);
}
}
/**
* 添加数据
*/
@Test
public 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(){});
System.out.println(resultProduct);
retrieveAll();
}
/**
* 修改数据1
*/
@Test
public 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(){});
System.out.println(resultTarget);
System.out.println("==================");
retrieveAll();
}
/**
* 修改数据2
*/
@Test
public void update2(){
Map 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(){});
System.out.println(product);
System.out.println("===========================");
retrieveAll();
}
/**
* 路径参数
* 删除数据:根据id删除
*/
@Test
public void delete(){
Product product = ClientBuilder.newClient()
.register(jsonProvider)
.target(baseAddress)
.path("/product/1")
.request(MediaType.APPLICATION_JSON)
.delete(new GenericType(){});
System.out.println(product);
System.out.println("===============");
retrieveAll();
}
/**
* 查询参数
* 删除数据:根据id删除
*/
@Test
public void delete_param(){
Product product = ClientBuilder.newClient()
.register(jsonProvider)
.target(baseAddress)
.path("/product")
.queryParam("id", 2L)
.request(MediaType.APPLICATION_JSON)
.delete(new GenericType(){});
System.out.println(product);
System.out.println("=================");
retrieveAll();
}
}
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 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 productList = WebClient.create(baseAddress, providerList)
.path("/products").accept(MediaType.APPLICATION_JSON)
.get(new GenericType>(){});