第一步、添加maven依赖
<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>
这里仅依赖 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>/WEB-INF/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 请求。
第三步、REST服务接口以及实现类
REST服务接口
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);
@POST
@Path("/product/create")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
Product createProduct(@FormParam("name")String name,@FormParam("price")int price);
@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);
@PUT
@Path("/product/update")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
Product updateProduct(@FormParam("id")long id,@FormParam("name")String name,@FormParam("price")int price);
@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);
}
接口所对应的实现类:
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));
}
@Override
public List<Product> retrieveAllProducts() {
Collections.sort(productList, new Comparator<Product>() {
@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<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;
}
@Override
public 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;
}
@Override
public Product createProduct(Product product) {
product.setId(new Date().getTime());
productList.add(product);
return product;
}
@Override
public Product createProduct(String name,int price){
Product product = new Product();
product.setId(new Date().getTime());
product.setName(name);
product.setPrice(price);
productList.add(product);
return product;
}
@Override
public 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;
}
@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 updateProduct(long id,String name,int price){
Product product = retrieveProductById(id);
if(product != null){
product.setName(name);
product.setPrice(price);
}
return product;
}
@Override
public 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;
}
@Override
public 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;
}
}
第四步、配置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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="productServiceImpl" class="com.test.rest.service.impl.ProductServiceImpl"></bean>
<import resource="spring-cxf.xml"/>
</beans>
将REST服务接口的实现类配置成SpringBean。
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-3.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="org.codehaus.jackson.jaxrs.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服务
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Demo</title>
<link href="http://localhost:8080/springCxfREST/css/bootstrap.min.css" rel="stylesheet">
<link href="http://localhost:8080/springCxfREST/css/colorbox.css" rel="stylesheet" type="text/css" />
</head>
<body>
<br>
<br>
<div class="container">
<form role="form" id="form_name">
<div class="form-group">
<label for="input_name">根据名称检索</label>
<input type="text" class="form-control" id="input_name" placeholder="名称" name="name"/>
</div>
<button id="submit_name" type="button" class="btn btn-default">检索</button>
</form>
<form role="form" id="form_create">
<div class="form-group">
<label for="input_create_name">名称</label>
<input type="text" class="form-control" id="input_create_name" placeholder="名称" name="name"/>
</div>
<div class="form-group">
<label for="input_create_price">价格</label>
<input type="text" class="form-control" id="input_create_price" placeholder="价格" name="price"/>
</div>
<button id="submit_create" type="button" class="btn btn-default">添加</button>
</form>
<div class="panel panel-default">
<button id="all" type="button" class="btn btn-default">RetrieveAll</button>
<button id="byID" type="button" class="btn btn-default">RetrieveByID</button>
<button id="name" type="button" class="btn btn-default">RetrieveByName</button>
</div>
<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://localhost:8080/springCxfREST/script/jquery.js" type="text/javascript"></script>
<script type="text/javascript" src="http://localhost:8080/springCxfREST/script/bootstrap.min.js"></script>
<script type="text/javascript" src="http://localhost:8080/springCxfREST/script/handlebars-v4.0.5.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() {
//检索所有
$("#all").click(function(){
$("#product").html("");
$.ajax({
type: 'get',
url: 'http://localhost:8080/springCxfREST/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);
}
});
});
//根据ID检索
$("#byID").click(function(){
$("#product").html("");
$.ajax({
type: 'get',
url: 'http://localhost:8080/springCxfREST/ws/rest/product/1',
dataType: 'json',
success: function(data) {
var template = $("#product_table_template").html();
var render = Handlebars.compile(template);
var html = render({
data: data
});
$('#product').html(html);
}
});
});
//根据名称检索
$("#name").click(function(){
$("#product").html("");
$.ajax({
type: 'get',
url: 'http://localhost:8080/springCxfREST/ws/rest/products/name?name=ipad mini',
dataType: 'json',
success: function(data) {
var template = $("#product_table_template").html();
var render = Handlebars.compile(template);
var html = render({
data: data
});
$('#product').html(html);
}
});
});
//提交表单,根据名称检索
$("#submit_name").click(function(){
$("#product").html("");
$.ajax({
type: 'post',
url: 'http://localhost:8080/springCxfREST/ws/rest/products/form/name?'+$("#form_name").serialize(),
dataType: 'json',
success: function(data) {
var template = $("#product_table_template").html();
var render = Handlebars.compile(template);
var html = render({
data: data
});
$('#product').html(html);
}
});
});
/* 提交表单,添加数据 */
$("#submit_create").click(function(){
$("#product").html("");
$.ajax({
type: 'post',
url: 'http://localhost:8080/springCxfREST/ws/rest/product/create?'+$("#form_create").serialize(),
dataType: 'json',
success: function(data) {
var template = $("#product_table_template").html();
var render = Handlebars.compile(template);
var html = render({
data: data
});
$('#product').html(html);
}
});
});
});
function goColorBox(){
$("#goOne").colorbox({ opacity:0.65,width: 500, height:500, close: "关闭", overlayClose: true});
}
</script>
</body>
</html>
使用一个简单的 HTML 页面来调用 REST 服务,也就是说,前端发送 AJAX 请求来调用后端发布的 REST 服务。这里使用了 jQuery、Bootstrap、Handlebars.js 等技术。
第六步、发布并访问
将项目部署到tomcat服务器中,启动tomcat服务器后,访问:http://localhost:8080/springCxfREST(项目名称)/index.jsp。
第七步、CXF对AJAX跨域的支持
AJAX跨域问题解决通常有两种方案,分别是JSONP和CORS,CXF分别对其都有很好的支持。
1、JSONP
添加JSONP依赖
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-extension-providers</artifactId>
<version>${cxf.version}</version>
</dependency>
修改spring中CXF的配置如下:
<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,可以点击以下链接查看具体原因:
最后,使用 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 自动生成
2、CORS
添加CORS依赖
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-security-cors</artifactId>
<version>${cxf.version}</version>
</dependency>
修改CXF在spring配置文件中的配置如下:
<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 特性。