
Java 與 REST 的邂逅(二)JAX-RS 核心 Annotation
Java 與 REST 的邂逅(一)淺談 Jersey 及 JAX-RS << 前情 承如第一章所介紹,JAX-RS使用了annotation來描述Java Class跟Http的對應。而本章要介紹JAX-RS中最核心的幾個annotations。
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。而它的定義是一個相對路徑的概念,以下面的例子來說, 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中有個用大括號包起來的 除此之外,可以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是 再來談到@*Param的變數形態,除了
除了前述的@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通常需要回傳一個 @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資料。比較常見的有( @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建立,可以用 @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有一些法則可以參考。下面是一個表單:
這在處理request及response body(entity)時,唯有符合上面的對應才可以正確使用。也就是@Consumes要跟request body parameter符合上面的對應,而@Produces要跟return type或是 @Context因為Resource Class是POJO,所以不能像Serlvet一樣,透過parent methods取得ServletContext,也無法用override method的callback參數,取得Request及Response object。在JAX-RS中是使用Inversion of controls的設計理念,透過Inject的方式用@Context來取得ServletConfig, ServletContext, HttpServletRequest 與 HttpServletResponse的變數。跟@XXXParam一樣,可以放在instance variable或是method parameter上面。 @Path("/hello") public class HelloRS { @Context HttpServletRequest request; @Context HttpServletResponse response; @Context ServletContext context; .... } 結語本章可以說把JAX-RS最需要知道的東西都介紹了,基本上可以開始捲起袖子,動手撰寫你的Restful service,接下來的章節會介紹一些比較特別的應用或是特殊的案例。 |