JAX-RS(Java API for RESTful Web Services)同样也是JSR的一部分,详细规范定义见 https://jcp.org/en/jsr/detail?id=311 。从JAVA EE 6开始,已经内置了该技术,跟.NET中的RESTful WCF类似,只要在常规方法上使用一些简单的注解,就可以对外暴露成RESTful 服务.
注:本文运行环境为Jboss EAP 6.x ,其它容器特别是tomcat,并未自带JAX-RS依赖的jar包,如果采用tomcat,需要自己导入这些相关依赖jar包。
一、最基础的HelloWorld (无参数)
@Path("/")
public class MyService { final String XMLNS_NAMESPACE = "http://yjmyzz.cnblogs.com/rest/service";
final String ROOT_NODE = "root"; @GET
@Path("/json/hello")
@Produces(MediaType.APPLICATION_JSON)
public JAXBElement<String> getHelloWorldJSON() {
JAXBElement<String> result = new JAXBElement<String>(new QName("",
ROOT_NODE), String.class, sayHelloWorld());
return result;
}
。。。
}
getHelloWorldJSON
private String sayHelloWorld() {
return "Hello JAX-RS!";
}
sayHelloWorld
解释:
@GET 表示该服务可以直接在浏览器地址栏里访问(对应HTTP请求中的GET方法)
@Path 这里用了二次,第一次是在Class上,相当服务的基地址;第二次是在方法上,表示具体某个服务方法的URL.
此外web.xml中,还需要增加以下内容:
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
web.xml
@Produces 表示返回消息的格式,配合MediaType.APPLICATION_JSON说明,本方法返回Json字符串格式
以上几项综合起来,最终可以用类似 http://localhost:8080/jboss-as-helloworld-rs/rest/json/hello 的地址来访问,返回的结果为:
JAXBElement<T> 类似一个包装器(Wrapper),用于将返回对象做下json/xml格式的包装,比如上图中的{root:}就是它的功劳,感兴趣的朋友,可以把返回类似改成最原始的String对比下差异.
如果需要返回XML格式,只需要把@Produces后面的MediaType.APPLICATION_JSON换成MediaType.APPLICATION_XML就行了,即:
@GET
@Path("/xml/hello")
@Produces(MediaType.APPLICATION_XML)
public JAXBElement<String> getHelloWorldXML() {
JAXBElement<String> result = new JAXBElement<String>(new QName(
XMLNS_NAMESPACE, ROOT_NODE), String.class, sayHelloWorld());
return result;
}
getHelloWorldXML
效果如下:
二、带一个基本类型参数的HelloWorld
@GET
@Path("/json/hi/{name}")
@Produces(MediaType.APPLICATION_JSON)
public JAXBElement<String> getHelloToNameJSON(@PathParam("name") String name) {
JAXBElement<String> result = new JAXBElement<String>(new QName("",
ROOT_NODE), String.class, sayHelloToName(name));
return result;
} @GET
@Path("/xml/hi/{name}")
@Produces(MediaType.APPLICATION_XML)
public JAXBElement<String> getHelloToNameXML(@PathParam("name") String name) {
JAXBElement<String> result = new JAXBElement<String>(new QName(
XMLNS_NAMESPACE, ROOT_NODE), String.class, sayHelloToName(name));
return result;
}
getHelloToNameJSON & getHelloToNameXML
private String sayHelloToName(String name) {
return "Hello " + name + ",welcome to the world of JAX-RS!";
}
sayHelloToName
对比一下刚才的代码,有二个变化:
2.1 @Path上多了一个{name}的部分,表示url中,将允许以 /json/hi/xxx 的访问URL响应请求
2.2 方法参数前有一个 @PathParam("name") 的注解,表示如果按/json/hi/xxx的方式访问,则url中xxx的部分,会被映射到name这个参数上(说得更通俗点,就是通过解析url最后的xxx部分直接把name传进来)
运行效果如下:
三、返回结果为实体Bean
实际应用中,web服务返回的结果,除了基本类型,还有可能是实体Bean,先来定义一个User类
package yjmyzz.service.model; import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "user")
public class User { String username;
String password;
int pin; @XmlElement
public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} @XmlElement
public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} @XmlAttribute
public int getPin() {
return pin;
} public void setPin(int pin) {
this.pin = pin;
} }
User
代码中加的一堆@XML开头的注解,是为了在传输过程中,如果需要以XML格式传输,这些注解可以帮助完成 XML <-> Obj的转换(详情可参见JAXB相关内容)
@GET
@Path("/xml/user")
@Produces(MediaType.APPLICATION_XML)
public User getUserInXML() {
return getUser("unknown");
} @GET
@Path("/json/user")
@Produces(MediaType.APPLICATION_JSON)
public User getUserInJSON() {
return getUser("unknown");
}
getUserInXML & getUserInJSON
注意:这里我们没有使用JAXBElement<T>对结果进行包装,辅助方法getUser()代码如下
private User getUser(String username) {
User user = new User();
user.setUsername(username);
String pwd = new Long(Math.round(Math.random() * 100000)).toString();
user.setPassword(pwd);
int pin = (int) (Math.random() * 1000);
user.setPin(pin);
return user;
}
getUser
运行结果如下:
当然,也可以给方法增加参数,先从最基本类型的String参数耍起:
@GET
@Path("/xml/userByName/{name}")
@Produces(MediaType.APPLICATION_XML)
public User getUserInXML(@PathParam("name") String username) {
return getUser(username);
} @GET
@Path("/json/userByName/{name}")
@Produces(MediaType.APPLICATION_JSON)
public User getUserInJSON(@PathParam("name") String username) {
return getUser(username);
}
getUserWithStringParameter
运行结果如下:
如果加上包装器JAXBElement<User>后,返回结果会变成什么样呢? 我也很好奇,so, 嘿-喂狗!
@GET
@Path("/xml/userByName2/{name}")
@Produces(MediaType.APPLICATION_XML)
public JAXBElement<User> getUserInXML2(@PathParam("name") String username) {
JAXBElement<User> result = new JAXBElement<User>(new QName(
XMLNS_NAMESPACE, ROOT_NODE), User.class, getUser(username));
return result;
} @GET
@Path("/json/userByName2/{name}")
@Produces(MediaType.APPLICATION_JSON)
public JAXBElement<User> getUserInJSON2(@PathParam("name") String username) {
JAXBElement<User> result = new JAXBElement<User>(new QName("",
ROOT_NODE), User.class, getUser(username));
return result;
}
getUserWithWrapper
结果如下:
个中差异,请自行体会
四、单个String参数的实体Bean注入
web服务中方法的传入参数,不可能都是基础类型,同样也可以是实体Bean
@GET
@Path("/xml/product/{name}")
@Produces(MediaType.APPLICATION_XML)
public Product getProductXML(@PathParam("name") Product product) {
return getProductFromServer(product);
} @GET
@Path("/xml/product2/{name}")
@Produces(MediaType.APPLICATION_XML)
public JAXBElement<Product> getProductXML2(
@PathParam("name") Product product) {
JAXBElement<Product> result = new JAXBElement<Product>(new QName(
XMLNS_NAMESPACE, ROOT_NODE), Product.class,
getProductFromServer(product));
return result;
}
getProductXML & getProductXML2
这里我们用到了一个新的Bean :Product类,代码如下:
package yjmyzz.service.model; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "product")
public class Product { public Product() {
System.out.println("construct: Product() is called!");
} public Product(String name) {
System.out.println("construct: Product(String name) is called!");
this.productName = name;
} @XmlElement
public String getProductName() {
return productName;
} public void setProductName(String productName) {
this.productName = productName;
} private String productName; }
Product Bean
先提个问题:从@Path上看,我们希望用类似 /xml/product/xxx 来请求该web方法,但是方法中的参数是一个Bean实例,而url中的xxx部分只是一个字符串,一个字符串如何被映射成一个Bean实例呢?
关键在于Product中的构造函数:Product(String name) ,方法getProductXML(@PathParam("name") Product product) 被请求时,url中的xxx部分会先映射成参数name,然后name又会被传入到构造函数Product(String name)中,于是一个崭新的Product对象诞生了!(可以通过eclipse Console的输出来印证Product(String name)是否被调用)
运行结果:
eclipse的控制台里,也可以看到构造器,确实被调用了
除了利用“带参数的构造器”来实现“url参数->Bean实例”的注入,JAX-RS机制还允许使用Bean自身提供的static valueOf()方法来实现注入,再来看一下示例:
从Product派生一个子类Book:
package yjmyzz.service.model; import java.util.Calendar;
import java.util.Date; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement(name = "book")
public class Book extends Product { private String isbn; private String author; private Date publishDate; public Book() {
System.out.println("construct: Book() is called!");
} public static Book valueOf(String isbn) {
System.out.println("Book: valueOf(String isbn) is called!"); Book book = new Book();
book.setISBN(isbn);
book.setProductName("Java编程思想(第4版)"); book.setAuthor("[美]埃克尔"); Calendar calendar = Calendar.getInstance();
calendar.set(2007, 6, 1);
book.setPublishDate(calendar.getTime());
return book;
} @XmlElement
public String getISBN() {
return isbn;
} public void setISBN(String isbn) {
this.isbn = isbn;
} @XmlElement
public String getAuthor() {
return author;
} public void setAuthor(String author) {
this.author = author;
} @XmlElement
public Date getPublishDate() {
return publishDate;
} public void setPublishDate(Date publishDate) {
this.publishDate = publishDate;
} }
Book
再定义一个新方法:
@GET
@Path("/xml/book/{ISBN}")
@Produces(MediaType.APPLICATION_XML)
public Book getBookXML(@PathParam("ISBN") Book book) {
return getBookFromServer(book);
}
getBookXML
里面有一个辅助方法,代码如下:
private Book getBookFromServer(Book book) {
book.setProductName(book.getProductName() + " from server!");
return book;
}
getBookFromServer
运行效果如下:
eclipse的控制台信息如下:
五、POST方式的Bean实例参数注入
前面提到的都是GET方式的web服务方法,GET方式能传输的数据大小有限,对于较大的参数数据,直接POST的场景更常见,下面是一个示例:
@POST
@Path("/json/add-book")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Book addBookJSON(Book book) {
return getBookFromServer(book);
} @POST
@Path("/xml/add-book")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_XML)
public Book addBookXML(Book book) {
return getBookFromServer(book);
}
add-book
解释一下:
又遇到了一个新的注解:@Consumes(MediaType.APPLICATION_JSON) 它表示传入的Book实例是json格式。
我们用jQuery以Ajax方式调用这个服务演示一下:
function testJsonAddBook(){
$(document).ready(function(){
$.ajax({
type:"POST",
url:"rest/json/add-book",
dataType: "json",
contentType: 'application/json',
data:JSON.stringify({"productName":"Java编程思想(第4版)","isbn":"9787111213826","author":"[美]埃克尔","publishDate":1183270161125}),
success: function(data, textStatus, jqXHR){
alert("以下是从服务器返回的Book对象:\n\n" + JSON.stringify(data));
},
error: function(jqXHR, textStatus, errorThrown){
alert('error: ' + textStatus);
}
});
})
} function testXmlAddBook(){
$(document).ready(function(){
$.ajax({
type:"POST",
url:"rest/xml/add-book",
dataType: "xml",
contentType: 'application/xml',
data:"<book><productName>Java编程思想(第4版) from server!</productName><author>[美]埃克尔</author><ISBN>9787111213826</ISBN><publishDate>2007-07-01T13:33:30.782+08:00</publishDate></book>",
success: function(data, textStatus, jqXHR){
alert("以下是从服务器返回的Book对象:\n\n" + data.documentElement.outerHTML);
},
error: function(jqXHR, textStatus, errorThrown){
alert('error: ' + textStatus);
}
});
})
}
testJsonAddBook & testXmlAddBook
运行效果如下:
如果断点调试ajax,可以看到传过来的json串已经映射成Bean实例了
示例源代码下载:helloworld-rs.zip