java JAX-RS快速開發RESTful服務

dead_lee發表於2021-09-09

JAX-RS(Java API for RESTful Web Services)同樣也是JSR的一部分,詳細規範定義見  。從JAVA EE 6開始,已經內建了該技術,跟.NET中的RESTful WCF類似,只要在常規方法上使用一些簡單的註解,就可以對外暴露成RESTful 服務.

注:本文執行環境為Jboss EAP 6.x ,其它容器特別是tomcat,並未自帶JAX-RS依賴的jar包,如果採用tomcat,需要自己匯入這些相關依賴jar包。

一、最基礎的HelloWorld (無引數)

 1 @Path("/")
 2 public class MyService {
 3 
 4     final String XMLNS_NAMESPACE = "http://yjmyzz.cnblogs.com/rest/service";
 5     final String ROOT_NODE = "root";
 6 
 7     @GET
 8     @Path("/json/hello")
 9     @Produces(MediaType.APPLICATION_JSON)
10     public JAXBElement getHelloWorldJSON() {
11         JAXBElement result = new JAXBElement(new QName("",
12                 ROOT_NODE), String.class, sayHelloWorld());
13         return result;
14     }
15 。。。
16 }
1     private String sayHelloWorld() {
2         return "Hello JAX-RS!";
3     }

解釋:
@GET 表示該服務可以直接在瀏覽器位址列裡訪問(對應HTTP請求中的GET方法)

@Path 這裡用了二次,第一次是在Class上,相當服務的基地址;第二次是在方法上,表示具體某個服務方法的URL.

此外web.xml中,還需要增加以下內容:

1     
2         javax.ws.rs.core.Application
3         /rest/*
4     

@Produces 表示返回訊息的格式,配合MediaType.APPLICATION_JSON說明,本方法返回Json字串格式

以上幾項綜合起來,最終可以用類似  的地址來訪問,返回的結果為:

圖片描述

JAXBElement 類似一個包裝器(Wrapper),用於將返回物件做下json/xml格式的包裝,比如上圖中的{root:}就是它的功勞,感興趣的朋友,可以把返回類似改成最原始的String對比下差異.

如果需要返回XML格式,只需要把@Produces後面的MediaType.APPLICATION_JSON換成MediaType.APPLICATION_XML就行了,即:

1     @GET
2     @Path("/xml/hello")
3     @Produces(MediaType.APPLICATION_XML)
4     public JAXBElement getHelloWorldXML() {
5         JAXBElement result = new JAXBElement(new QName(
6                 XMLNS_NAMESPACE, ROOT_NODE), String.class, sayHelloWorld());
7         return result;
8     }

效果如下:

圖片描述

 

二、帶一個基本型別引數的HelloWorld

 1     @GET
 2     @Path("/json/hi/{name}")
 3     @Produces(MediaType.APPLICATION_JSON)
 4     public JAXBElement getHelloToNameJSON(@PathParam("name") String name) {
 5         JAXBElement result = new JAXBElement(new QName("",
 6                 ROOT_NODE), String.class, sayHelloToName(name));
 7         return result;
 8     }
 9 
10     @GET
11     @Path("/xml/hi/{name}")
12     @Produces(MediaType.APPLICATION_XML)
13     public JAXBElement getHelloToNameXML(@PathParam("name") String name) {
14         JAXBElement result = new JAXBElement(new QName(
15                 XMLNS_NAMESPACE, ROOT_NODE), String.class, sayHelloToName(name));
16         return result;
17     }
1     private String sayHelloToName(String name) {
2         return "Hello " + name + ",welcome to the world of JAX-RS!";
3     }

對比一下剛才的程式碼,有二個變化:

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類


 1 package yjmyzz.service.model;
 2 
 3 import javax.xml.bind.annotation.XmlAttribute;
 4 import javax.xml.bind.annotation.XmlElement;
 5 import javax.xml.bind.annotation.XmlRootElement;
 6  
 7 @XmlRootElement(name = "user")
 8 public class User {
 9  
10     String username;
11     String password;
12     int pin;
13  
14     @XmlElement
15     public String getUsername() {
16         return username;
17     }
18  
19     public void setUsername(String username) {
20         this.username = username;
21     }
22  
23     @XmlElement
24     public String getPassword() {
25         return password;
26     }
27  
28     public void setPassword(String password) {
29         this.password = password;
30     }
31  
32     @XmlAttribute
33     public int getPin() {
34         return pin;
35     }
36  
37     public void setPin(int pin) {
38         this.pin = pin;
39     }
40  
41 }


程式碼中加的一堆@XML開頭的註解,是為了在傳輸過程中,如果需要以XML格式傳輸,這些註解可以幫助完成 XML Obj的轉換(詳情可參見JAXB相關內容)

1     @GET
 2     @Path("/xml/user")
 3     @Produces(MediaType.APPLICATION_XML)
 4     public User getUserInXML() {
 5         return getUser("unknown");
 6     }
 7 
 8     @GET
 9     @Path("/json/user")
10     @Produces(MediaType.APPLICATION_JSON)
11     public User getUserInJSON() {
12         return getUser("unknown");
13     }


注意:這裡我們沒有使用JAXBElement對結果進行包裝,輔助方法getUser()程式碼如下

1     private User getUser(String username) {
2         User user = new User();
3         user.setUsername(username);
4         String pwd = new Long(Math.round(Math.random() * 100000)).toString();
5         user.setPassword(pwd);
6         int pin = (int) (Math.random() * 1000);
7         user.setPin(pin);
8         return user;
9     }

執行結果如下:
圖片描述

圖片描述

當然,也可以給方法增加引數,先從最基本型別的String引數耍起:

1     @GET
 2     @Path("/xml/userByName/{name}")
 3     @Produces(MediaType.APPLICATION_XML)
 4     public User getUserInXML(@PathParam("name") String username) {
 5         return getUser(username);
 6     }
 7 
 8     @GET
 9     @Path("/json/userByName/{name}")
10     @Produces(MediaType.APPLICATION_JSON)
11     public User getUserInJSON(@PathParam("name") String username) {
12         return getUser(username);
13     }

執行結果如下:
圖片描述

圖片描述

如果加上包裝器JAXBElement後,返回結果會變成什麼樣呢? 我也很好奇,so, 嘿-餵狗!

 1     @GET
 2     @Path("/xml/userByName2/{name}")
 3     @Produces(MediaType.APPLICATION_XML)
 4     public JAXBElement getUserInXML2(@PathParam("name") String username) {
 5         JAXBElement result = new JAXBElement(new QName(
 6                 XMLNS_NAMESPACE, ROOT_NODE), User.class, getUser(username));
 7         return result;
 8     }
 9 
10     @GET
11     @Path("/json/userByName2/{name}")
12     @Produces(MediaType.APPLICATION_JSON)
13     public JAXBElement getUserInJSON2(@PathParam("name") String username) {
14         JAXBElement result = new JAXBElement(new QName("",
15                 ROOT_NODE), User.class, getUser(username));
16         return result;
17     }

結果如下:

圖片描述

圖片描述

箇中差異,請自行體會

 

四、單個String引數的實體Bean注入

web服務中方法的傳入引數,不可能都是基礎型別,同樣也可以是實體Bean

1     @GET
 2     @Path("/xml/product/{name}")
 3     @Produces(MediaType.APPLICATION_XML)
 4     public Product getProductXML(@PathParam("name") Product product) {
 5         return getProductFromServer(product);
 6     }
 7 
 8     @GET
 9     @Path("/xml/product2/{name}")
10     @Produces(MediaType.APPLICATION_XML)
11     public JAXBElement getProductXML2(
12             @PathParam("name") Product product) {
13         JAXBElement result = new JAXBElement(new QName(
14                 XMLNS_NAMESPACE, ROOT_NODE), Product.class,
15                 getProductFromServer(product));
16         return result;
17     }


這裡我們用到了一個新的Bean :Product類,程式碼如下:

 1 package yjmyzz.service.model;
 2 
 3 import javax.xml.bind.annotation.XmlElement;
 4 import javax.xml.bind.annotation.XmlRootElement;
 5 
 6 @XmlRootElement(name = "product")
 7 public class Product {
 8 
 9     public Product() {
10         System.out.println("construct: Product() is called!");
11     }
12     
13     public Product(String name) {
14         System.out.println("construct: Product(String name) is called!");
15         this.productName = name;
16     }
17 
18     @XmlElement
19     public String getProductName() {
20         return productName;
21     }
22 
23     public void setProductName(String productName) {
24         this.productName = productName;
25     }
26 
27     private String productName;
28 
29 }


先提個問題:從@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:

 1 package yjmyzz.service.model;
 2 
 3 import java.util.Calendar;
 4 import java.util.Date;
 5 
 6 import javax.xml.bind.annotation.XmlElement;
 7 import javax.xml.bind.annotation.XmlRootElement;
 8 
 9 @XmlRootElement(name = "book")
10 public class Book extends Product {
11 
12     private String isbn;
13 
14     private String author;
15 
16     private Date publishDate;
17 
18     public Book() {
19         System.out.println("construct: Book() is called!");
20     }
21 
22     public static Book valueOf(String isbn) {
23         System.out.println("Book: valueOf(String isbn) is called!");
24 
25         Book book = new Book();
26         book.setISBN(isbn);
27         book.setProductName("Java程式設計思想(第4版)");
28 
29         book.setAuthor("[美]埃克爾");
30 
31         Calendar calendar = Calendar.getInstance();
32         calendar.set(2007, 6, 1);
33         book.setPublishDate(calendar.getTime());
34         return book;
35     }
36 
37     @XmlElement
38     public String getISBN() {
39         return isbn;
40     }
41 
42     public void setISBN(String isbn) {
43         this.isbn = isbn;
44     }
45 
46     @XmlElement
47     public String getAuthor() {
48         return author;
49     }
50 
51     public void setAuthor(String author) {
52         this.author = author;
53     }
54 
55     @XmlElement
56     public Date getPublishDate() {
57         return publishDate;
58     }
59 
60     public void setPublishDate(Date publishDate) {
61         this.publishDate = publishDate;
62     }
63 
64 }


再定義一個新方法:

1     @GET
2     @Path("/xml/book/{ISBN}")
3     @Produces(MediaType.APPLICATION_XML)
4     public Book getBookXML(@PathParam("ISBN") Book book) {
5         return getBookFromServer(book);
6     }

裡面有一個輔助方法,程式碼如下:

1     private Book getBookFromServer(Book book) {
2         book.setProductName(book.getProductName() + " from server!");
3         return book;
4     }


執行效果如下:

圖片描述

eclipse的控制檯資訊如下:

圖片描述

 

五、POST方式的Bean例項引數注入

前面提到的都是GET方式的web服務方法,GET方式能傳輸的資料大小有限,對於較大的引數資料,直接POST的場景更常見,下面是一個示例:

 1     @POST
 2     @Path("/json/add-book")
 3     @Produces(MediaType.APPLICATION_JSON)
 4     @Consumes(MediaType.APPLICATION_JSON)
 5     public Book addBookJSON(Book book) {
 6         return getBookFromServer(book);
 7     }
 8     
 9     
10     @POST
11     @Path("/xml/add-book")
12     @Produces(MediaType.APPLICATION_XML)
13     @Consumes(MediaType.APPLICATION_XML)
14     public Book addBookXML(Book book) {
15         return getBookFromServer(book);
16     }

解釋一下:

又遇到了一個新的註解:@Consumes(MediaType.APPLICATION_JSON) 它表示傳入的Book例項是json格式。

我們用jQuery以Ajax方式呼叫這個服務演示一下:

1         function testJsonAddBook(){
 2             $(document).ready(function(){
 3                 $.ajax({
 4                     type:"POST",
 5                     url:"rest/json/add-book",
 6                     dataType: "json",
 7                     contentType: 'application/json',
 8                     data:JSON.stringify({"productName":"Java程式設計思想(第4版)","isbn":"9787111213826","author":"[美]埃克爾","publishDate":1183270161125}),
 9                     success: function(data, textStatus, jqXHR){
10                         alert("以下是從伺服器返回的Book物件:nn" + JSON.stringify(data));
11                     },
12                     error: function(jqXHR, textStatus, errorThrown){
13                         alert('error: ' + textStatus);
14                     }
15                 });
16             })
17         }
18 
19 
20         function testXmlAddBook(){
21             $(document).ready(function(){
22                 $.ajax({
23                     type:"POST",
24                     url:"rest/xml/add-book",
25                     dataType: "xml",
26                     contentType: 'application/xml',
27                     data:"Java程式設計思想(第4版) from server![美]埃克爾97871112138262007-07-01T13:33:30.782+08:00",
28                     success: function(data, textStatus, jqXHR){
29                         alert("以下是從伺服器返回的Book物件:nn" + data.documentElement.outerHTML);
30                     },
31                     error: function(jqXHR, textStatus, errorThrown){
32                         alert('error: ' + textStatus);
33                     }
34                 });
35             })
36         }

執行效果如下:
圖片描述

圖片描述

 

如果斷點除錯ajax,可以看到傳過來的json串已經對映成Bean例項了

圖片描述

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4686/viewspace-2800436/,如需轉載,請註明出處,否則將追究法律責任。

相關文章