Java与REST的邂逅之Jersey

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-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为基础,必且有以下三个特色

  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 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」:

找到Artifact Id = maven-archetype-webapp:

填入Project信息:

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信息:

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/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 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跑起来,结果如下。

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的核心概念。

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

承如第一章所介绍,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的相对位置。


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


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

}

假设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:


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,或是有@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去处理。下面是个范例:


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-type,Jersey会要选择最适合的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都可以放在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,下面是一个例子:


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之后,甚至把常见的几个常见的4XX的status 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 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 [email protected])
  • 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,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 Service,Jersey的轻量简洁但是强大的功能,对于RESTful的应用可以写得非常简单漂亮。这时候我们不难会继续想说用Jersey来写RESTful Web Application有没有搞头,这绝对有搞头!!! 而且事实上也非常的简单好用。

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

传统纯粹用Servlet打造的MVC Pattern:

用Jersey来做MVC的实作:

设定Jersey MVC

首先你需要加上这两个Jersey Maven Dependency。Jersey目前提供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>

再来是在我们的Jersey的Application class中,新增有关MVC的设定。其中JspMvcFeature是用来描述要使用Jersery MVC这个Feature,并且已JSP当作Template Engine。而JspMvcFeature.TEMPLATES_BASE_PATH是用来描述JSP的root的路径,这在后面的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,第二个参数是丢给Template的model。注意template path不需要指定.jsp,Jersey会根据你使用的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);
    }
}

在Jersey把model丢到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>

输出的结果

结论

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

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

时间: 2024-10-03 13:46:37

Java与REST的邂逅之Jersey的相关文章

Java上传文件夹(Jersey)

背景介绍:公司要在CMS系统上为运营人员提供一个功能供运营人员将做好的活动页面上传到阿里云存储上,上传的内容为一个文件夹,文件夹内部有.html网页,JS文件夹下有JS文件,CSS文件夹下有样式表,Images文件夹下有多张图片,具体的目录接口如下: 要在网页上将整个文件夹进行上传,下面介绍下我整个实现的过程. 项目架构,前端使用的JSP,后端使用的Jersey,一个类似WebService的技术. 直接上代码,前端JSP文件: <%@ page language="java"

如何在java REST API中用GZip和Jersey压缩相应

有许多情景当你的REST api提供的相应是非常长的,并且我们都知道传递速度和贷款在移动设备/网络上是多重要.当开发支持REST apis的移动app的时候,我认为首要的性能最优化的点就是需要解决.猜猜是什么?因为响应式文本,因此我们能压缩这些文本.而且随着当前的只能手机和平板的能力,在客户端解压文本应该不是个大问题...因此在这篇文章中,如果你使用java的Jersey构建它,我将介绍你怎么能有选择性的压缩REST API响应,这个Jersey事JAX-RS的映射实现(还有更多)... 1.J

Java实现Restful框架Jersey学习

Java与REST的邂逅(一):浅谈Jersey及JAX-RS Java与REST的邂逅(二):JAX-RS核心Annotation Java与REST的邂逅(三):浅谈Jersey MVC Java实现Restful框架Jersey学习

用Jersey为Android客户端开发Restful Web Service

平时在做Android客户端的时候经常要与服务器之间通信,客户端通过服务端提供的接口获取数据,然后再展示在客户端的界面上,作为Android开发者,我们平时更多的是关注客户端的开发,而对服务端开发的关注相对较少,但是如果我们要自己一个人设计并开发一套完整的系统,我们就必须同时具备客户端和服务端的开发知识,而且我认为同时掌握客户端和服务端的开发技术还是很有用处的,不仅有助于提升我们的架构知识和能力,而且还……你懂得!身边一些做WEB开发的朋友很多都说为客户端开发接口和单纯地做WEB项目并没有很大的

Java Jersey2使用总结

原文  http://blog.segmentfault.com/lenbo_ma/1190000000495321 主题 Java 前言 在短信平台一期工作中,为便于移动平台的开发,使用了Java Jersey框架开发RESTFul风格的Web Service接口.在使用的过程中发现了一些问题并积累了一些项目经验,做了一下总结,便于个人成长,同时也希望对有需要的同仁有好的借鉴和帮助. 简介 Jersey是JAX-RS(JSR311)开源参考实现用于构建 RESTful Web service,

用 NetBeans 快速开发 Java JAX-RS RESTful 服务

有很多IDE可以开发Java RESTful服务,Eclipse.NetBeans等,个人偏好使用NetBeans,本文介绍使用NetBeans开发的入门步骤. <理解RESTful架构>.<RESTful API 设计指南>.<RESTful API 设计最佳实践>这三篇文章是介绍RESTful架构比较经典的文章,推荐对RESTful有兴趣的可以读一下. 我也整理的阅读笔记在GitHub上:https://github.com/yulongyz/Reading/blo

Java注解教程:自定义注解示例,利用反射进行解析

Java注解能够提供代码的相关信息,同时对于所注解的代码结构又没有直接影响.在这篇教程中,我们将学习Java注解,如何编写自定义注解,注解的使用,以及如何使用反射解析注解. 注解是Java 1.5引入的,目前已被广泛应用于各种Java框架,如Hibernate,Jersey,Spring.注解相当于是一种嵌入在程序中的元数据,可以使用注解解析工具或编译器对其进行解析,也可以指定注解在编译期或运行期有效. 在注解诞生之前,程序的元数据存在的形式仅限于java注释或javadoc,但注解可以提供更多

Jersey+Spring 实现rest 接口 服务调用

下载地址:http://download.java.net/maven/2/com/sun/jersey/contribs/jersey-spring/ 或官网 https://maven.java.net/content/repositories/releases/com/sun/jersey/contribs/jersey-spring/1.9/ 所需包: jersey-client-1.8.jar jersey-core-1.8.jar jersey-json-1.8.jar jersey

Jersey写Restful接口获取参数的问题

缘起 工作时使用java开发服务器后台,用Jersey写Restful接口,发现有一个Post方法始终获取不到参数,查了半天,发现时获取参数的注释不太对,将@formparam写成了@queryparam,发现了这个改过来就好了,顺便整理了一下不同参数的作用. 简述 获取URI的参数 获取Get请求的参数 获取Post类型的参数 添加参数默认值 获取Map参数 1.@PathParam 使用该注释获取参数时可以获取URI中制定规则的参数 例如: 当浏览器请求 http://localhost:8