1 RESTful简要介绍
RESTful风格的WebService之所以当下如此流行,是由于其相对于SOAP风格的WebService更简洁、更轻量级,REST风格的WebService传输的是JSON或极其简洁的XML,因而其效率和性能都比较理想。
RESTful风格的WebService主张重用HTTP协议,面向资源编程(ROA)。扼要的说,RESTful风格WebService中,每一个URL即表示一个资源,比如http://www.example.com/employees/1 表示id为1的员工。
1. 如果对此URL调用HTTP GET方法,则返回员工的XML形式;
2. 如果对此URL调用HTTP POST/PUT方法,则可以新增或者修改此员工;
3. 如果对此URL调用HTTP DELETE方法,则可以删除此员工;
因此,我们在设计一个RESTful风格的接口时,一定要拥有面向资源设计的考量!
此外,在实现接口的过程中,要遵循RESTful风格的几个特性,他们分别是:
1. 无状态性:HTTP本身即是无状态协议,因此RESTful天然的具备无状态性,具备优良的水平扩展能力!
2. 幂等性:GET/PUT/DELETE方法具备此特性,幂等性可概述为无论重复调用多少次,其结果都一致!POST例外,不过设计的时候也可以按照此特性设计!
3. 唯一性:即URL地址要唯一的表示一个资源!
关于事务、安全等更多的高级特性这里不阐述,有兴趣的可参考《Restful Web Service中文版》一书。
下面以开发一个普通接口和开发REST风格WebService接口为对比,指导大家快速入门。
2 开发一个普通的接口
2.1 定义DTO
public class Info implements Serializable {
private String id;
private String name;
private String description;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
} |
2.2 定义接口
public interface SaleService extends Serializable {
public List<Info> getInfos();
public Info getInfo(String id);
public void saveOrUpdateInfo(Info info);
public void deleteInfo(String id);
} |
2.3 实现类
接口的实现,就此省略。
3 开发RESTful风格WebService服务端
3.1 定义资源(定义DTO)
@XmlRootElement(name = "Info")
public class Info implements Serializable {
private String id;
private String name;
private String description;
@XmlElement(name = "ID")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@XmlElement(name = "NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@XmlElement(name = "DESCRIPTION")
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
} |
解析:@XmlRootElement(name = "Info")之类的注释,是JAXB规范,用于XML与Java对象之间的互相转换。不熟悉JAXB规范的可以自行搜索相关资料。
此Info对象对应的XML格式如下:
<Info>
<DESCRIPTION>des1</DESCRIPTION>
<ID>1</ID>
<NAME>name1</NAME>
</Info> |
3.2 开发接口
@Produces({ MediaType.APPLICATION_XML })
public interface SaleService extends Serializable {
@GET
@Path("/infos")
public List<Info> getInfos();
@GET
@Path("/infos/{id}")
public Info getInfo(@PathParam("id") String id);
@POST
@Path("/infos")
@Consumes({ MediaType.APPLICATION_XML, MediaType.TEXT_XML,
MediaType.APPLICATION_JSON })
public void saveOrUpdateInfo(Info info);
@DELETE
@Path("/infos/{id}")
public void deleteInfo(@PathParam("id") String id);
} |
解析:接口中的注释均为JSR311中的规范。
@Produces表示这个接口响应格式为XML
表示响应http://www.example.com/.../infos的GET请求,返回Info列表。
表示响应http://www.example.com/.../infos/? 的GET请求,URL中的“?”作为参数代入方法中,最终返回对应的Info
表示响应http://www.example.com/.../infos 的POST请求,可以接受APPLICATION_XML,TEXT_XML,JSON格式,最终保存Info对象。
表示响应http://www.example.com/.../infos/? 的DELETE请求,URL中的“?”作为参数代入方法中,最终删除对应的Info
3.3 实现类
接口的实现,同上。
3.4 配置CXF(整合Spring、maven)
3.4.1 接口的Spring配置文件
定义接口的实现类
<bean id="saleService" class="com.csair.acp.service.impl.SaleServiceImpl" /> |
3.4.2 CXF的Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<jaxrs:server id="saleRestService" address="/v1">
<jaxrs:serviceBeans>
<ref bean="saleService" />
</jaxrs:serviceBeans>
</jaxrs:server>
</beans> |
解析:<ref bean="saleService" />对应接口的Spring定义,address="/v1"定义地址的前缀。比如: http://www.example.com/xxx/v1/infos/?
强烈建议加前缀!以此来提供不同版本的WebService访问!
3.4.3 修改web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping> |
3.4.4 Maven配置
在maven中使用cfx发布webservice需要添加如下依赖:
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-bundle</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-bundle-jaxrs</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-core</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
<version>2.2.4</version>
</dependency>
到此,配置完毕,可以启动部署到web容器中启动,在FireFox中访问http://localhost:8080/v1/infos ,看看是否有XML格式的输出。
4 开发RESTful风格WebService客户端(Java)
4.1 定义客户端接口
public interface SaleClient extends Serializable {
public List<Info> getInfos();
public Info getInfo(String id);
public void saveOrUpdateInfo(Info info);
public void deleteInfo(String id);
} |
4.2 定义实现类
客户端如果能引用服务端的jar包,那么开发将非常简单。
import com.csair.acp.resources.Info; //引用自服务端
import com.csair.acp.service.SaleService; //引用自服务端
public class SaleClientImpl implements SaleClient {
private static String BASE_ADDRESS = "http://localhost:8080/v1";
SaleService service;
public SaleClientImpl() {
initProxy();
}
private SaleService initProxy() {
service = JAXRSClientFactory.create(BASE_ADDRESS, SaleService.class);
WebClient.client(service).accept(MediaType.APPLICATION_XML);// 一定需要
return service;
}
@Override
public List<Info> getInfos() {
try {
return service.getInfos();
} catch (WebApplicationException ex) {
ex.printStackTrace();
return null;
}
}
@Override
public Info getInfo(String id) {
try {
return service.getInfo(id);
} catch (WebApplicationException ex) {
ex.printStackTrace();
return null;
}
}
@Override
public void saveOrUpdateInfo(Info info) {
try {
service.saveOrUpdateInfo(info);
} catch (WebApplicationException ex) {
ex.printStackTrace();
}
}
@Override
public void deleteInfo(String id) {
try {
service.deleteInfo(id);
} catch (WebApplicationException ex) {
ex.printStackTrace();
}
}
} |
客户端的开发十分简便,如果用其他语言,那么需要自行使用对应的HTTP类库进行编码。
如果使用spring injecting proxies 方式进行配置,则使用如下配置:
<jaxrs:client id ="saleClient " address="http://localhost:8080/v1" serviceClass="com.csair.acp.service.SaleService">
</jaxrs:client>
具体配置参照:http://cxf.apache.org/docs/jax-rs-client-api.html
5 范例
本文对应的例子为maven构建:
rest-server为服务端,运行jetty:run即可
rest-client为客户端,运行rest-client/src/test/java/com/csair/acp/client/impl/SaleClientImplTest.java单元测试类即可