Apache CXF实现Web Service(2)——不借助重量级Web容器和Spring实现一个纯的JAX-RS(RESTful) web service

时间:2022-09-21 05:19:08

实现目标

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

 package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model;

 import java.util.Map;

 import javax.xml.bind.annotation.XmlRootElement;

 import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.dao.RoomDAO;

 @XmlRootElement(name="rooms")
public class Rooms {
Map<String,Room> rooms;
public Rooms()
{
rooms=RoomDAO.getMapOfRooms();
}
public Map<String, Room> getRooms() {
return rooms;
}
public void setRooms(Map<String, Room> rooms) {
this.rooms = rooms;
}
}

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是数据访问层),这里我们直接将所需要测试的数据创建其中

 package com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.dao;

 import java.util.HashMap;
import java.util.Map; import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Person;
import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Room;
import com.cnblog.richaaaard.cxftest.standalone.rs.helloworld.model.Rooms; public class RoomDAO {
private static Map<String, Room> rooms;
static {
rooms = new HashMap<String, Room>(); Person p1=new Person();
p1.setName("Boris");
p1.setSex("male"); Room r=new Room();
r.setId("001");
r.getPersons().put(p1.getName(), p1);
rooms.put("001", r);
} public static void addRoom(Room room) {
rooms.put(room.getId(), room);
} public static void deleteRoom(String id) {
if (rooms.containsKey(id)) {
rooms.remove(id);
} } public static void updateRoom(String id,Room room) {
rooms.remove(id);
rooms.put(room.getId(), room);
} public static Room getRoom(String id) {
if (rooms.containsKey(id)) {
return rooms.get(id);
} else {
return null;
}
}
/*operations to persons*/
public static void addPerson(String id_room,Person person) {
if(rooms.containsKey(id_room))
{
Room room=rooms.get(id_room);
room.getPersons().put(person.getName(), person);
}
} public static Rooms getRooms()
{
return new Rooms();
} public static void deletePerson(String id_room,String name)
{
if(rooms.containsKey(id_room))
{
Room room=rooms.get(id_room);
room.getPersons().remove(name);
}
} public static Map<String, Room> getMapOfRooms()
{
return rooms;
}
}

到这里,我们基本已经完成准备工作

那么如何写一个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也都是比较好用的小工具)

Apache CXF实现Web Service(2)——不借助重量级Web容器和Spring实现一个纯的JAX-RS(RESTful) web service

*扩展,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

"

Apache CXF实现Web Service(2)——不借助重量级Web容器和Spring实现一个纯的JAX-RS(RESTful) web service

这是因为,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格式的数据正常返回了。

Apache CXF实现Web Service(2)——不借助重量级Web容器和Spring实现一个纯的JAX-RS(RESTful) web service

追问:如果我们用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:

Apache CXF实现Web Service(2)——不借助重量级Web容器和Spring实现一个纯的JAX-RS(RESTful) web service

不知道有没有人注意两个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