Apache CXF實現Web Service(2)——不借助重量級Web容器和Spring實現一個純的JAX-RS(RESTful) web service

Richaaaard發表於2015-12-02

實現目標

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

相關文章