Jersey 开发RESTful(七)Jersey快速入门

【原创文章,转载请注明原文章地址,谢谢!】

在之前的文章中,我们已经看到了SpringMVC开发REST应用的方式。但是注意一点,SpringMVC在开发REST应用时,是不支持JSR311/JSR339标准的。如果想要按照标准行事,最常用的实现了这两个标准的框架就是Jersey和CxF了。但是,因为Jersey是最早的实现,也是JSR311参考的主要对象,所以,可以说Jersey就是事实上的标准(类似Hibernate是JPA的事实上的标准),也是现在使用最为广泛的REST开发框架之一。

Hello Jersey

首先要理解,Jersey是一个REST框架,既然是REST框架,那自然提供了REST服务相关的一切东西。那么我们在使用的时候,自然可以和SpringMVC做对比。但是,因为是一个全新的框架,所以自然细节和相关概念会比SpringMVC实现RESTful要多很多,这点需要注意。

Jersey的一大特点就是,基于Jersey的REST应用,可以运行在Servlet环境下面,也可以脱离该环境。下面就分别使用两种方式来完成Jersey的Hello world。

基于Servlet容器

  • 创建一个基于Maven的web项目,在pom.xml中只需要引入一个Jersey的Servlet容器依赖:
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>2.25</version>
</dependency>
  • 再引入maven的tomcat插件:
<plugin>
    <groupId>org.apache.tomcat.maven</groupId>
    <artifactId>tomcat7-maven-plugin</artifactId>
    <version>2.2</version>
    <configuration>
        <port>8082</port>
        <path>/</path>
    </configuration>
</plugin>

在依赖树中我们可以看到,Jersey的一些核心库被引入了进来:


image.png

其中,container-servlet-core就是基于Servlet容器的jersey核心库,common是jersey的核心基础包,server是jersey核心服务包,在server中还依赖jersey-client包,这个是jersey的客户端包(可以使用该包非常方便的消费REST服务和测试);

  • 修改web.xml,添加一个Jersey的核心servlet(简单理解为MVC框架中的前端控制器即可):
<servlet>
<servlet-name>JerseyServletContainer</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
 <init-param>
      <param-name>jersey.config.server.provider.packages</param-name>
      <param-value>cn.wolfcode.jersey</param-value>
 </init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>JerseyServletContainer</servlet-name>
    <url-pattern>/webapi/*</url-pattern>
</servlet-mapping>

其中注意ServletContainer即为核心控制器,而jersey.config.server.provider.packages参数一看就是用于扫描jersey中REST服务类所在的包(可以简单理解为SpringMVC中的component-scan);

  • 完成REST服务类
package cn.wolfcode.jersey._01hello;

@Path("hello")
public class HelloService {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hi(){
        return "hello jersey";
    }
}

简单解释一下这段代码:
1,注意类是放在cn.wolfcode.jersey._01hello包中,而这个包是我们jersey.config.server.provider.packages参数中配置的包的子包,即该类是能够被扫描到的;
2,在类上面添加了@Path("hello"),代表资源根路径为hello,类似于SpringMVC中在类上面添加@RequestMapping("hello");
3,方法hi上面添加了两个标签,@GET标签代表该方法接受GET类型请求,类似于SpringMVC中的@GetMapping标签;
4,@Produces标签代表该方法的响应MIME类型为text/plain,类似于@RequestMapping中的produces属性;
5,该方法返回String,这个String值Jersey会自动按照text/plain格式输出。

  • 启动tomcat,请求/webapi/hello,得到正确的响应:


    image.png

使用内置容器

Jersey提供了在SE环境下的部署,即使用内置的服务器来发布REST服务。提供了对Jetty,JDK HttpServer,Grizzly HttpServer,和一个内置的Simple HttpServer来部署。下面简单来看一下使用Jetty发布Jersey服务的例子:

  • maven中添加依赖
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-jetty-http</artifactId>
    <version>2.21</version>
</dependency>

该依赖里面包含了jetty相关的jar包及jersey-jetty相关发布依赖;

  • 创建一个Application类,用于设置发布环境:
package cn.wolfcode.jersey;
public class RestApplication extends ResourceConfig {
    public RestApplication(){
        this.packages("cn.wolfcode.jersey");
    }
}

注意:
1,我们的RestApplication类继承了ResourceConfig类,ResourceConfig是Jersey中用于配置应用资源的类,在Jersey中,把所有提供REST服务的类,都称为资源类;
2,ResourceConfig类继承了Application类,这是Jersey中一个非常基础的类,用于定义一个JAX-RS应用的基础组件。
该类实现了org.glassfish.jersey.server.ServerConfig接口,该接口用于注册应用中的资源组件;
该类还实现了javax.ws.rs.core.Configurable接口,该接口用于向当前应用上下文中注册一些必要的组件和元信息(比如后面我们要介绍的Provider)。
3,在RestApplication类的构造方法中,我们调用了packages方法注册了扫描资源类的基础包;

  • 发布应用
public class App {

public static void main(String[] args) {
    JettyHttpContainerFactory.createServer(URI.create("http://localhost:8082/"), new RestApplication());
}
}

新建一个App类,在该类的main方法中,使用包装好的JettyHttpContainerFactory,来发布一个应用。第一个参数很简单,就是发布的url地址,第二个参数就是传入一个我们创建的应用配置对象。
注意:使用jetty发布的时候,不能有虚拟路径(必须发布在根路径上)

  • 使用普通Java Application执行main方法,请求localhost:8082/hello,得到正确的响应:


    image.png

至此,使用两种方式发布Jersey的Hello World应用完成。

Jersey基础应用

下面来介绍Jersey的一些基本使用方式。做一个入门概念,注意,再次强调,Jersey的使用和SpringMVC对比着学习,会非常轻松。

资源设置

Jersey中使用@Path注解来设置资源,也支持资源的继承和路径参数,下面给出一个示例代码:

@Path("path")
public class PathRest {

/**
 * 映射url中匹配的占位符
 * 
 * @param id
 * @return
 */
@GET
@Path("{id}")
public String pathParam(@PathParam("id") Long id) {
    System.out.println(this);
    System.out.println(id);
    return "success";
}
}

简单解释:
1,PathRest类上面的@Path("path")限制了资源根路径为/path,而pathParam方法上的@Path("{id}")限制了子资源请求路径为/path/{id},类似于SpringMVC中的@RequestMapping("{id}")。
2,在请求方法中,使用@PathParam("id")完成从url中对资源占位符的解析,并同样支持类型转化,相当于SpringMVC中的@PathVariable标签;

JSON和XML

在Jersey中返回JSON类型和XML类型是非常简单的事情,而且功能比SpringMVC更强。我们先来简单的看看怎么使用(下面的案例使用Servlet容器部署的方式,使用Jetty的方式需要额外的操作)。

  • 添加JSON和XML的pom.xml依赖
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>2.25</version>
</dependency>
    
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.25</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.6</version>
    <scope>provided</scope>
</dependency>

添加了jersey-media-json-jackson这个依赖包,使用Jackson来完成json和xml的转化。

  • 创建一个Department对象:
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement
public class Department {
    private Long id;
    private String name;
}

注意,为了支持XML的生成,我们仍然添加了@XmlRootElement标签。

  • 资源类代码:
@Path("dept")
public class DepartmentRest {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public List<Department> list() {
        List<Department> dept = new ArrayList<>();
        dept.add(new Department(1L, "dept1"));
        dept.add(new Department(2L, "dept2"));
        return dept;
    }
}

注意:
1,@GET标签:说明该资源方法是使用get方式请求;
2,@Produces标签:代表该资源方法生产的响应MIME格式为application/json;
3,@Consumes标签:代表该资源方法能够接受请求MIME类型为application/json;
4,在方法中,直接返回department对象的列表;

  • 请求GET /dept


    image.png

可以看到,Jersey会自动的根据@Produces产生的数据来生成响应内容。我们再来看看XML的生成方式,只需要在资源类中增加一个方法:

@GET
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_XML)
public List<Department> listXml() {
    List<Department> dept = new ArrayList<>();
    dept.add(new Department(1L, "dept1"));
    dept.add(new Department(2L, "dept2"));
    return dept;
}

可以看到,唯一的区别就是@Produces和@Consumes的类型设置为application/xml。
这次,再请求的时候,注意我们需要设置客户端的Accepts为application/xml:


image.png

得到的请求结果为:


image.png

正确得到XML的结果。

  • 合并
    在上面的例子中,我们是通过两个资源方法来完成JSON和XML格式的内容输出,其实Jersey中提供了更合理的方式,把两段代码合并起来:
@GET
@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
@Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public List<Department> list() {
    List<Department> dept = new ArrayList<>();
    dept.add(new Department(1L, "dept1"));
    dept.add(new Department(2L, "dept2"));
    return dept;
}

可以看到,我们在@Produces和@Consumes中分别设置了我们要支持的application/xml和application/json格式,在请求的时候,分别设置Accepts为application/xml或者application/json,就可以得到JSON或者XML格式了!!这点是SpringMVC做不到的,而且更直接的反应出REST的概念!

请求类型设置

每个方法接受固定的请求类型,这个也是REST的基本需求之一。在SpringMVC中,通过@RequestMapping(method=XXX)来设置。在Jersey中,提供了一些更为简单的标签来设置,下面提供一组CRUD操作示例:

@GET //1
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_XML)
public List<Department> list() {
    List<Department> dept = new ArrayList<>();
    dept.add(new Department(1L, "dept1"));
    dept.add(new Department(2L, "dept2"));
    return dept;
}

@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_XML)
public Department get(@PathParam("id") Long id) {
    return new Department(id, "dept2");
}

@POST //2
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_XML)
public Department save(@FormParam("name") String name) {
    Department d = new Department(1L, name);
    return d;
}

@PUT //3
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_XML)
public Department update(@PathParam("id") Long id, @FormParam("name") String name) {
    Department d = new Department(id, name);
    return d;
}

@DELETE //4
@Path("{id}")
@Consumes(MediaType.APPLICATION_XML)
public void delete(@PathParam("id") Long id) {
    System.out.println("删除部门:" + id);
}

为了不引入更多额外的内容,只展示了一个简单的CRUD。其中:
1,@GET注解之前已经提到过,表示接受GET类型请求,类似@GetMapping;
2,@POST,表示接受POST类型请求,类似@PostMapping,或者@RequestMapping(method=RequestMethod.POST);其次注意,在save方法参数列表中出现了@FormParam("name"),代表name参数映射请求的form表单中的name名称的字段值;关于参数绑定,Jersey还是提供了相当多的注解来映射,后面再介绍。
3,@PUT,表示接受POST类型请求,类似@RequestMapping(method=RequestMethod.PUT);
4,@DELETE,表示接受DELETE类型请求,类似@RequestMapping(method=RequestMethod.DELETE);
5,另外,Jersey还提供了@HEAD,@OPTIONS,看名字对应的就是HEAD和OPTIONS两种请求方式,但是需要注意的一点就是Jersey没有提供针对PATCH 和 TRACE这两种请求方式的对应注解。

小结,至此,最基础的Jersey的使用快速的了解的一下。可以看到,Jersey确实在REST语义方面的支持比SpringMVC还要简单和直接,的的确确是一个REST服务开发的非常优秀的框架。后面针对Jersey的各个部分做详细介绍。

WechatIMG7.jpeg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335