1. 概述

REST 架构风格已经流行多年,至今仍备受关注。在 Java 中实现 RESTful API 有多种方式:可以使用 Spring、JAX-RS,或者如果你足够自信,甚至可以直接编写原生 Servlet。核心在于暴露 HTTP 方法的能力,其余都是如何组织这些方法以及如何引导客户端调用你的 API。

正如标题所示,本文将聚焦 JAX-RS。但“只是一个 API”是什么意思?这意味着本文旨在澄清 JAX-RS 与其实现之间的混淆,并展示一个规范的 JAX-RS Web 应用示例。

2. 在 Java EE 中的定位

JAX-RS 本质上只是一个规范——由 Java EE 提供的一组接口和注解。当然,我们还有具体实现,其中最知名的是 RESTEasyJersey

如果你决定构建一个符合 JEE 标准的应用服务器,Oracle 会要求你的服务器必须为部署的应用提供 JAX-RS 实现。这就是为什么它被称为 Java Enterprise Edition 平台

另一个规范与实现的典型例子是 JPAHibernate

2.1. 轻量级 War 包

这对我们开发者有什么帮助?帮助在于我们的部署包应该尽可能精简,让应用服务器提供所需库。开发 RESTful API 时同样适用:最终产物不应包含任何 JAX-RS 实现细节。

正确做法:依赖服务器提供的实现
错误做法:将实现库打包进 War

当然,我们可以自行提供实现(这里 有 RESTEasy 教程)。但这样就不能再称我们的应用为“Java EE 应用”了。如果某天有人说:“该切换到 Glassfish 或 Payara 了,JBoss 太贵了!”,虽然理论上可行,但过程会非常痛苦。

自行提供实现时,必须确保服务器排除其内置实现——通常需要在部署包中添加专有 XML 文件。这种文件往往充斥着各种无人理解的标签和指令,除了三年前离职的开发者。

2.2. 始终了解你的服务器

我们强调要善用平台提供的资源。在选择服务器前,至少在生产环境中,应该了解它提供的 JAX-RS 实现细节(名称、厂商、版本和已知缺陷)。例如:

  • Glassfish 内置 Jersey
  • Wildfly/JBoss 内置 RESTEasy

这需要前期投入研究时间,但只需在项目启动或迁移时做一次。

3. 实战示例

想快速上手 JAX-RS?最简单的方式是创建 Maven Web 项目,在 pom.xml 添加以下依赖:

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>7.0</version>
    <scope>provided</scope>
</dependency>

我们选择 JavaEE 7,因为已有大量应用服务器支持它。这个 API jar 包含了所需的注解(位于 javax.ws.rs 包)。为什么 scope 是 "provided"?因为编译时需要它,但运行时由服务器提供。

添加依赖后,首先编写入口类:一个继承 javax.ws.rs.core.Application 的空类,并用 javax.ws.rs.ApplicationPath 注解:

@ApplicationPath("/api")
public class RestApplication extends Application {
}

我们定义了入口路径 /api,所有资源路径都会以此作为前缀。

接下来看资源类示例:

@Path("/notifications")
public class NotificationsResource {
    @GET
    @Path("/ping")
    public Response ping() {
        return Response.ok().entity("Service online").build();
    }

    @GET
    @Path("/get/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getNotification(@PathParam("id") int id) {
        return Response.ok()
          .entity(new Notification(id, "john", "test notification"))
          .build();
    }

    @POST
    @Path("/post/")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response postNotification(Notification notification) {
        return Response.status(201).entity(notification).build();
    }
}

包含三个接口:

  • 简单的 ping 接口用于检测服务状态
  • GET 接口获取 Notification(普通 POJO,含属性和 getter/setter)
  • POST 接口创建 Notification

将此 War 部署到任何支持 JEE7 的应用服务器,以下命令将生效:

curl http://localhost:8080/simple-jaxrs-ex/api/notifications/ping/

curl http://localhost:8080/simple-jaxrs-ex/api/notifications/get/1

curl -X POST -d '{"id":23,"text":"lorem ipsum","username":"johana"}' 
  http://localhost:8080/simple-jaxrs-ex/api/notifications/post/ 
  --header "Content-Type:application/json"

其中 simple-jaxrs-ex 是 Web 应用的上下文根。

⚠️ 踩坑提示:此代码在 Glassfish 4.1.0 和 Wildfly 9.0.1.Final 测试通过。但后两个命令在 Glassfish 4.1.1 会失败,因为这个 bug。该版本存在 JSON 序列化已知问题(若必须使用此版本,需自行处理 JSON 编组)。

4. 总结

记住关键点:JAX-RS 是强大的 API,你需要的绝大多数功能(甚至全部)已由 Web 服务器实现。无需把部署包变成难以管理的库堆。

本文展示了简单示例,实际场景可能更复杂。比如你可能需要自定义编组器。遇到这种情况,优先寻找基于 JAX-RS 的解决方案,而非 Jersey、RESTEasy 等具体实现。很可能只需一两个注解就能解决问题。


原始标题:JAX-RS is just an API!

« 上一篇: Spring Retry 使用教程