Apache CXF实现Web Service(2)——不借助重量级Web容器和Spring实现一个纯的JAX-RS(RESTful) web service
实现目标
http://localhost:9000/rs/roomservice 为入口,
http://localhost:9000/rs/roomservice/room为房间列表,
http://localhost:9000/rs/roomservice/room/001/ 为001号房间的信息,
http://localhost:9000/rs/roomservice/room/001/person 为在001号房间主的人的列表
在Eclipse中新建一个Java Project
(可以不是WTP的Dynamic Web Project)参考文章:Apache CXF实现Web Service(1)中的介绍
再看pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.cnblog.richaaaard.cxfstudy</groupId> <artifactId>cxf-test-standalone-rs-helloworld</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>cxf-test-standalone-rs-helloworld Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <!-- <cxf.version>2.7.18</cxf.version> --> <cxf.version>3.1.4</cxf.version> </properties> <dependencies> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-security</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-policy</artifactId> <version>${cxf.version}</version> </dependency> <!-- <dependency> --> <!-- <groupId>org.apache.cxf</groupId> --> <!-- <artifactId>cxf-bundle-jaxrs</artifactId> --> <!-- <version>${cxf.version}</version> --> <!-- </dependency> --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxrs</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>javax.ws.rs</groupId> <artifactId>jsr311-api</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.5.8</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.5.8</version> </dependency> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.0</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>cxfstudy</finalName> <resources> <resource> <directory>src/main/resources</directory> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**</include> </includes> <excludes> <exclude>**/*.java</exclude> </excludes> </resource> </resources> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <configuration> <contextPath>/</contextPath> <connectors> <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"> <port>9000</port> </connector> </connectors> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> </project>
与(1)中介绍的类同,JAXRSServerFactoryBean存在于cxf-rt-frontend-jaxrs中,而集合包cxf-bundle-jaxrs是不必要的
供资源使用的实体类Room和Person
package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="Room")
public class Room {
public Room()
{
persons=new HashMap<String,Person>();
}
String id;
Map<String,Person> persons;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Map<String, Person> getPersons() {
return persons;
}
public void setPersons(Map<String, Person> persons) {
this.persons = persons;
}
}
注意不要漏掉@XmlRootElement
package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name="Person") public class Person { private String name; private String sex; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
为了解决“No message body writer found for class”,即非简单对象无法序列化的问题,我们暂时先加两个Wrapper类作为Room的集合类和Person集合类,后面会专门介绍如何更优雅的解决这个问题。
Rooms.java
1 package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model; 2 3 import java.util.Map; 4 5 import javax.xml.bind.annotation.XmlRootElement; 6 7 import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.dao.RoomDAO; 8 9 @XmlRootElement(name="rooms") 10 public class Rooms { 11 Map<String,Room> rooms; 12 public Rooms() 13 { 14 rooms=RoomDAO.getMapOfRooms(); 15 } 16 public Map<String, Room> getRooms() { 17 return rooms; 18 } 19 public void setRooms(Map<String, Room> rooms) { 20 this.rooms = rooms; 21 } 22 }
Persons.java
package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model; import java.util.Map; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name="persons") public class Persons { Map<String,Person> persons; public Persons() { persons=null; } public Persons(Room room) { persons=room.getPersons(); } public Map<String, Person> getPersons() { return persons; } public void setPersons(Map<String, Person> persons) { this.persons = persons; } }
实体有了,需要数据,我们虚拟了一个DAO(原本DAO是数据访问层),这里我们直接将所需要测试的数据创建其中
1 package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.dao; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Person; 7 import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Room; 8 import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Rooms; 9 10 public class RoomDAO { 11 private static Map<String, Room> rooms; 12 static { 13 rooms = new HashMap<String, Room>(); 14 15 Person p1=new Person(); 16 p1.setName("Boris"); 17 p1.setSex("male"); 18 19 20 21 Room r=new Room(); 22 r.setId("001"); 23 r.getPersons().put(p1.getName(), p1); 24 rooms.put("001", r); 25 } 26 27 public static void addRoom(Room room) { 28 rooms.put(room.getId(), room); 29 } 30 31 public static void deleteRoom(String id) { 32 if (rooms.containsKey(id)) { 33 rooms.remove(id); 34 } 35 36 } 37 38 public static void updateRoom(String id,Room room) { 39 rooms.remove(id); 40 rooms.put(room.getId(), room); 41 } 42 43 public static Room getRoom(String id) { 44 if (rooms.containsKey(id)) { 45 return rooms.get(id); 46 } else { 47 return null; 48 } 49 } 50 /*operations to persons*/ 51 public static void addPerson(String id_room,Person person) { 52 if(rooms.containsKey(id_room)) 53 { 54 Room room=rooms.get(id_room); 55 room.getPersons().put(person.getName(), person); 56 } 57 } 58 59 public static Rooms getRooms() 60 { 61 return new Rooms(); 62 } 63 64 public static void deletePerson(String id_room,String name) 65 { 66 if(rooms.containsKey(id_room)) 67 { 68 Room room=rooms.get(id_room); 69 room.getPersons().remove(name); 70 } 71 } 72 73 public static Map<String, Room> getMapOfRooms() 74 { 75 return rooms; 76 } 77 }
到这里,我们基本已经完成准备工作
那么如何写一个RESTful Web Service呢?
package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.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 com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.dao.RoomDAO; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Person; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Persons; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Room; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Rooms; @Path("/roomservice") @Produces("application/xml") public class RoomService { @GET @Path("/room/{id}") @Consumes("application/xml") public Room getRoom(@PathParam("id")String id ) { System.out.println("get room by id= "+id); Room room=RoomDAO.getRoom(id); return room; } @GET @Path("/room") @Consumes("application/xml") public Rooms getAllRoom() { System.out.println("get all room"); Rooms rooms=RoomDAO.getRooms(); return rooms; } @POST @Path("/room") @Consumes("application/xml") public void addRoom(Room room) { System.out.println("add room which id is:"+room.getId()); RoomDAO.addRoom(room); } @PUT @Path("/room/{id}") @Consumes("application/xml") public void updateRoom(@PathParam("id")String id,Room room) { System.out.println("update room which original id is:"+room.getId()); RoomDAO.updateRoom(id,room); } @DELETE @Path("/room/{id}") @Consumes("application/xml") public void deleteRoom(@PathParam("id")String id) { System.out.println("remove room by id= "+id); RoomDAO.deleteRoom(id); } @POST @Path("/room/{id}") @Consumes("application/xml") public void addPerson(@PathParam("id") String id,Person person) { System.out.println("add person who's name is:"+person.getName()); RoomDAO.addPerson(id, person); } @GET @Path("/room/{id}/person") @Consumes("application/xml") public Persons getAllPersonOfRoom(@PathParam("id") String id) { // System.out.println("get room by id= "+id); // Room room=RoomDAO.getRoom(id); // return room.getPersons(); // TODO No message body writer HashMap System.out.println("get room by id= "+id); Room room=RoomDAO.getRoom(id); return new Persons(room); } @DELETE @Path("/room/{id}/{name}") @Consumes("application/xml") public void deletePerson(@PathParam("id")String id,@PathParam("name")String name) { System.out.println("remove person who's name is: "+name); RoomDAO.deletePerson(id, name); } }
注意在类头上的Annotation @Path和@Produces
@Path("/roomservice")——指这个服务的根路径,用于区分其他服务
@Produces("application/xml")——指这个服务生产xml的响应
最后我们还需一个服务
我们使用与(1)类似的方法,用JAXRSServerFactoryBean来启动一个服务
package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.server; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Person; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Persons; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Room; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Rooms; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.service.RoomService; public class Server { public static void main(String[] args) { RoomService service = new RoomService(); // Service instance JAXRSServerFactoryBean restServer = new JAXRSServerFactoryBean(); restServer.setResourceClasses(Room.class, Person.class, Rooms.class, Persons.class); restServer.setServiceBean(service); restServer.setAddress("http://localhost:9000/rs"); restServer.create(); } }
运行服务Run As... -> Java Application
124 [main] WARN org.apache.cxf.jaxrs.utils.ResourceUtils - No resource methods have been found for resource class com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Room 124 [main] WARN org.apache.cxf.jaxrs.utils.ResourceUtils - No resource methods have been found for resource class com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Person 125 [main] WARN org.apache.cxf.jaxrs.utils.ResourceUtils - No resource methods have been found for resource class com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Rooms 125 [main] WARN org.apache.cxf.jaxrs.utils.ResourceUtils - No resource methods have been found for resource class com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Persons 222 [main] INFO org.apache.cxf.endpoint.ServerImpl - Setting the server's publish address to be http://localhost:9000/rs 284 [main] INFO org.eclipse.jetty.util.log - Logging initialized @500ms 318 [main] INFO org.eclipse.jetty.server.Server - jetty-9.2.11.v20150529 330 [main] WARN org.eclipse.jetty.server.handler.AbstractHandler - No Server set for org.apache.cxf.transport.http_jetty.JettyHTTPServerEngine$1@49e202ad 357 [main] INFO org.eclipse.jetty.server.ServerConnector - Started ServerConnector@7364985f{HTTP/1.1}{localhost:9000} 357 [main] INFO org.eclipse.jetty.server.Server - Started @582ms 362 [main] WARN org.eclipse.jetty.server.handler.ContextHandler - Empty contextPath 369 [main] INFO org.eclipse.jetty.server.handler.ContextHandler - Started o.e.j.s.h.ContextHandler@351d0846{/,null,AVAILABLE}
暂时忽略WARN里面的日志
然后我们通过浏览器测试
(测试方式有多种,原则上能对http Interceptor的工具都可以对web service进行测试,eclipse里有自带的工具,TcpTrace、SoapUI也都是比较好用的小工具)
*扩展,RESTful用json格式
我们将RoomService.java中所有的"application/xml"替换成"application.json",然后重启服务。
浏览器访问会出现一行错误提示
"
No message body writer has been found for class com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Rooms, ContentType: application/json
"
这是因为,CXF默认有xml的序列化provider,我们需要显性指定一个JsonProvider,这里我们先用一个业界比较流行的Jackson来支持。
先在pom中加入依赖
<dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>2.6.3</version> </dependency>
然后为我们的server实例指定一个JsonProvider: JacksonJsonProvider
package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.server; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Person; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Persons; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Room; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Rooms; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.service.RoomService; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; public class Server { public static void main(String[] args) { RoomService service = new RoomService(); // Service instance JAXRSServerFactoryBean restServer = new JAXRSServerFactoryBean(); restServer.setResourceClasses(Room.class, Person.class, Rooms.class, Persons.class); restServer.setServiceBean(service); restServer.setAddress("http://localhost:9000/rs"); restServer.setProvider(new JacksonJsonProvider()); restServer.create(); } }
返回浏览器,访问地址:http://localhost:9000/rs/roomservice/room 查看,这时我们看到json格式的数据正常返回了。
追问:如果我们用cxf自己的JSONProvider会怎样?
如果用cxf自己的JSONProvider,我们需要引入
cxf-rt-rs-extension-providers.jar
而这个jar在运行时还会依赖jettison里面的TypeConverter,所以我们需要引入
jettison.jar
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-extension-providers</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>1.3.7</version> </dependency>
完事之后将Server里面的Provider换掉
package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.server; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import org.apache.cxf.jaxrs.provider.json.JSONProvider; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Person; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Persons; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Room; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Rooms; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.service.RoomService; public class Server { public static void main(String[] args) { RoomService service = new RoomService(); // Service instance JAXRSServerFactoryBean restServer = new JAXRSServerFactoryBean(); restServer.setResourceClasses(Room.class, Person.class, Rooms.class, Persons.class); restServer.setServiceBean(service); restServer.setAddress("http://localhost:9000/rs"); // restServer.setProvider(new JacksonJsonProvider()); restServer.setProvider(new JSONProvider<Object>()); restServer.create(); } }
从浏览器访问,也同样得到了序列化的json:
不知道有没有人注意两个json string不太一样。这是因为不同库的实现方式不同,Jettison生成的字符串明显≥Jackson的实现。
那么问题来了
如何选择json的实现呢?在实际应用中,xml好还是json或是其他格式会更好呢?
参考:
http://www.cnblogs.com/ggjucheng/p/3352477.html
https://cwiki.apache.org/confluence/display/CXF20DOC/JAXRS+Services+Configuration