首页 > 代码库 > Apache CXF 102 CXF with REST
Apache CXF 102 CXF with REST
前言
续上篇Apache CXF 101,摘抄部分REST概念性知识,以运行实例考察CXF对REST的支持。
目录
1 REST简介
2 工具
3 运行实例
内容
本Spike记录中内容,如无特别指出,均引用[1]。
1 REST简介
有两种实现Web Service的主要方式:SOAP(Simple Object Access Protocl)和REST架构风格(Representational State Transfer architecture style)。
用REST架构风格构建的服务(RESTful服务),用简单的XML等形式封装数据、通过HTTP传输数据。
与基于SOAP的服务开发方法相比,施加于服务开发者的技术负担更小,特别是服务只需发送和接收简单的XML消息时。
REST架构风格是关于资源的,资源是用URI(Uniform Resource Indicator)标识的表示(representation)。
资源可以是任意信息,例如Book、Order等。客户通过交换资源的表示来查询或更新URI标识的资源。
表示中包含以具体格式记录的实际信息,常见的格式有HTML/XML/JSON。客户通常可以指定它/他/她需要的表示。所有处理特定资源上的信息均包含于请求自身,使得交互是无状态的。
用REST架构风格原则构建的Web服务称为RESTful Web服务。RESTful Web服务可视为可通过URI标识的资源。RESTful Web服务用标准的HTTP方法(GET/POST…)暴露操作的集合。客户使用HTTP协议通过URI访问/调用这些方法。
RESTful Service URI example
http://cxf.spike.com/department/deptname/employee
GET 返回部门中雇员列表
POST 创建部门中雇员记录
DELETE 删除部门中雇员记录
JSR support
JAX-RS[2]
JAS-RS定义这些目标:
? POJO为中心
JAX-RS API提供了一组注解和相关的类/接口,将POJO暴露为RESTful资源。
? HTTP为中心
RESTful资源通过HTTP协议暴露,该规范提供了清晰的HTTP协议与相关的JAX-RS API中类、方法之间的映射。同时提供了如何匹配HTTP请求到资源类和方法的指南。
? 格式独立
该API提供了一种可插拔机制,该机制允许以标准的方式添加HTTP内容类型。
? 容器独立
用JAX-RS开发的应用应该可以运行于任意容器中。该规范定义了如何用JAX-RS API部署应用的标准机制。
? Java企业版容器包含
该规范定义了RESTful资源如何宿于Java EE6容器和如何利用容器提供的功能。
JAX-RS的实现[3]
Apache CXF开源的Web服务框架。
Jersey由Sun提供的JAX-RS的参考实现。
RESTEasy JBoss的实现。
Restlet由Jerome Louvel和Dave Pawson开发,是最早的REST框架,先于JAX-RS出现。
Apache Wink一个Apache软件基金会孵化器中的项目,其服务模块实现JAX-RS规范
2 工具
Firefox插件:Poster(https://addons.mozilla.org/en-US/firefox/addon/poster/)
安装后工具位置
工具面板
用数据测试
结果呈现
3 运行实例
Book Shop Application
Functionality:
(1) Category CRUD
(2) Add Books to a Category
(3) Getting Books belongs to a Category
[1]中指出了(CXF) RESTful服务开发实践步骤
(1) 创建Request/Response的Java数据对象
(2) 绑定Request/Response对象
(3) 创建RESTful实现:创建实现类并用JAX-RS注解
(4) 服务单元测试
(5) 创建客户端,调用服务方法
(6) 将服务部署于容器中
(1) 创建Request/Response的Java数据对象
(2) 绑定Request/Response对象
两个JAXB绑定的pojo,JAXB是CXF默认的绑定方式
Book
package demo.restful.pojo; import javax.xml.bind.annotation.XmlRootElement; /** * Description: Book<br/> * Date: 2014-5-12 下午10:54:16 */ @XmlRootElement(name = "Book") public class Book { private String bookId; private String bookISBNnumber; private String bookName; private String author;//one author only public String getBookId() { return bookId; } public void setBookId(String bookId) { this.bookId = bookId; } public String getBookISBNnumber() { return bookISBNnumber; } public void setBookISBNnumber(String bookISBNnumber) { this.bookISBNnumber = bookISBNnumber; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @Override public String toString() { return "Book [bookId=" + bookId + ", bookISBNnumber=" + bookISBNnumber + ", bookName=" + bookName + ", author=" + author + "]"; } }
Category
package demo.restful.pojo; import java.util.List; import javax.xml.bind.annotation.XmlRootElement; /** * Description: Category<br/> * Date: 2014-5-12 下午10:53:52 */ @XmlRootElement(name = "Category") public class Category { private String categoryId; private String categoryName; private List<Book> books; public String getCategoryId() { return categoryId; } public void setCategoryId(String categoryId) { this.categoryId = categoryId; } public String getCategoryName() { return categoryName; } public void setCategoryName(String categoryName) { this.categoryName = categoryName; } public List<Book> getBooks() { return books; } public void setBooks(List<Book> books) { this.books = books; } @Override public String toString() { return "Category [categoryId=" + categoryId + ", categoryName=" + categoryName + ", books=" + books + "]"; } }
(3) 创建RESTful实现:创建实现类并用JAX-RS注解
CategoryService接口
package demo.restful.service; import javax.ws.rs.core.Response; import demo.restful.pojo.Category; /** * Description: Category Service Interface With Exception Handling<br/> * Date: 2014-5-12 下午11:12:02 */ public interface CategoryService { public Category getCategory(String id); public Response addCategory(Category category); public Response deleteCategory(String id); public Response updateCategory(Category category); public Response addBooks(Category category); public Response getBooks(String id); }
CategoryService实现类
package demo.restful.service; 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 demo.restful.data.CategoryDAO; import demo.restful.pojo.Category; /** * Description: Category Service Implementation With Exception Handling<br/> * Date: 2014-5-12 下午11:12:02 */ @Path("/categoryservice") @Produces({ "application/xml", "application/json" }) public class CategoryServiceImpl implements CategoryService { private CategoryDAO categoryDAO;//wired public CategoryDAO getCategoryDAO() { return categoryDAO; } public void setCategoryDAO(CategoryDAO categoryDAO) { this.categoryDAO = categoryDAO; } @GET @Path("/category/{id}") 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/xml", "application/json" }) 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}") 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") 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/xml", "application/json" }) 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(); } } }
以内存中Map实现的DAO:服务支持类CategoryDAO
package demo.restful.data; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import demo.restful.pojo.Book; import demo.restful.pojo.Category; /** * Description: Category DAO Stub Implementation<br/> * Date: 2014-5-12 下午11:22:17 */ public class CategoryDAO { //category-id : category private static Map<String, Category> categoryMap = new HashMap<String, Category>(); //category-id : books private static Map<String, List<Book>> bookMap = new HashMap<String, List<Book>>(); static { //Populate some static data 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"); List<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); } //Add Books associated with the Category public void addBook(Category category) { bookMap.put(category.getCategoryId(), category.getBooks()); } public List<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) 服务单元测试
因服务有灵活的部署方式,部署在Servlet容器中上一篇记录中已经有所说明,另一种方式是以常规应用程序直接运行在JVM中。
Server实现
Spring配置文件restapp.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="categoryService" class="demo.restful.service.CategoryServiceImpl"> <property name="categoryDAO"> <ref bean="categoryDAO" /> </property> </bean> <bean id="categoryDAO" class="demo.restful.data.CategoryDAO"> </bean> </beans>
常量类
package demo.restful; /** * Description: 常量类<br/> * Date: 2014-5-12 下午11:41:09 */ public class Constants { public static final String SERVICE_URL = "http://localhost:9000/"; }
Server
package demo.restful.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import org.springframework.context.support.ClassPathXmlApplicationContext; import demo.restful.Constants; import demo.restful.pojo.Book; import demo.restful.pojo.Category; import demo.restful.service.CategoryService; import demo.restful.service.CategoryServiceImpl; /** * Description: Server<br/> * Date: 2014-5-12 下午11:37:09 */ public class Server { public static void main(String[] args) { ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] { "/demo/restful/restapp.xml" }); CategoryService categoryService = (CategoryServiceImpl) appContext.getBean("categoryService"); // Service instance JAXRSServerFactoryBean restServer = new JAXRSServerFactoryBean(); restServer.setResourceClasses(Category.class, Book.class); restServer.setServiceBean(categoryService); restServer.setAddress(Constants.SERVICE_URL); restServer.create(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); try { br.readLine(); } catch (IOException e) { e.printStackTrace(); } System.out.println("Server Stopped"); System.exit(0); } }
这样自然的,单元测试就有两种方法:部署在Servlet容器中的服务以浏览器/Poster测试,以常规应用程序部署的服务则可以编写JUnit单元测试。
Poster测试
这里用到的Poster的两个主要设置:
"Content to Send"label中将"Content Type"设置为application/xml(或application/json,视准备数据而定)
"Header"label中添加Name: Accept, Value: application/xml(或application/json)
新增Category
更新Category
JUnit单元测试
HTTPClient - 简单的服务客户端示例
package demo.restful.client; import java.util.Iterator; import org.apache.cxf.jaxrs.client.WebClient; import demo.restful.pojo.Book; import demo.restful.pojo.Category; /** * Description: HTTP Client<br/> * Date: 2014-5-12 下午11:57:41 */ public class HTTPClient { public static void main(String[] args) { WebClient client = WebClient.create("http://localhost:9000/"); Category category = client.path("categoryservice/category/001").accept("application/xml").get(Category.class); System.out.println("Category details from REST service."); System.out.println("Category Name " + category.getCategoryName()); System.out.println("Category Id " + category.getCategoryId()); System.out.println("Book details for Category " + category.getCategoryId() + " from REST service"); String bookString = "categoryservice/category/" + category.getCategoryId() + "/books"; WebClient clientBook = WebClient.create("http://localhost:9000/"); Category categoryBooks = clientBook.path(bookString).accept("application/xml").get(Category.class); 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()); } } }
RESTCategoryService,测试媒体类型application/xml,为直观显示执行结果,未使用JUnit断言。
package demo.restful.client; import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.ws.rs.core.Response; import org.apache.cxf.jaxrs.client.WebClient; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import demo.restful.pojo.Book; import demo.restful.pojo.Category; /** * Description: RESTCategoryService in Servlet Container Unit test<br/> * Date: 2014-5-13 下午9:41:27 */ public class RESTCategoryServiceTest { private WebClient client = null; private static final String FORMAT = "application/xml"; private static String id = null; @BeforeClass public static void setUpBeforClass() { id = newId(); } @Before public void setUp() { client = WebClient.create("http://localhost:8082/CXF/"); } @Test public void getCategory() { System.out.println("======================getCategory"); Category category = client.path("categoryservice/category/001").accept(FORMAT).get(Category.class); System.out.println(category); System.out.println("======================getCategory"); } @Test public void addCategory() { System.out.println("======================addCategory"); Category category = new Category(); category.setCategoryId(id); category.setCategoryName(".NET"); System.out.println("Add " + category + "..."); Category response = client.path("categoryservice/category").accept(FORMAT).type(FORMAT).post(category, Category.class); System.out.println(response); //test using query System.out.println("Get category " + id + "..."); client = WebClient.create("http://localhost:8082/CXF/");//new WebClient!!! System.out.println(client.path("categoryservice/category/" + id).accept(FORMAT).get(Category.class)); System.out.println("======================addCategory"); } @Test public void updateCategory() { System.out.println("======================updateCategory"); Category category = new Category(); category.setCategoryId(id); category.setCategoryName("Microsoft .NET"); Response response = client.path("categoryservice/category").put(category); System.out.println(response.getStatus()); System.out.println(response.getEntity()); System.out.println("======================updateCategory"); } @Test public void addBooks() { System.out.println("======================addBooks"); client.path("/categoryservice/category/book").type(FORMAT).accept(FORMAT); Category cat = new Category(); cat.setCategoryId(id); cat.setCategoryName("Fiction Series"); Book book1 = new Book(); book1.setAuthor("Naveen Balani"); book1.setBookId("NB001"); book1.setBookISBNnumber("ISBNB001"); book1.setBookName("Fiction Book1"); List<Book> booksList = new ArrayList<Book>(); booksList.add(book1); cat.setBooks(booksList); Category response = client.post(cat, Category.class); System.out.println(response); System.out.println("======================addBooks"); } @Test public void getBooks() { System.out.println("======================getBooks"); Category category = client.path("/categoryservice/category/" + id + "/books").accept(FORMAT).get(Category.class); System.out.println("Book details retreived from service with format " + FORMAT); List<Book> books = category.getBooks(); for (Book book : books) { 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()); } System.out.println("======================getBooks"); } @Test public void deleteCategory() { System.out.println("======================deleteCategory"); Response response = client.path("categoryservice/category/" + id).delete(); System.out.println(response.getStatus()); System.out.println("======================deleteCategory"); } private static String newId() { return UUID.randomUUID().toString().replaceAll("\\-", "").toUpperCase(); } }
JSONRESTCategoryServiceTest,与RESTCategoryService基本相同,唯一的区别在于媒体类型为application/json
package demo.restful.client; import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.ws.rs.core.Response; import org.apache.cxf.jaxrs.client.WebClient; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import demo.restful.pojo.Book; import demo.restful.pojo.Category; /** * Description: JSONRESTCategoryService in Servlet Container Unit test<br/> * Date: 2014-5-13 下午9:41:27 */ public class JSONRESTCategoryServiceTest { private WebClient client = null; private static final String FORMAT = "application/json"; private static String id = null; @BeforeClass public static void setUpBeforClass() { id = newId(); } @Before public void setUp() { client = WebClient.create("http://localhost:8082/CXF/"); } @Test public void getCategory() { System.out.println("======================getCategory"); Category category = client.path("categoryservice/category/001").accept(FORMAT).get(Category.class); System.out.println(category); System.out.println("======================getCategory"); } @Test public void addCategory() { System.out.println("======================addCategory"); Category category = new Category(); category.setCategoryId(id); category.setCategoryName(".NET"); System.out.println("Add " + category + "..."); Category response = client.path("categoryservice/category").accept(FORMAT).type(FORMAT).post(category, Category.class); System.out.println(response); //test using query System.out.println("Get category " + id + "..."); client = WebClient.create("http://localhost:8082/CXF/");//new WebClient!!! System.out.println(client.path("categoryservice/category/" + id).accept(FORMAT).get(Category.class)); System.out.println("======================addCategory"); } @Test public void updateCategory() { System.out.println("======================updateCategory"); Category category = new Category(); category.setCategoryId(id); category.setCategoryName("Microsoft .NET"); Response response = client.path("categoryservice/category").put(category); System.out.println(response.getStatus()); System.out.println(response.getEntity()); System.out.println("======================updateCategory"); } @Test public void addBooks() { System.out.println("======================addBooks"); client.path("/categoryservice/category/book").type(FORMAT).accept(FORMAT); Category cat = new Category(); cat.setCategoryId(id); cat.setCategoryName("Fiction Series"); Book book1 = new Book(); book1.setAuthor("Naveen Balani"); book1.setBookId("NB001"); book1.setBookISBNnumber("ISBNB001"); book1.setBookName("Fiction Book1"); List<Book> booksList = new ArrayList<Book>(); booksList.add(book1); cat.setBooks(booksList); Category response = client.post(cat, Category.class); System.out.println(response); System.out.println("======================addBooks"); } @Test public void getBooks() { System.out.println("======================getBooks"); Category category = client.path("/categoryservice/category/" + id + "/books").accept(FORMAT).get(Category.class); System.out.println("Book details retreived from service with format " + FORMAT); List<Book> books = category.getBooks(); for (Book book : books) { 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()); } System.out.println("======================getBooks"); } @Test public void deleteCategory() { System.out.println("======================deleteCategory"); Response response = client.path("categoryservice/category/" + id).delete(); System.out.println(response.getStatus()); System.out.println("======================deleteCategory"); } private static String newId() { return UUID.randomUUID().toString().replaceAll("\\-", "").toUpperCase(); } }
(5) 创建客户端,调用服务方法
见HTTPClient示例。
(6) 将服务部署于容器中
上一篇记录中已经有所说明。
参考资料
[1]Balani N., Hathi R..Apache CXF web service development Develop and deploy SOAP and RESTful web service[M]. Birmingham: Packet Publishing. 2009.
[2]JSR311: JAX-RS: The JavaTM API for RESTful Web Services[EB/OL]. https://jcp.org/en/jsr/detail?id=311. 2014-05-12.
[3]JAX-RS. Wiki. http://zh.wikipedia.org/wiki/JAX-RS. 2014-05-12.