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

posted @ 2015-12-02 11:03  Richaaaard  阅读(2915)  评论(1编辑  收藏  举报