實現目標
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