JAVA的 WebService规范 JAX-RS

REST 是一种软件架构模式,只是一种风格,不是像SOAP 那样本身承载着一种消息协议,(两种风格的Web 服务均采用HTTP 做传输协议是因为HTTP 协议能穿越防火墙,JAVA
的远程调用RMI 等是重量级协议,不能穿越防火墙),因此你也可以叫做REST 是基于HTTP协议的软件架构。REST 中重要的两个概念就是资源定位和资源操作,而HTTP 协议恰好完整的提供了这两个要点,HTTP 协议中的URI 可以完成资源定位,GET、POST、OPTION等方法可以完成资源操作,因此REST 完全依赖HTTP 协议就可以完成Web 服务,而不像SOAP 协议那样只利用HTTP 的传输特性,定位与操作由SOAP 协议自身完成,也正是由于SOAP 消息的存在,使得SOAP 笨重。你也可以说REST 充分利用了HTTP 协议的特性,而不是像SOAP 那样只利用了其传输这一特性(事实上大多数人提到HTTP 协议就只会想到它能用于数据传输)。
REST 对于HTTP 的利用分为以下两种:首先是资源定位,这就是URI,这本身并没有什么特别的,但要注意REST 对HTTP 的资源定位理解更加到位,也就是你的Web 服务的URI
要能足够表意,例如:http://www.fetion.com.cn/fetionwap/baby/getBabyInfoById?id=1,从URI上可以看出这个Web 服务定位到的资源是查询飞信WAP 宠物的信息,依据参数id 值查询。
那么可以继续出现以下层级:
http://www.fetion.com.cn/fetionwap/baby/storeroom/getStoreRoomById?id=1

http://www.fetion.com.cn/fetionwap/baby/storeroom/chicken/getCounts?id=1

我们看到REST 风格的URI 的目录层级足够表意,也就是资源定位,这种定位要求URI 是唯一的。因为REST 流行于互联网,网上的资源应该有唯一的资源位置(例如:图片、视频)。当然,如果你的服务越复杂,URI 可能就越长,越难理解,这也算是REST 风格的缺点。
第二种就是利用HTTP 的GET、POST、PUT、DELETE 四种操作外加HEAD 请求报头完成资源操作,你可以把前四种HTTP 的操作类比成数据库操作的SELECT、UPDATE、INSERT、DELETE 操作,有这几种最简单的操作任意组合就可以完成各种各样的复杂操作,当然这是REST 的理念,事实上这样创建应用有点儿牵强。
REST 是一种软件架构理念,现在被移植到Web 服务上(因此不要提到REST 就马上想到WebService,JAX-RS 只是将REST 设计风格应用到Web 服务开发),那么在开发Web 服务上,偏于面向资源的服务适用于REST,偏于面向活动的服务。另外,REST 简单易用,效率高,SOAP 成熟度较高,安全性较好。REST 提供的网络服务叫做OpenAPI,它不仅把HTTP 作为传输协议,也作为处理数据的工具,可以说对HTTP 协议做了较好的诠释,充分体现了HTTP 技术的网络能力。目前Google、Amazon、淘宝都有基于REST 的OpenAPI 提供调用。

JAX-RS 的API 在javax.ws.rs.*包中,其中大部分也是注解。

上一个简单的例子说明如何使用。

需要jsr311-api这个jar包。

package JAXRS;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

@Path(value = “/student/{id}”)
@Produces(“application/xml”)
public interface IStudentService {
@GET
@Path(value = “/info”)
Student getStudent(@PathParam(“id”) long id, @QueryParam(“name”)
String name);
@GET
@Path(value = “/info2”)
Student getStudent(@QueryParam(“name”) String name);
}

说明:
1.这个REST 的服务接口的最终响应结果是XML(@Produces 注解标注,这个注解可以包含一组字符串,默认值是*/*,它指定REST 服务的响应结果的MIME 类型,例如:
application/xml、application/json、image/jpeg 等),你也可以同时返回多种类型,但具体生成结果时使用哪种格式取决于ContentType。CXF 默认返回的是JSON 字符串。
2.访问方法URI 是/student/1/info?name=Andrew-Lee、/student/1/info2?name=Fetion,由@Path
注解组合而来;
3.@QueryParam 注解用于指定将URL 上的查询参数传递给使用这个注解的属性值;
4.@PathParam 注解用于指定将URL 上的路径参数作为使用这个注解的属性值。
5.@GET 注解指定方法对应于Http 的GET 请求。JAX-RS 提供javax.ws.rs.HttpMethod 注解
允许你增加除了@GET 等之外的方法,例如:你想定义一个新的HTTP 方法@PATCH,你
可以这样像下面这样编写代码:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod(“PATCH”)
public @interface PATCH {
}

但你要注意,你新增加的这个方法,Web 服务器一定要支持才可以,如果不支持,你就需要配置你的Web 服务器,譬如上面定义的@PATCH 注解指定的PATCH 方法在标准的Http方法中根本就不存在。

6. 跟@pathparam不同,@queryparam中,指定的是URL中的参数是以键值对的形式出现的,而在程序中
@QueryParam(“from”) int from则读出URL中from的值, 而@pathparem中,URL中只出现参数的值,不出现键值对,比如: “/users/2011/06/30”

 

package JAXRS;

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class StudentServiceImpl implements IStudentService {
public Student getStudent(long id, String name) {
Student s = new Student();
s.setId(id);
s.setName(name);
try {
s.setBirthday(new SimpleDateFormat(“yyyy-MM-dd”)
.parse(“1983-04-26”));
} catch (ParseException e) {
e.printStackTrace();
}
return s;
}

public Student getStudent(String name) {
Student s = new Student();
s.setId(1);
s.setName(name);
try {
s.setBirthday(new SimpleDateFormat(“yyyy-MM-dd”)
.parse(“1983-04-26″));
} catch (ParseException e) {
e.printStackTrace();
}
return s;
}

}

 

package JAXRS;

import java.util.Date;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name=”Student”)
public class Student {
private long id;
private String name;
private Date birthday;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}

因为最终的产出结果是XML,因此你需要对返回值Student 使用JAXB 注解,这样CXF 才会知道如何转换。

 

发布REST 服务(Jetty):

访问REST 风格的Web 服务很简单,你完全不需要像SOAP 使用SDK 生成客户端代码,因为REST 完全依赖HTTP 协议, 从这里可见REST 是轻量级的。我们访问http://127.0.0.1:335/ws/services/student/1/info?name=Andrew Lee 地址,可以看见页面如下所示:

我们看到Web 服务访问成功,当然这个响应的XML 客户端通常要接到并处理,所以你可以用((HttpURLConnection)new URL(“***”).openConnection()).getInputStream()获取,然后使用程序处理这个接收到的结果。为了更加好的使用HTTP 访问REST 服务,推荐你使用Apache HttpComponents-Client 组件进行HTTP 操作,因为这里使用的示例是GET 方法的请求,你是用URL 直接访问或者使用java.net.URL 类来访问很容易,但是如果Web 服务的方法使用@PUT 等注解,那么你就需要费一番头脑来在请求报头中加入要请求的方法类型等信息,这些是很繁琐的事情。HTTP-Client 的访问代码如下所示:

package JAXRS;

import java.io.IOException;
import java.io.InputStream;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

public class RestClient {
public static void main(String[] args) throws ClientProtocolException,IOException {
HttpGet get = new HttpGet(“http://127.0.0.1:335/student/info2?name=Fetion”);
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = httpclient.execute(get);
InputStream ins = response.getEntity().getContent();
byte[] b = new byte[1024];
StringBuilder sb = new StringBuilder();
while (ins.read(b) != -1) {
sb.append(new String(b, “UTF-8″));
}
System.out.println(sb.toString());
}
}

注意上面只是简单示例,使用的是逐个字节读入的方式,因此最好不要在输出的XML 中出现非ISO-8859-1 的字符。如果公开的Web 服务是PUT 方法,那么你可以使用HttpPut 类来完成处理。CXF 中的JAX-WS 的一些设置对于JAX-RS 也同样有效,例如日志拦截器,但不是所有的
都可以使用,例如:后面讲到的WSS4J 的拦截器就不能使用到JAX-RS 上,因为WS-*是SOAPWeb 服务的相关规范。下面是服务端被访问时输出的日志信息:
2009-6-23 21:55:05 org.apache.cxf.interceptor.LoggingInInterceptor
logging
信息: Inbound Message
—————————-

ID: 1
Address: /ws/services/student/info2
Encoding: UTF-8
Content-Type:
Headers: {connection=[Keep-Alive], host=[127.0.0.1:335],
user-agent=[Apache-HttpClient/4.0-beta2 (java 1.5)],
Content-Type=[null]}
Payload:
————————————–
2009-6-23 21:55:06
org.apache.cxf.interceptor.LoggingOutInterceptor$LoggingCallback
onClose
信息: Outbound Message
—————————
ID: 1
Encoding:
Content-Type: application/xml
Headers: {Date=[Tue, 23 Jun 2009 13:55:05 GMT]}
Payload: <?xml version=”1.0″ encoding=”UTF-8″
standalone=”yes”?><Student><birthday>1983-04-26T00:00:00+08:00</birth
day><id>1</id><name>Fetion</name></Student>
————————————–

标签