Java 與 REST 的邂逅(二)JAX-RS 核心 Annotation by popcorny | CodeData
top

Java 與 REST 的邂逅(二)JAX-RS 核心 Annotation

分享:

Java 與 REST 的邂逅(一)淺談 Jersey 及 JAX-RS << 前情

承如第一章所介紹,JAX-RS使用了annotation來描述Java Class跟Http的對應。而本章要介紹JAX-RS中最核心的幾個annotations。

@PATH Resource的位置
@GET, @POST, @PUT, @DELETE 所處理的Http Method。
@Consumes 所處理的Mime Type。對應到Http Request Header的Content-Type
@Produces 可產生的Mime Type。對應到Http Request Header的Accept
@PathParam 把變數對應到@Path中所定義的參數@QueryParam把變數對應到URI中的QueryString所定義的參數
@FormParam 把變數對應到Form中所定義的參數
@HeaderParam 把變數對應到某個Header的變數
@Context 將Container的Context注射(inject)到POJO當中

Root Resource Classes

在解釋這些annotations之前,我們要先定義Root Resource Class。在JAX-RS中,我們把Resource對應到一個Class,而Root Resource Class就是處理某個Resource的Root class。它只要是一個POJO(Plain Old Java Object)的形式,並且用@Path描述resource位置即可視為Root Resource Class。然而它的method中必須至少要有一個是定義為@GET,@POST,@PUT,@DELETE或是@Path的method,而我們稱這些method為Resource method。

@Path

@Path定義resource的位置,可以用來描述class或是method。而它的定義是一個相對路徑的概念,以下面的例子來說,HelloRS就是定義一個/hello的相對路徑,而這個相對位置會跟Servlet的Webapp path以及Jersey的application path組合起來就是完整的URI位置。@Path也可以放在method之上,所代表的就是Resource method的相對位置。

package tw.com.codedata.jersey;

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

@Path("/hello")
public class HelloRS {

    @GET
    public String sayHelloWorld() {
        return "Hello world";
    }   

    @GET
    @Path("/{name}")
    public String sayHello(@PathParam("name") String name) {
        return "Hello, " + name;
    }
}

另外也發現第二個method中有個用大括號包起來的name,這是定義一個Path parameter,可以用@PathParam這個annotation來去描述這個路徑上的name的值會帶入sayHello的String name這個參數。

除此之外,可以regular expression來描述Path parameter。假使我們的name需要的是一個字元開始,後面接的是字元或數字的字串,那可以改寫成下面這樣:

@Path("/hello")
public class HelloRS {

    @GET
    @Path("/{name: [a-zA-Z][a-zA-Z0-9]*}")
    public String sayHello(@PathParam("name") String name) {
        return "Hello, " + name;
    }
}

如果沒有特別指定regular expression,可以想像預設的regular expression是"[^/]+?"。有沒有’/'開頭結果是一樣的,同樣的結尾有沒有’/'意思也一樣,是否要寫’/'就看自己的習慣。

@GET, @POST, @PUT, @DELETE

@GET,@POST,@PUT,@DELETE這些都是用來描述method為一個resource method,並且描述所處理的http method。另外還有@HEAD或是@OPTIONS,其實這兩個方法可以不需要明確實作,而可使用Jersey本身的預設實作。HTTP HEAD會去呼叫有定義@GET的resource method,但是不會回傳內容到client。而HTTP OPTIONS預設則會回傳一個WADL格式的描述語言,它有點類似WSDL,但是並不是一個標準化的語言。如果HTTP OPTIONS到的URL是對應到一個resource method,則會列出這個method所支援的參數;而如果Options對應到的是一個root resource class,則會列出這個Class中所有可支援的method參數。

@XXXParam

當某個Http Request找到對應的resource method時,通常會因為需要處理Request本身的QueryString, Header, 或是post時可能有帶form的參數等等,另外還有根據不同的Path也會有不同的處理。JAX-RS很體貼的透過annotation的方式,可以把這些資料種換成method parameter。主要有定義的是@PathParam, @QueryParam, @FormParam, @HeaderParam。另外還有一個一起搭配的@DefaultValue,顧名思義,可以在這些參數沒有對應值時給予一個預設值。下面是個範例:

    
@GET
@Path("/name")
public String sayHello(
	  @PathParam("name") String name,
	  @QueryParam("count") @DefaultValue("1") int count)
{
	StringBuffer sb = new StringBuffer();
	for(int i=0; i<count; i++)
	{
		sb.append("Hello, " + name + "\n");
	}
	return sb.toString();
}

假設url是http://localhost:8080/JerseyExample/rest/hello/CodeData?count=5,那結果就是印五次的Hello, CodeData。

再來談到@*Param的變數形態,除了String之外,還可以是:

  1. 所有的Java基礎形態
  2. 擁有單一String為constructor的Class
  3. 擁有valueOf(String)或是fromString(String)的static method的class
  4. List<T>, Set<T>或是SortedSet<T>。T必須符合前三項(當然基礎形態就是各自對應的Class)。

除了前述的@xxxParam以外,在JAX-RS 2.0也新增了@BeanParam,它允許把前面提的這些@xxxParam餵進一個Bean當中,然後把這個Bean當作resource method的參數。這讓我們可以重複使用這些參數的定義,甚至也可以方便做一些bean validation的動作。以下我們先定義一個JavaBean:

 
public class MyBean {   
   @FormParam("myData")
   private String data;

   @HeaderParam("myHeader")
   private String header;

   @PathParam("id")
   public void setResourceId(String id) {...}

   ...
}

用上面的Bean來接參數:

@Path("myresources") public class MyResources {
   @POST
   @Path("{id}")
   public void post(@BeanParam MyBean myBean) {...}

   ...
 }

Sub-resource

一個Resource class當中,有@GET,@POST,..的method,或是有@Path的method,稱之為resource method。然而有兩個比較特殊的狀況,一個是只有描述@GET,@POST,但是沒有用@Path去描述,則代表此resource method處理的Path是跟Resource class同樣路徑;另外一種特殊情況是只有@Path,但是沒有@GET,@POST等描述此method,則此類稱之為sub resource locator。

Sub-resource locator通常需要回傳一個Class<SubResourceClass>,而這個SubResourceClass就跟Root resource class類似,擁有數個resource method,但是不需要有@Path來描述class。使用上,可以把比較複雜的resource群組成一個sub-resource,放在一個class去處理。下面是個範例:

@Path("/hello")
public class HelloRS {

    @Path("/sub")
    public Class sayHelloToMySelf(){
         return SubResource.class;        
    }   

    public static class SubResource {        
         @GET
         public String get()
         {
              return "Hello, sub-resource.
         }
    }
}

@Consumes, @Produces

@Consumes@Produces分別用來描述可以接收跟產生的MIME type。

@Produces比較常用,它會用來判斷Http Header中的Accept,是否有符合@Produces中描述的MIME type。如果符合,server會回傳此MIME type的內容回去給client;如果沒有,server會吐出406(Not acceptable)。Http定義中,Header中的Accept本來就可以帶多個MIME-type,Jersey會要選擇最適合的type做回傳。而@Produces中也可以定義多個組MIME type,需要以逗號來分隔每個MIME type。

    
@GET
@Path("/html")   
@Produces({"text/html", "text/plain"})
public String sayHtmlHello()
{
	 return "<h1>hello</h1>"; 
}

@Consume是用來描述可以接收的Request body(entity),通常是用來處理POST或是PUT帶過來的request資料。比較常見的有(application/x-www-form-urlencoded)來處理html form帶過來的post資料,或是(multipart/*)做檔案上傳之用。而另外在rest api中,也常常直接傳一個(application/json)的格式當做request body。下面的範例定義一個sub resource支援POST method,並且會把傳過來的request body中的資料,串在回傳的Hello後面。

@POST
@Consumes("text/plain")
@Path("/echo")
public String sayHelloEcho(String message)
{
	return "Hello, " +message;    	
}

可以用下面的指令來做測試:

curl -X POST \
 -H "Content-Type: text/plain" \
 -d "this is from post data"  \

http://localhost:8080/JerseyExample/rest/hello/echo

@Consumes跟@Produces都可以放在Class跟Method。Class定義的會直接變成method的預設值,而method本身也可以override掉Class的定義。

回傳

前面大部份的例子都是回傳一個String當做回傳值,並且用@Produces來決定回傳的MIME type。這是JAX-RS回傳的方法之一,也是最簡單的方法。而JAX-RS還提供一個比較泛用的回傳型態,那就是Response。Response通常是用一個Response.ResponseBuilder建立,可以用Response.ok()或是Response.statue()來產生一個ResponseBuilder,並且可以用一連串的fluent style API去填入要回傳的資料,最後用build()來產生可以回傳的Response object,下面是一個例子:

@GET
public Response sayHelloWorld() {
    return Response
    .ok("Hello world")
    .type(MediaType.TEXT_PLAIN)
    .build();
}

可以新定義一個resource method會固定回傳404(not found):

@GET
@Path("/notfound")
public Response sayNotFound() {
    return Response
    .status(404)
    .type(MediaType.TEXT_PLAIN)
    .entity("resource not found").build();
}

另外一種比較特殊的回傳方法是透過Exception,在JAX-RS有定義一個WebApplicationException可以代入一個status code或是一個Response instance。而到了JAX-RS 2之後,甚至把常見的幾個常見的4XX的status code都定義了,例如NotFoundException等等。

MIME type and Java type

前面有提到@Consume跟@Produce,代表的是可以吃的Request body跟可以回傳的Response body。而Request body可以直接透過參數的方式吃進來,同理Response body可以用回傳值直接吐回去。如下面的例子:

@POST
@Consumes("text/plain")
@Path("/echo")
public String sayHelloEcho(String message)
{
    return "Hello, " + message;
}

每個resource method可以最多允許一個沒有annotation的參數,這代表的是處理request body(entity)的參數。而MIME type跟Java type有一些法則可以參考。下面是一個表單:

  • All media types (*/*)
    • byte[]
    • java.lang.String
    • java.io.Reader (inbound only)
    • java.io.File
    • javax.activation.DataSource
    • javax.ws.rs.core.StreamingOutput (outbound only)
  • XML media types (text/xml, application/xml and application/…+xml)
    • javax.xml.transform.Source
    • javax.xml.bind.JAXBElement
    • Application supplied JAXB classes (types annotated with @XmlRootElement or@XmlType)
  • Form content (application/x-www-form-urlencoded)
    • MultivaluedMap<String,String>
  • Plain text (text/plain)
    • java.lang.Boolean
    • java.lang.Character
    • java.lang.Number

這在處理request及response body(entity)時,唯有符合上面的對應才可以正確使用。也就是@Consumes要跟request body parameter符合上面的對應,而@Produces要跟return type或是Response.entity()的形態要正確對應。

@Context

因為Resource Class是POJO,所以不能像Serlvet一樣,透過parent methods取得ServletContext,也無法用override method的callback參數,取得Request及Response object。在JAX-RS中是使用Inversion of controls的設計理念,透過Inject的方式用@Context來取得ServletConfig, ServletContext, HttpServletRequestHttpServletResponse的變數。跟@XXXParam一樣,可以放在instance variable或是method parameter上面。

@Path("/hello")
public class HelloRS {
    @Context HttpServletRequest request;
    @Context HttpServletResponse response;
    @Context ServletContext context;
    ....
}

結語

本章可以說把JAX-RS最需要知道的東西都介紹了,基本上可以開始捲起袖子,動手撰寫你的Restful service,接下來的章節會介紹一些比較特別的應用或是特殊的案例。

後續 >> Java 與 REST 的邂逅(三)淺談 Jersey MVC

分享:
按讚!加入 CodeData Facebook 粉絲群

相關文章

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

關於作者

學生時代熱愛 Java 技術,然而為五斗米閃到腰跑去 IC Design House 去寫 C。這幾年重新拾起 Java 技術,並且也同時開發 Web, iOS 以及Android App,繼續另外一個閃到腰的旅程 ...

熱門論壇文章

熱門技術文章