CXF发布RSETful 风格WebService?
1、关于RestFul的介绍
简介
REST也就是Representational State Transfer。REST并不特指一种技术,也不是一个标准,它仅仅是一个构架风格。REST?指的是一组架构约束条件和原则。满足这些约束条件和原则通过网络暴露资源给用户。事实上,WWW就是经典的REST架构风格。在服务器端,应用程序状态和功能可以分为各种资源。它向客户端公开。每个资源都使用?URI (Universal Resource Identifier)?得到一个惟一的地址。所有资源都共享统一的界面,以便在客户端和服务器之间传输状态。使用的是标准的HTTP?方法,比如?GET、PUT、POST?和?DELETE。客户端通过交换各种资源的表现体来查询或更新资源。资源的表现体包括HTML,XML,JSON等。客户端需要解析响应服务器的表现体。客户端和服务器之间的交互在请求之间是无状态的,无状态请求可以由任何可用服务器应答。
下面是一些RESTfu例子,提供雇员和部门的信息,并介绍客户端怎样访问这些服务
URI for the RESTful service—http://<host>/department/deptname/employee:
??????GET—获得deptname部门的所有员工信息
??????POST—为deptname部门创建员工信息。
??????DELETE—删除?deptname部门一名员工信息
URI for the RESTful service—http://<host>/department/deptname/employee/naveen:
??????GET—获得deptname部门名叫naveen的员工信息
??????PUT—为deptname部门创建名叫naveen的员工信息
??????DELETE—删除deptname部门名叫naveen的员工信息
?
下面是POST请求的例子http://<host>/department/deptname/employee
?
?
POST /department/deptname/employee HTTP/1.1
Content-Type: */*
Accept: application/xml
User-Agent: Apache CXF 2.2.2
Cache-Control: no-cache
Pragma: no-cache
Host: 127.0.0.1:9001
<employee><firstname>rajeev</firstname><lastname>hathi</lastname>
<dob>10/26/78</dob></employee>
?
Java API for RESTful services
?
上一节,我们了解雇员POST请求。如果需要提供一种实现去识别雇员POST请求,我们应该做如下工作:
?
识别这是否一个HTTP POST请求。
将HTTP POST请求中的XML的内容转换为实现端所需要的格式,例如JAVA对象。
执行指定的操作,例如插入雇员信息到数据库。
以HTTP形式响应客户端,例如设置标志响应成功的HTTP状态200?,并将响应转换到指定格式(XML或JSON),最后将其设置到HTTP Body。
?
依据需求,可能你要实现所有的HTTP方法,如GET,PUT,DELETE等。这不就是标准的RESTful JAVA开发模式吗?接着,Java API for RESTful Web services (JAX-RS)规范制定了开发RESTful的标准。
?
JAX-RS规范定义了创建RESTful服务的语法。JAX-RS使用annotations注解在实现RESTful的服务,使用annotations注解POJO将它暴露为RESTful资源。RESTful服务类中,通过URI(/category)和HTTP(GET, POST)方法注解方法。
?
@GET
???????@Path("/category/{id}")
???????public Category getCategory(@PathParam("id") String id)
?
实现JAX-RS的框架在运行的时候,通过映射HTTP请求到RESTful方法,负责调用正确的JAVA实现方法。JAX-RS规范提供这种映射的方法的算法。基础算法包括,判断JAVA资源类,判断HTTP URI请求,判断内容格式(例如application/xml),还有HTTP方法(例如GET)
?
JAX-RS规范提出如下要点:
?
POJO依赖性
为了暴露为资源,使用annotations注解POJO,
?
HTTP依赖性
RESTful资源暴露在HTTP中,规范中将HTTP?协议和JAX-RS API相对应映射。
?
格式独立性
API中提供嵌入式的方法,标准化添加HTTP内容的类型。例如,application/xml就是HTTP内容的类型中的一种。
?
容器独立性
可以在任何一种容器中部署实现JAX-RS规范的应用程序。
?
在各个系统交互领域,Web services逐渐成为主流。有两种主要方式来开发Web Services:Simple Object Access Protocol (SOAP)和Representational State Transfer (REST)
?
开发基于SOAP的Web Services需要很多的约束,?以便客户端和服务端交互信息,例如,使用Web Service Description Language (WSDL)来描述信息。还有很多WS的标准如WS-Security。
?
使用REST构架的服务被称为RESTful服务。这种架构利用简单的XML在HTTP协议上,就像网页一样发送请求,简化了基于SOAP架构开发。RESTful Web Services尤其适用于只需要提交和接受简单的XML信息。
?
使用CXF开发RESTFul服务
接下来会介绍使用CXF框架来开发RESTful风格的Web Services。
?参考质料:http://cxf.apache.org/docs/restful-services.html
简介
Java API for RESTful services
CXF JAX-RS实现
开发RESTful服务
CXF JAX-RS实现
CXF实现了JAX-RS1.0规范,并提供了很多特性帮助开发者搭建企业级的RESTful服务。
下面是CXF框架提供创建RESTful服务的各种特性。
集成Spring
Spring框架已经变成事实上的构建企业级JAVA应用程序集成框架。CXF提供与Spring整合,简化了配置和部署RESTful应用程序。Spring提供依赖注入促进松散耦合,提供各种服务,像声明式事务管理。所有这些Spring提供的特性都可以被开发RESTful服务的CXF框架使用。
?
插入式数据绑定
数据库绑定就是映射HTTP请求,例如JSON或XML,到对应的JAVA对象。同样的,在发送HTTP响应之前,服务端的JAVA实现需要映射为客户端所需要的格式。通过提供数据库绑定组件,CXF在后台透明的处理映射。CXF支持各种数据绑定机制,如JAXB,JSON,XMLBean和Aegis。CXF允许指定特定的绑定机制。
?
客户端API
JAX-RS规范并没有提供客户端调用REST服务的API。CXF提供了这种API直接调用RESTful服务,也可以使用Spring框架配置到应用程序中。
?
安全
CXF可以使用Spring框架集成的声明式安全组件,按照应用程序的需要限制资源类和方法,而不必使用代码处理安全性问题。
?
过滤器
过滤器用来预处理或后处理信息。CXF可以创建和配置过滤器来审核信息,记录信息的日志,还有基于应用要求修改请求或响应。
?
?
?
CXF也运行开发者使用JAX-WS Provider和Dispatch API来创建RESTful服务。
?
开发RESTful服务
?
本节将介绍使用JAX-RS实现的方法来开发RESTful服务,并执行CRUD操作。首先看一下Book Shop应用。
?
Book Shop应用是一款网络应用,提供技术书籍(JAVA或.NET)的分类。Book Shop让管理员为新的书籍创建分类,修改分类等。一旦这个分类存在,应用可以为这个分类添加新的书籍。
?
这个应用将提供如下方法:
创建分类
更新分类
删除分类
获取分类列表
获取特定分类
为特定分类添加书籍
获取特定分类中所有书籍
?
?
为了开发RESTful服务,需要做如下工作:
- ?创建POJO类
- 为POJO类提供数据绑定
- 创建实现RESTful功能的服务类
- 创建调用RESTful服务的客户端
2、开始建立项目
新建一个web项目“RestFul”,将之前的apache的CXF项目下的lib里所有的jar包导入项目(为避免缺少包,因为我们只是用来学习而已)
1、创建POJO类
package com.xlk.restful.domain; import javax.xml.bind.annotation.XmlRootElement; /** * * book实体类 </br> * 类名: Book </br> * 日期: 2014-2-25 下午02:07:52 </br> * @author 许立亢 * @version 1.0 */ @XmlRootElement(name = "Book") public class Book { private String bookId; private String bookISBNnumber; private String bookName; //Let assume one author only private String author;
//get set 方法
}
?
package com.xlk.restful.domain; import java.util.Collection; import javax.xml.bind.annotation.XmlRootElement; /** * * Category实体类 </br> * 类名: Category </br> * 日期: 2014-2-25 下午02:08:35 </br> * @author 许立亢 * @version 1.0 */ @XmlRootElement(name = "Category") public class Category { private String categoryId; private String categoryName; private Collection<Book> books; //get set 方法 }
? ? ? ? ? 一定要在类的前边加上annotation ,这样才能让这个person的信息在POJO和XML之间转换。
2、为POJO类提供数据绑定
为了提供RESTful服务端和客户端通信,POJO需要转换为特定的格式,例如XML或JSON。为了实现这部分功能,需要一个数据绑定的组件来映射JAVA对象和XML(或者指定的格式)。
?
CXF使用JAXB作为默认的数据绑定组件。JAXB使用注解来定义JAVA对象和XML之间映射的关系。
?
在POJO类Category中,注解@XmlRootElement指定Category为XML的根元素。Category类的属性默认指定映射为@XmlElement。@XmlElement用来定义XML中的子元素。@XmlRootElement和@XmlElement允许自定义命名空间和XML中元素的名称。如果没有定义的话,JAXB在运行的时候默认的使用同样的属性名和类名来定义XML元素。
?
? ? ? ? 下面的XML请求表示了Category数据对象:
? ? ? ? ?
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Category> <books> <author>Naveen Balani</author> <bookISBNnumber>ISBNB001</bookISBNnumber> <bookId>NB001</bookId> <bookName>Fiction Book1</bookName> </books> <categoryId>005</categoryId> <categoryName>Fiction Series</categoryName> </Category>
?
3、创建实现RESTful功能的服务类
类注
CategoryService的类声明上定义了@Path?和@Produces注解。@Path定义了URI路径,客户端可以通过这个路径访问到CategoryService对象。如@Path("/categoryservice"),那么URI请求,就可以是http://localhost:9000/categoryservice/。@Produces定义了服务类和方法生产内容的类型。如@Produces("application/xml"),那么CategoryService只会生产application/xml。?
方法注解
每个方法都和@Produces相关,它决定了这个方法生产什么样的响应。每个方法都有和HTTP方法映射的的注解,例如@GET?和@POST。方法中的@Path注解指定了该方法的URI。例如getCategory()方法上的@Path("/category/{id}"),可以使用http://localhost:9000/categoryservice/category/001,访问到category id?为001的分类。{id}就好比参数。接着,让我们看看@PathParam注解。@PathParam注解是用来给URI?路径一个模板变量,方法的输入参数。@PathParam("id")?就是URI中的@Path ("/category/{id}")。
?
@GET
???????@Path("/category/{id}")
???????public Category getCategory(@PathParam("id") String id)
?
package com.xlk.restful.service.impl; //JAX-RS Imports import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; 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.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import com.xlk.restful.dao.impl.CategoryDAO; import com.xlk.restful.domain.Category; /** * * Category的restful服务端 </br> * 类名: CategoryService </br> * 日期: 2014-2-25 下午02:21:15 </br> * @author 许立亢 * @version 1.0 */ @Path("/categoryservice") @Produces({"application/json","application/xml"}) public class CategoryService { private CategoryDAO categoryDAO = new CategoryDAO(); public CategoryDAO getCategoryDAO() { return categoryDAO; } public void setCategoryDAO(CategoryDAO categoryDAO) { this.categoryDAO = categoryDAO; } @GET @Path("/category/{id}") @Produces({"application/json","application/xml"}) public Category getCategory(@PathParam("id") String id) { System.out.println("getCategory called with category id: " + id); Category cat = (Category) getCategoryDAO().getCategory(id); if (cat == null) { ResponseBuilder builder = Response.status(Status.BAD_REQUEST); builder.type("application/xml"); builder.entity("<error>Category Not Found</error>"); throw new WebApplicationException(builder.build()); } else { return cat; } } @POST @Path("/category") @Consumes({"application/json","application/xml"}) public Response addCategory(Category category) { System.out.println("addCategory called"); Category cat = (Category) getCategoryDAO().getCategory( category.getCategoryId()); if (cat != null) { return Response.status(Status.BAD_REQUEST).build(); } else { getCategoryDAO().addCategory(category); return Response.ok(category).build(); } } @DELETE @Path("/category/{id}") @Consumes({"application/json","application/xml"}) public Response deleteCategory(@PathParam("id") String id) { System.out.println("deleteCategory with category id : " + id); Category cat = (Category) getCategoryDAO().getCategory(id); if (cat == null) { return Response.status(Status.BAD_REQUEST).build(); } else { getCategoryDAO().deleteCategory(id); return Response.ok().build(); } } @PUT @Path("/category") @Consumes({"application/json","application/xml"}) public Response updateCategory(Category category) { System.out.println("updateCategory with category id : " + category.getCategoryId()); Category cat = (Category) getCategoryDAO().getCategory( category.getCategoryId()); if (cat == null) { return Response.status(Status.BAD_REQUEST).build(); } else { getCategoryDAO().updateCategory(category); return Response.ok(category).build(); } } @POST @Path("/category/book") @Consumes({"application/json","application/xml"}) public Response addBooks(Category category) { System.out.println("addBooks with category id : " + category.getCategoryId()); Category cat = (Category) getCategoryDAO().getCategory( category.getCategoryId()); if (cat == null) { return Response.status(Status.NOT_FOUND).build(); } else { getCategoryDAO().addBook(category); return Response.ok(category).build(); } } @GET @Path("/category/{id}/books") @Consumes("application/xml,application/json") public Response getBooks(@PathParam("id") String id) { System.out.println("getBooks called with category id : " + id); Category cat = (Category) getCategoryDAO().getCategory(id); if (cat == null) { return Response.status(Status.NOT_FOUND).build(); } else { cat.setBooks(getCategoryDAO().getBooks(id)); return Response.ok(cat).build(); } } }
?DAO
?
package com.xlk.restful.dao.impl; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.xlk.restful.domain.Book; import com.xlk.restful.domain.Category; /* * DataAcess object for performing CRUD operations. * Dummy implementation. */ public class CategoryDAO { private static Map<String, Category> categoryMap = new HashMap<String, Category>(); private static Map<String, Collection<Book>> bookMap = new HashMap<String, Collection<Book>>(); static { Category category1 = new Category(); category1.setCategoryId("001"); category1.setCategoryName("Java"); categoryMap.put(category1.getCategoryId(), category1); Book book1 = new Book(); book1.setAuthor("Naveen Balani"); book1.setBookName("Spring Series"); book1.setBookId("001"); book1.setBookISBNnumber("ISB001"); Book book2 = new Book(); book2.setAuthor("Rajeev Hathi"); book2.setBookName("CXF Series"); book2.setBookId("002"); book2.setBookISBNnumber("ISB002"); Collection<Book> booksList = new ArrayList<Book>(); booksList.add(book1); booksList.add(book2); bookMap.put(category1.getCategoryId(), booksList); } public void addCategory(Category category) { categoryMap.put(category.getCategoryId(), category); } public void addBook(Category category) { bookMap.put(category.getCategoryId(), category.getBooks()); } public Collection<Book> getBooks(String categoryId) { return bookMap.get(categoryId); } public Category getCategory(String id) { Category cat = null; //Dummy implementation to return a new copy of category to //avoid getting overridden by service if(categoryMap.get(id) != null) { cat = new Category(); cat.setCategoryId(categoryMap.get(id).getCategoryId()); cat.setCategoryName(categoryMap.get(id).getCategoryName()); } return cat; } public void deleteCategory(String id) { categoryMap.remove(id); // Remove association of books bookMap.remove(id); } public void updateCategory(Category category) { categoryMap.put(category.getCategoryId(), category); } }
?
?4、创建调用RESTful服务的客户端
JAX-RS并不提供调用RESTful服务客户端。CXF框架提供了两种方式来创建客户端,这两种都可以使用Spring配置。
?
代理API
代理API允许你使用RESTful服务的资源类和接口。代理类是客户端直接调用接口方法,使用户不需要手工创建HTTP请求。将RESTful服务类传递给org.apache.cxf.jaxrs.client.JAXRSClientFactory类。一旦代理类创建好了,你可以直接使用RESTful服务接口类的任何方法。
?
CategoryService store = JAXRSClientFactory.create("http://
localhost:9000", CategoryService.class);
//Makes remote call to Category RESTFul service
store.getBooks("001");
?
HTTP客户端
使用org.apache.cxf.jaxrs.client.WebClient调用RESTful服务。本例中采用HTTP客户端。
package com.xlk.restful.client; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import com.xlk.restful.domain.Category; import com.xlk.restful.service.impl.CategoryService; /** * * 启动restful服务 </br> * 类名: CategoryServerStart </br> * 日期: 2014-2-25 下午02:25:30 </br> * @author 许立亢 * @version 1.0 */ public class CategoryServerStart { public static void main(String[] args) { // Service instance CategoryService categoryService = new CategoryService(); JAXRSServerFactoryBean restServer = new JAXRSServerFactoryBean(); restServer.setResourceClasses(Category.class); restServer.setServiceBean(categoryService); restServer.setAddress("http://localhost:9000/"); restServer.create(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); try { br.readLine(); } catch (IOException e) { } System.out.println("Server Stopped"); System.exit(0); } }
?
?
package com.xlk.restful.client; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import javax.ws.rs.core.Response; import org.apache.cxf.jaxrs.client.WebClient; import com.xlk.restful.domain.Book; import com.xlk.restful.domain.Category; /** * * 使用restful风格测试 </br> * 类名: CategoryServiceRESTClient </br> * 日期: 2014-2-25 下午02:26:03 </br> * @author 许立亢 * @version 1.0 */ public class CategoryServiceRESTClient { //Put some static value private static final String CATEGORY_URL = "http://localhost:9000/"; private static final String CATEGORY_ID = "005"; private static final String TYPE_XML = "application/xml"; private static final String TYPE_JSON = "application/json"; public static void main(String[] args) { // System.out.println("Format is " + args[0]); testAddCategory(TYPE_XML); testUpdateCategory(TYPE_XML); testGetCategory(TYPE_XML); testAddBooksForCategory(TYPE_XML); testGetBooksForCategory(TYPE_XML); testDeleteCategory(TYPE_XML); testAddCategory(TYPE_JSON); testUpdateCategory(TYPE_JSON); testGetCategory(TYPE_JSON); testAddBooksForCategory(TYPE_JSON); testGetBooksForCategory(TYPE_JSON); testDeleteCategory(TYPE_JSON); } private static void testAddCategory(final String format) { System.out.println("testAddCategory called with format " + format); WebClient client = WebClient.create(CATEGORY_URL); client.path("/categoryservice/category").accept( format).type(format); Category cat = new Category(); cat.setCategoryId(CATEGORY_ID); cat.setCategoryName("Fiction"); Category catResponse = client.post(cat, Category.class); System.out.println("Category Id retreived for format " + format + " is " + catResponse.getCategoryId()); assertEquals(catResponse.getCategoryId(), CATEGORY_ID); } private static void testUpdateCategory(final String format) { System.out.println("testUpdateCategory called with format " + format); WebClient client = WebClient.create(CATEGORY_URL); client.path("/categoryservice/category").accept( format).type(format); Category cat = new Category(); cat.setCategoryId(CATEGORY_ID); cat.setCategoryName("Fiction Series"); Response response = client.put(cat); System.out.println("Status retreived for update category for format " + format + " is " + response.getStatus()); assertEquals("200", String.valueOf(response.getStatus())); } private static void testGetCategory(final String format) { System.out.println("testGetCategory called with format " + format); WebClient client = WebClient.create(CATEGORY_URL); Category category = client.path("/categoryservice/category/" + CATEGORY_ID).accept( format).type(format).get(Category.class); System.out.println("Category details retreived from service with format " + format); System.out.println("Category Name " + category.getCategoryName()); System.out.println("Category Id " + category.getCategoryId()); assertEquals(CATEGORY_ID, category.getCategoryId()); } private static void testAddBooksForCategory(final String format) { System.out.println("testAddBooksForCategory called with format " + format); WebClient client = WebClient.create(CATEGORY_URL); client.path("/categoryservice/category/book").type(format). accept(format); Category cat = new Category(); cat.setCategoryId(CATEGORY_ID); cat.setCategoryName("Fiction Series"); Book book1 = new Book(); book1.setAuthor("Naveen Balani"); book1.setBookId("NB001"); book1.setBookISBNnumber("ISBNB001"); book1.setBookName("Fiction Book1"); Collection<Book> booksList = new ArrayList<Book>(); booksList.add(book1); cat.setBooks(booksList); client.post(cat, Category.class); } private static void testGetBooksForCategory(final String format) { System.out.println("testGetBooksForCategory called with format " + format); WebClient clientBook = WebClient.create(CATEGORY_URL); Category categoryBooks = clientBook.path( "/categoryservice/category/" + CATEGORY_ID + "/books").type(format).accept(format).get(Category.class); System.out.println("Book details retreived from service with format " + format); assertEquals(String.valueOf(categoryBooks.getBooks().size()), "1"); Iterator<Book> iterator = categoryBooks.getBooks().iterator(); while (iterator.hasNext()) { Book book = iterator.next(); System.out.println("Book Name " + book.getBookName()); System.out.println("Book ISBN " + book.getBookISBNnumber()); System.out.println("Book ID " + book.getBookId()); System.out.println("Book Author " + book.getAuthor()); } } private static void testDeleteCategory(final String format) { System.out.println("testDeleteCategory called with format " + format); WebClient client = WebClient.create(CATEGORY_URL); client.path("/categoryservice/category/" + CATEGORY_ID).type(format). accept(format); Response response = client.delete(); System.out.println("Status retreived for delete category for format " + format + " is " + response.getStatus()); assertEquals("200", String.valueOf(response.getStatus())); } private static void assertEquals(String expected, String result) { if (!expected.equalsIgnoreCase(result)) { throw new RuntimeException("Expecte value " + expected + ", Got value" + result); } } }?
?3、测试项目
执行CategoryServerStart类中main方法,启动项目,控制台显示如下:
?项目部署启动成功。
执行测试类,CategoryServiceRESTClient,会依次调用相应的接口
?
这样一个基于restful的风格CXF开发基本完成。
整体的项目部署构建如下
?
下一章,继续介绍?
Restful(二)Spring整合CXF,发布RSETful 风格WebService
?
?
?
?
?