Java与REST的邂逅之Jersey

时间:2022-06-10 19:33:24

Java REST 的邂逅(一)浅谈 Jersey JAX-RS

 

简介

 

在Web的世界中,Java从最早的Servlet/JSP,发展到JSTL/JSF,而third party也有action-based的Struts及Spring MVC,或component-based的GWT, ZK等,事实上Java的Web世界已经非常的成熟。然而这些架构主要设计是以Web Application为主要要求,但是Web的世界中还有另外一个常见的应用是Web Services。

 

设计Web Services通常有两条路可以走,一种是SOAP/WSDL等XML-Based的技术,定义是相对严谨的Web services;另外一种会是比较轻量级的以JSON为传递数据的格式,而通常也会设计成RESTful的Web Services。这两种技术在Java Community中也分别被定义成标准,分别是JAX-WS(Java API for XML Web Services)-JSR224跟JAX-RS(Java API for RESTful Web Services)-JSR311,前者在Java EE 5时被纳入,后者在Java EE 6被纳入。

 

RESTful Service由于轻量、好测试有弹性的特性,越来越被大家所喜爱。本次系列文主要是介绍JAX-RS,并且以JAX-RS的RI(Refernce Implementation) Jersey当做环境。

 

何谓 REST?

 

REST是REpresentational State Transfer的缩写,从这个缩写实在很难看出这东西是什么,然而他是一个设计理念,而这个概念也渐渐变成目前Web设计的主流。REST把所有WEB上的东西都以一个资源(Resource)去看待,并且所有资源都会有一个URI(Uniform Resource Identifier),这个概念就是们常见的网址,由于Web底层用的HTTP本身就是这样的设计,所以这个概念应该大部分的人都知道。

 

进一步去看HTTP,稍微了解的人应该都知道HTTP有几种Method,常见的有GET跟POST,而比较少见的有PUT跟DELETE。在当初设计HTTP时,有这样的设计是希望GET代表取得资源,POST代表的是新增资源,而PUT跟DELETE就分别代表更新跟移除资源,这有没有有点像数据库设计讲的 CRUD (Create,Read,Update,Delete)呢? 事实上就是如此,甚至还可以一对一的对应。但由于我们Web上层用的HTML只有定义到GET跟POST,导致我们都忘记有PUT跟DELETE的存在,另外还有HEAD/STATUS等Method,都分别定义来对资源做的动作。

 

那RESTful Web Services又是什么呢? RESTful Web Services(或称RESTful Web API)是以HTTP为基础,必且有以下三个特色

 

所有的API或是以Resource的形式存在,例如 htttp://www.example.com/products/12345

这个服务可以接受与返回某个MIME-TYPE,最常见的是JSON格式,也可以回传PNG/JPG/TXT等格式。

对资源的操作会支持各种请求方法 (例如GET, POST, PUT, DELETE)

实际案例

 

假设我们系统有个product类型的资源。那么取得所有的products:

 

1

GET http://www.example.com/products

取得某个product id的product:

 

1

GET http://www.example.com/products/12345

新增一个product:

 

1

2

3

4

POST http://www.example.com/products

{

{'name': 'foo'}, {'price': 1000}

}

更新一个proudct:

 

1

2

3

4

PUT http://www.example.com/products/12345

{

{'inventory': 5}

}

Java 与 RESTful Web Service

 

有了RESTful基本概念,那要开始介绍的是Java对RESful Web Service的解决方案:JAX-RS。

 

JAX-RS跟所有Java EE的技术一样,它只提供了技术标准,可以允许各家厂商有自己的实作,本系列用的Jersey即是实作之一。JAX-RS是架构在Java EE的Servlet之上,并且使用annotation来简化资源位置跟参数的描述,大大的减少开发RESTful Web Services的复杂度。

 

假设我们有一个简单的hello的resource,他的URI可能是:

 

http:///localhost:8080/hello/codedata

 

我们希望输出是:

 

1

Hello, codedata

那这个简单的应用的程序代码可能如下:

 

1

2

3

4

5

6

7

8

@Path("/hello")

public class HelloRS {

@GET

@Path("/{name}")

public String sayHello(@PathParam("name") String name) {

return "Hello, " + name;

}

}

看到上面简洁的程序代码,应该开始对于JAX-RS的精简感到兴奋。如果有用过Servlet的朋友应该可以想想同样的需求写在Servlet,会有多少的code在做request跟response的操作,还有对参数的处理跟型别转换。而这些复杂的动作,都可以透过JAX-RS的annotation的描述,对应到Java的class/method/parameter。而且不像Servlet必须继承一个Servlet的Base class,在JAX-RS中都是以POJO的形式存在。另外在JAX-RS也用类似Spring的injection的方式,把外部一些对象inject到对象当中,减少Class跟Class之间的耦合度。

 

Jersey 的安装步骤

 

笔者的环境是:

 

Eclipse 4.3 (Kepler) + M2E (Maven Plugin for Eclipse)

Maven 3.0.4

Tomcat 7.0.27

Jersey 2.2

1. 产生一个Maven webapp project

 

如果你是用mvn command:

 

1

2

3

4

mvn archetype:generate \

-DgroupId=tw.com.codedata.jersey \

-DartifactId=JerseyExample \

-DarchetypeArtifactId=maven-archetype-webapp

因为我是用Eclipse的M2E plugin,所以操作为「File -> New -> Other… 选Maven Project」:

 

mvn-project-1

 

找到Artifact Id = maven-archetype-webapp:

 

mvn-project-2

 

填入Project信息:

 

mvn-project-3

 

2. 产生好Maven project后,因为maven产生的webapp project不会有src/main/java这个目录,所以我们需要手动把这个目录产生出来。这点千万不要忘记。

 

3. 增加Jersey的dependency

 

打开pom.xml:

 

1

2

3

4

5

<dependency>

<groupId>org.glassfish.jersey.containers</groupId>

<artifactId>jersey-container-servlet</artifactId>

<version>2.2</version>

</dependency>

如果你跟我一样是用M2E,请点project root右键单击,Maven -> Add Dependency,填入dependency信息:

 

mvn-dep

 

4. 如果你的container可以支持Servlet 3.0,那会建议使用Servlet 3.0,所以请把src/main/webapp/WEB-INF/web.xml的内容改成是Servlet 3.0的descriptor。

 

原本是:

 

1

2

3

4

5

6

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd" >

 

<web-app>

...

</web-app>

改成:

 

1

2

3

4

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">

<display-name>Archetype Created Web Application</display-name>

</web-app>

5. 新增一个Jersey Application。此Application等同是注册一个("/rest/*")的url mapping给Jersey,完全不需要去web.xml作额外设定。

 

1

2

3

4

5

6

7

8

9

10

11

package tw.com.codedata.jersey;

 

import javax.ws.rs.ApplicationPath;

import org.glassfish.jersey.server.ResourceConfig;

 

@ApplicationPath("rest")

public class MyApplication extends ResourceConfig{

public MyApplication(){

packages("tw.com.codedata.jersey");

}

}

6. 产生我们第一个Helloworld的restful service。

 

这边我们定义了两个resource:

 

一个是/rest/hello,这个会印出"Hello world"

一个是/rest/hello/{name},这个会印出"Hello, {name}"

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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;

}

}

7. 把你的webapp跑起来,结果如下。

 

jersey-result-1

 

 

 

jersey-result-2

 

JerseyExample是webapp name,rest是Jersey的root,hello是hello resource的root,当然这些都可透过设定的方式拿掉。

 

用Application的设定方法,只能限定在Servlet 3.0的webapp(由web.xml决定,也就是我们step3做的事情),如果你的webapp无法使用Servlet 3.0,或是想要在web.xml做设定,请参考

https://jersey.java.net/documentation/2.2/user-guide.html#deployment

 

本篇先简单介绍RESTful service跟Jersey的建议安装步骤。下一篇会开始介绍Jersey/JAX-RS的核心概念。

简介

Web的世界中,Java从最早的Servlet/JSP,发展到JSTL/JSF,而third party也有action-basedStrutsSpring MVC,或component-basedGWT, ZK等,事实上JavaWeb世界已经非常的成熟。然而这些架构主要设计是以Web Application为主要要求,但是Web的世界中还有另外一个常见的应用是Web Services

设计Web Services通常有两条路可以走,一种是SOAP/WSDLXML-Based的技术,定义是相对严谨的Web services;另外一种会是比较轻量级的以JSON为传递数据的格式,而通常也会设计成RESTfulWeb Services。这两种技术在Java Community中也分别被定义成标准,分别是JAX-WS(Java API for XML Web Services)-JSR224JAX-RS(Java API for RESTful Web Services)-JSR311,前者在Java EE 5时被纳入,后者在Java EE 6被纳入。

RESTful Service由于轻量、好测试有弹性的特性,越来越被大家所喜爱。本次系列文主要是介绍JAX-RS,并且以JAX-RSRI(Refernce Implementation) Jersey当做环境。

何谓 REST?

RESTREpresentational State Transfer的缩写,从这个缩写实在很难看出这东西是什么,然而他是一个设计理念,而这个概念也渐渐变成目前Web设计的主流。REST把所有WEB上的东西都以一个资源(Resource)去看待,并且所有资源都会有一个URI(Uniform Resource Identifier),这个概念就是们常见的网址,由于Web底层用的HTTP本身就是这样的设计,所以这个概念应该大部分的人都知道。

进一步去看HTTP,稍微了解的人应该都知道HTTP有几种Method,常见的有GETPOST,而比较少见的有PUTDELETE。在当初设计HTTP时,有这样的设计是希望GET代表取得资源,POST代表的是新增资源,而PUTDELETE就分别代表更新跟移除资源,这有没有有点像数据库设计讲的 CRUD (Create,Read,Update,Delete)? 事实上就是如此,甚至还可以一对一的对应。但由于我们Web上层用的HTML只有定义到GETPOST,导致我们都忘记有PUTDELETE的存在,另外还有HEAD/STATUSMethod,都分别定义来对资源做的动作。

RESTful Web Services又是什么呢? RESTful Web Services(或称RESTful Web API)是以HTTP为基础,必且有以下三个特色

  1. 所有的API或是以Resource的形式存在,例如 htttp://www.example.com/products/12345
  2. 这个服务可以接受与返回某个MIME-TYPE,最常见的是JSON格式,也可以回传PNG/JPG/TXT等格式。
  3. 对资源的操作会支持各种请求方法 (例如GET, POST, PUT, DELETE)

实际案例

假设我们系统有个product类型的资源。那么取得所有的products

1

GET http://www.example.com/products

取得某个product idproduct

1

GET http://www.example.com/products/12345

新增一个product

1

2

3

4

POST http://www.example.com/products

{

    {'name': 'foo'}, {'price': 1000}

}

更新一个proudct

1

2

3

4

PUT http://www.example.com/products/12345

{

   {'inventory': 5}

}

Java RESTful Web Service

有了RESTful基本概念,那要开始介绍的是JavaRESful Web Service的解决方案:JAX-RS

JAX-RS跟所有Java EE的技术一样,它只提供了技术标准,可以允许各家厂商有自己的实作,本系列用的Jersey即是实作之一。JAX-RS是架构在Java EEServlet之上,并且使用annotation来简化资源位置跟参数的描述,大大的减少开发RESTful Web Services的复杂度。

假设我们有一个简单的helloresource,他的URI可能是:

http:///localhost:8080/hello/codedata

我们希望输出是:

1

Hello, codedata

那这个简单的应用的程序代码可能如下:

1

2

3

4

5

6

7

8

@Path("/hello")  

public class HelloRS {

    @GET

    @Path("/{name}")

    public String sayHello(@PathParam("name") String name) {

        return "Hello, " + name;

    }

}

看到上面简洁的程序代码,应该开始对于JAX-RS的精简感到兴奋。如果有用过Servlet的朋友应该可以想想同样的需求写在Servlet,会有多少的code在做requestresponse的操作,还有对参数的处理跟型别转换。而这些复杂的动作,都可以透过JAX-RSannotation的描述,对应到Javaclass/method/parameter。而且不像Servlet必须继承一个ServletBase class,在JAX-RS中都是以POJO的形式存在。另外在JAX-RS也用类似Springinjection的方式,把外部一些对象inject到对象当中,减少ClassClass之间的耦合度。

Jersey 的安装步骤

笔者的环境是:

  • Eclipse 4.3 (Kepler) + M2E (Maven Plugin for Eclipse)
  • Maven 3.0.4
  • Tomcat 7.0.27
  • Jersey 2.2

1. 产生一个Maven webapp project

如果你是用mvn command

1

2

3

4

mvn archetype:generate \

-DgroupId=tw.com.codedata.jersey \

-DartifactId=JerseyExample \

-DarchetypeArtifactId=maven-archetype-webapp

因为我是用EclipseM2E plugin,所以操作为「File -> New -> Other… 选Maven Project」:

Java与REST的邂逅之Jersey

找到Artifact Id = maven-archetype-webapp

Java与REST的邂逅之Jersey

填入Project信息:

Java与REST的邂逅之Jersey

2. 产生好Maven project后,因为maven产生的webapp project不会有src/main/java这个目录,所以我们需要手动把这个目录产生出来。这点千万不要忘记

3. 增加Jerseydependency

打开pom.xml

1

2

3

4

5

<dependency>

     <groupId>org.glassfish.jersey.containers</groupId>

     <artifactId>jersey-container-servlet</artifactId>

     <version>2.2</version>

</dependency>

如果你跟我一样是用M2E,请点project root右键单击,Maven -> Add Dependency,填入dependency信息:

Java与REST的邂逅之Jersey

4. 如果你的container可以支持Servlet 3.0,那会建议使用Servlet 3.0,所以请把src/main/webapp/WEB-INF/web.xml的内容改成是Servlet 3.0descriptor

原本是:

1

2

3

4

5

6

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd" >

   

<web-app>

...

</web-app>

改成:

1

2

3

4

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">

  <display-name>Archetype Created Web Application</display-name>

</web-app>

5. 新增一个Jersey Application。此Application等同是注册一个("/rest/*")url mappingJersey,完全不需要去web.xml作额外设定。

1

2

3

4

5

6

7

8

9

10

11

package tw.com.codedata.jersey;

   

import javax.ws.rs.ApplicationPath;

import org.glassfish.jersey.server.ResourceConfig;

   

@ApplicationPath("rest")

public class MyApplication extends ResourceConfig{

    public MyApplication(){

        packages("tw.com.codedata.jersey");

    }

}

6. 产生我们第一个Helloworldrestful service

这边我们定义了两个resource

一个是/rest/hello,这个会印出"Hello world"
一个是/rest/hello/{name},这个会印出"Hello, {name}"

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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;

    }

}

7. 把你的webapp跑起来,结果如下。

Java与REST的邂逅之Jersey

   

Java与REST的邂逅之Jersey

JerseyExamplewebapp namerestJerseyroothellohello resourceroot,当然这些都可透过设定的方式拿掉。

Application的设定方法,只能限定在Servlet 3.0webapp(由web.xml决定,也就是我们step3做的事情),如果你的webapp无法使用Servlet 3.0,或是想要在web.xml做设定,请参考
https://jersey.java.net/documentation/2.2/user-guide.html#deployment

本篇先简单介绍RESTful serviceJersey的建议安装步骤。下一篇会开始介绍Jersey/JAX-RS的核心概念。

 

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

承如第一章所介绍,JAX-RS使用了annotation来描述Java ClassHttp的对应。而本章要介绍JAX-RS中最核心的几个annotations

@PATH

Resource的位置

@GET, @POST, @PUT, @DELETE

所处理的Http Method

@Consumes

所处理的Mime Type。对应到Http Request HeaderContent-Type

@Produces

可产生的Mime Type。对应到Http Request HeaderAccept

@PathParam

把变量对应到@Path中所定义的参数@QueryParam把变量对应到URI中的QueryString所定义的参数

@FormParam

把变量对应到Form中所定义的参数

@HeaderParam

把变量对应到某个Header的变量

@Context

ContainerContext注射(inject)POJO当中

Root Resource Classes

在解释这些annotations之前,我们要先定义Root Resource Class。在JAX-RS中,我们把Resource对应到一个Class,而Root Resource Class就是处理某个ResourceRoot class。它只要是一个POJO(Plain Old Java Object)的形式,并且用@Path描述resource位置即可视为Root Resource Class。然而它的method中必须至少要有一个是定义为@GET,@POST,@PUT,@DELETE或是@Pathmethod,而我们称这些methodResource method

@Path

@Path定义resource的位置,可以用来描述class或是method。而它的定义是一个相对路径的概念,以下面的例子来说,HelloRS就是定义一个/hello的相对路径,而这个相对位置会跟ServletWebapp path以及Jerseyapplication path组合起来就是完整的URI位置。@Path也可以放在method之上,所代表的就是Resource method的相对位置。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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的值会带入sayHelloString name这个参数。

除此之外,可以regular expression来描述Path parameter。假使我们的name需要的是一个字符开始,后面接的是字符或数字的字符串,那可以改写成下面这样:

1

2

3

4

5

6

7

8

9

@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会去呼叫有定义@GETresource 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,顾名思义,可以在这些参数没有对应值时给予一个默认值。下面是个范例:

1

2

3

4

5

6

7

8

9

10

11

12

13

@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();

}

假设urlhttp://localhost:8080/JerseyExample/rest/hello/CodeData?count=5,那结果就是印五次的Hello, CodeData

再来谈到@*Param的变量形态,除了String之外,还可以是:

  1. 所有的Java基础形态
  2. 拥有单一StringconstructorClass
  3. 拥有valueOf(String)或是fromString(String)static methodclass
  4. List<T>, Set<T>或是SortedSet<T>T必须符合前三项(当然基础形态就是各自对应的Class)

除了前述的@xxxParam以外,在JAX-RS 2.0也新增了@BeanParam,它允许把前面提的这些@xxxParam喂进一个Bean当中,然后把这个Bean当作resource method的参数。这让我们可以重复使用这些参数的定义,甚至也可以方便做一些bean validation的动作。以下我们先定义一个JavaBean

1

2

3

4

5

6

7

8

9

10

11

12

public class MyBean {  

   @FormParam("myData")

   private String data;

   

   @HeaderParam("myHeader")

   private String header;

   

   @PathParam("id")

   public void setResourceId(String id) {...}

   

   ...

}

用上面的Bean来接参数:

1

2

3

4

5

6

7

@Path("myresources") public class MyResources {

   @POST

   @Path("{id}")

   public void post(@BeanParam MyBean myBean) {...}

   

   ...

 }

Sub-resource

一个Resource class当中,有@GET,@POST,..method,或是有@Pathmethod,称之为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去处理。下面是个范例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@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-typeJersey会要选择最适合的type做回传。而@Produces中也可以定义多个组MIME type,需要以逗号来分隔每个MIME type

1

2

3

4

5

6

7

@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后面。

1

2

3

4

5

6

7

@POST

@Consumes("text/plain")

@Path("/echo")

public String sayHelloEcho(String message)

{

    return "Hello, " +message;     

}

可以用下面的指令来做测试:

1

2

3

4

5

curl -X POST \

 -H "Content-Type: text/plain" \

 -d "this is from post data"  \

   

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

@Consumes@Produces都可以放在ClassMethodClass定义的会直接变成method的默认值,而method本身也可以overrideClass的定义。

回传

前面大部份的例子都是回传一个String当做回传值,并且用@Produces来决定回传的MIME type。这是JAX-RS回传的方法之一,也是最简单的方法。而JAX-RS还提供一个比较泛用的回传型态,那就是ResponseResponse通常是用一个Response.ResponseBuilder建立,可以用Response.ok()或是Response.statue()来产生一个ResponseBuilder,并且可以用一连串的fluent style API去填入要回传的数据,最后用build()来产生可以回传的Response object,下面是一个例子:

1

2

3

4

5

6

7

@GET

public Response sayHelloWorld() {

    return Response

    .ok("Hello world")

    .type(MediaType.TEXT_PLAIN)

    .build();

}

可以新定义一个resource method会固定回传404(not found)

1

2

3

4

5

6

7

8

@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之后,甚至把常见的几个常见的4XXstatus code都定义了,例如NotFoundException等等。

MIME type and Java type

前面有提到@Consume@Produce,代表的是可以吃的Request body跟可以回传的Response body。而Request body可以直接透过参数的方式吃进来,同理Response body可以用回传值直接吐回去。如下面的例子:

1

2

3

4

5

6

7

@POST

@Consumes("text/plain")

@Path("/echo")

public String sayHelloEcho(String message)

{

    return "Hello, " + message;

}

每个resource method可以最多允许一个没有annotation的参数,这代表的是处理request body(entity)的参数。而MIME typeJava 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

这在处理requestresponse body(entity)时,唯有符合上面的对应才可以正确使用。也就是@Consumes要跟request body parameter符合上面的对应,而@Produces要跟return type或是Response.entity()的形态要正确对应。

@Context

因为Resource ClassPOJO,所以不能像Serlvet一样,透过parent methods取得ServletContext,也无法用override methodcallback参数,取得RequestResponse object。在JAX-RS中是使用Inversion of controls的设计理念,透过Inject的方式用@Context来取得ServletConfig, ServletContext,HttpServletRequest  HttpServletResponse的变数。跟@XXXParam一样,可以放在instance variable或是method parameter上面。

1

2

3

4

5

6

7

@Path("/hello")

public class HelloRS {

    @Context HttpServletRequest request;

    @Context HttpServletResponse response;

    @Context ServletContext context;

    ....

}

结语

本章可以说把JAX-RS最需要知道的东西都介绍了,基本上可以开始卷起袖子,动手撰写你的Restful service,接下来的章节会介绍一些比较特别的应用或是特殊的案例。

Java REST 的邂逅(三)浅谈 Jersey MVC

简介

前面两篇介绍以Jersey来做RESTful Web ServiceJersey的轻量简洁但是强大的功能,对于RESTful的应用可以写得非常简单漂亮。这时候我们不难会继续想说用Jersey来写RESTful Web Application有没有搞头,这绝对有搞头!!! 而且事实上也非常的简单好用。

Jersey本身,它定义了一个Viewableclass,当resource method回传的是Viewable,则代表我们想要把结果转到View上去执行,这不就是我们常见的MVC pattern?没错,在Jersey当中,我们可以把resource method当作controller,在其中根据client request,产生对应的model,再dispatchview去做呈现。因此如果想要选择一个比较轻量级的MVC framework,但是又不想用Servlet当作Controller那么阳春,那Jersey会是一个很好的选择。

传统纯粹用Servlet打造的MVC Pattern
Java与REST的邂逅之Jersey

Jersey来做MVC的实作:
Java与REST的邂逅之Jersey

设定Jersey MVC

首先你需要加上这两个Jersey Maven DependencyJersey目前提供JSP或是FreeMarker两种template的实作,在此篇我们就以比较常用的JSP作为例子。

<dependency> 
    <groupId>org.glassfish.jersey.ext</groupId> 
    <artifactId>jersey-mvc</artifactId> 
    <version>2.5</version> 
</dependency> 
<dependency> 
    <groupId>org.glassfish.jersey.ext</groupId> 
    <artifactId>jersey-mvc-jsp</artifactId> 
    <version>2.5</version> 
</dependency> 

再来是设定web.xml。根据官网的文件,若要要使用Jersey MVC并且使用JSP当作template,必须在web.xml中明确用Filter的方式来设定Jersey,这个目前我还不清楚原因,官网这样写我们就照着做。

<web-app xmlns="http://java.sun.com/xml/ns/javaee" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
 http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
 version="3.0"> 
 <display-name>Jersey MVC Sample</display-name> 

 

 <!-- Jersey --> 
 <filter> 
 <filter-name>jersey</filter-name> 
 <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class> 
 <init-param> 
 <param-name>javax.ws.rs.Application</param-name> 
 <param-value>tw.com.codedata.jersey.MyApplication</param-value> 
 </init-param> 
 <init-param> 
 <param-name>jersey.config.servlet.filter.forwardOn404</param-name> 
 <param-value>true</param-value> 
 </init-param> 
 </filter> 
 <filter-mapping> 
 <filter-name>jersey</filter-name> 
 <url-pattern>/*</url-pattern> 
 </filter-mapping> 
</web-app> 

再来是在我们的JerseyApplication class中,新增有关MVC的设定。其中JspMvcFeature是用来描述要使用Jersery MVC这个Feature,并且已JSP当作Template Engine。而JspMvcFeature.TEMPLATES_BASE_PATH是用来描述JSProot的路径,这在后面的Viewable的建构子当中所带的template path就会根据此路径来找到对应的template

package tw.com.codedata.jersey; 

 

import org.glassfish.jersey.server.ResourceConfig; 
import org.glassfish.jersey.server.mvc.jsp.JspMvcFeature; 

 

public class MyApplication extends ResourceConfig{ 
 public MyApplication(){ 
 packages("tw.com.codedata.jersey.controller"); 
 register(JspMvcFeature.class); 
 property(JspMvcFeature.TEMPLATES_BASE_PATH, "/WEB-INF/jsp"); 
 } 
} 

再来来改写我们的Resource(Controller) class。在Resource method中我们回传一个Viewable代表我们想把结果丢到View上做呈现。建构子的第一个参数是Template path,第二个参数是丢给Templatemodel。注意template path不需要指定.jspJersey会根据你使用的template engine来找到对应的扩展名。

package tw.com.codedata.jersey.controller; 

 

import java.util.HashMap; 

 

import javax.ws.rs.DefaultValue; 
import javax.ws.rs.GET; 
import javax.ws.rs.Path; 
import javax.ws.rs.QueryParam; 

 

import org.glassfish.jersey.server.mvc.Viewable; 

 

@Path("/hello") 
public class HelloController { 
 @GET 
 public Viewable sayHello(@QueryParam("name") @DefaultValue("World") String name) { 
 HashMap model = new HashMap(); 
 model.put("name", name); 
 return new Viewable("/hello", model); 
 } 
} 

Jerseymodel丢到JSP后,它会产生一个it的变量,这就是我们刚刚丢过来的model对象。我们可以再用JSP相关的技术,例如JSTL或是Expression Language把结果呈现成HTML。下面这个档案放在WEB-INF/jsp/hello.jsp

<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 
<html> 
<head> 
</head> 
<body> 
Hello, <c:out value="${it.name}" /> 
</body> 
</html> 

输出的结果
Java与REST的邂逅之Jersey

结论

Jersey针对RESTful提供了需要的GET,POST,PUT,DELETEannotation来快速对应method成为一个resource method。并且有各式各样的@XXXParam来处理来自Http Request的各种参数。然而我们也可以把同样的概念带到RESTful Web Application,并且优雅地用Viewable,来让Controller可以把结果丢到View上去作呈现。

当然,如果您已经对Spring很熟悉了,并且已经大量的使用Spring的功能,你可以选择使用Spring MVC,这两个是很类似的东西。但如果你想选择比较轻量级,必且想使用JAX-RS提供的APIJersey MVC提供给你的是一个比较轻量但功能也足够应付大部份情况的解决方案。