当前位置: 首页 > news >正文

还在用PUT更新局部数据?Jakarta REST 4.0 的“合并补丁”,优雅!

图片

Jakarta REST 4.0 是 Jakarta EE 11 中的一次重要更新,其中大部分工作都集中在内部整理(housekeeping)上。例如,在现代化 Jakarta REST 的 TCK(技术兼容性套件)方面付出了巨大的努力。此外,新版本还移除了对 ManagedBean 和 JAXB 规范的支持。

对于开发者而言,有几个值得注意的 API 变更:

  • • 新增了一些便捷的方法来检查请求头的值,特别是那些包含由令牌分隔的列表的请求头,包括 HttpHeaders#containsHeaderStringClientRequestContext#containsHeaderStringClientResponseContext#containsHeaderStringContainerRequestContext#containsHeaderString 和 ContainerResponseContext#containsHeaderString

  • • 新增了一个方法 UriInfo#getMatchedResourceTemplate,用于检索当前请求所有匹配路径的 URI 模板。

  • • 增加了对 JSON Merge Patch 的支持

前两个是较小的改进。让我们来仔细看看 JSON Merge Patch。

JSON Merge Patch 简介

JSON Merge Patch 在 RFC 7386 中被定义如下:

本规范定义了 JSON Merge Patch 格式及其处理规则。Merge Patch 格式主要旨在与 HTTP PATCH 方法结合使用,作为一种描述对目标资源内容进行一系列修改的方式。

考虑以下示例 JSON 文档:

{"title": "我的第二篇文章","author": {"givenName": "Hantsy","familyName": "Bai"},"tags": ["second", "article"],"content": "这是我第二篇文章的内容"
}

假设你想把 tags 更新为 ["JAX-RS", "RESTEasy", "Jersey"],并把 author 改为 {"givenName": "Jack", "familyName": "Ma"}。你可以发送一个这样的请求:

PATCH /articles/2 HTTP/1.1
Host: localhost
Content-Type: application/merge-patch+json{"author": {"givenName": "Jack","familyName": "Ma"},"tags": ["JAX-RS", "RESTEasy", "Jersey"]
}

经过合并补丁(Merge Patch)操作后,最终得到的 JSON 文档将是:

{"title":"我的第二篇文章","author":{"givenName":"Jack","familyName":"Ma"},"tags":["JAX-RS","RESTEasy","Jersey"],"content":"这是我第二篇文章的内容"
}

(注意:title 和 content 字段没有在补丁中提供,所以它们保持不变。而 author 和 tags 字段则被补丁中的新值完整替换了。)

让我们通过一个简单的 REST 资源示例,来演示如何在代码中实现这个过程。

示例项目

假设我们需要管理一个文章集合,用一个 Article 类来表示:

  • • Article.java (使用 Java Record)
    import java.time.LocalDateTime;
    import java.util.List;publicrecordArticle(Integer id,String title,Author author,String content,List<String> tags,LocalDateTime publishedAt
    ) {// record 提供了不可变性,这些 "with" 方法用于创建新的、已修改的副本public Article withId(int id) {returnnewArticle(id, title, author, content, tags, publishedAt);}public Article withTags(List<String> tags) {returnnewArticle(id, title, author, content, tags, publishedAt);}public Article withAuthor(Author author) {returnnewArticle(id, title, author, content, tags, publishedAt);}
    }
  • • Author.java (使用 Java Record)
    public record Author(String givenName, String familyName) {}
    正如“Java SE Record 在 Jakarta EE 11 中的支持”一文中所述,尽管 JSON-B 在 Jakarta EE 11 中没有完全对齐 Record 的支持,但其实现 Eclipse Yasson 已经支持对 record 的序列化和反序列化。

ArticleRepository 是一个简单的内存中的存储库:

import jakarta.enterprise.context.ApplicationScoped;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;@ApplicationScoped
publicclassArticleRepository {privatestaticfinal ConcurrentHashMap<Integer, Article> articles = newConcurrentHashMap<>();privatestaticfinalAtomicIntegerID_GEN=newAtomicInteger(1);static { // 初始化一些数据varid1= ID_GEN.getAndIncrement();articles.put(id1,newArticle(id1, "我的第一篇文章",newAuthor("Hantsy", "Bai"),"这是我的第一篇文章",List.of("first", "article"),LocalDateTime.now()));varid2= ID_GEN.getAndIncrement();articles.put(id2,newArticle(id2, "我的第二篇文章",newAuthor("Hantsy", "Bai"),"这是我的第二篇文章",List.of("second", "article"),LocalDateTime.now()));}public List<Article> findAll() {return List.copyOf(articles.values());}public Article findById(int id) {return articles.get(id);}public Article save(Article article) {if (article.id() == null) {varid= ID_GEN.getAndIncrement();article = article.withId(id);}articles.put(article.id(), article);return article;}
}

现在,让我们看看 ArticleResource(JAX-RS 资源类):

import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.json.*;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.io.StringReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;@Path("articles")
@RequestScoped
publicclassArticleResource {@InjectArticleRepository repository;Jsonb jsonb;@PostConstructpublicvoidinit() {jsonb = JsonbBuilder.create();}@GETpublic Response getArticles() {return Response.ok(repository.findAll()).build();}@GET@Path("{id}")public Response getArticle(@PathParam("id") Integer id) {return Response.ok(repository.findById(id)).build();}@POST@Consumes(MediaType.APPLICATION_JSON)public Response createArticle(Article article) {varsaved= repository.save(article);return Response.created(URI.create("/articles/" + saved.id())).build();}// JSON Patch (RFC 6902) 示例1: 对资源集合进行批量操作@PATCH@Consumes(MediaType.APPLICATION_JSON_PATCH_JSON)public Response saveOrUpdateAllArticles(JsonArray patch) {varall= repository.findAll();varresult= Json.createPatch(patch).apply(Json.createReader(newStringReader(jsonb.toJson(all))).readArray());List<Article> articles = jsonb.fromJson(jsonb.toJson(result),newArrayList<Article>() {}.getClass().getGenericSuperclass());articles.forEach(repository::save);return Response.noContent().build();}// JSON Patch (RFC 6902) 示例2: 对单个资源进行操作@PATCH@Path("{id}")@Consumes(MediaType.APPLICATION_JSON_PATCH_JSON)public Response updateArticle(@PathParam("id") Integer id, JsonArray patch) {vartarget= repository.findById(id);varpatchedResult= Json.createPatch(patch).apply(Json.createReader(newStringReader(jsonb.toJson(target))).readObject());vararticle= jsonb.fromJson(jsonb.toJson(patchedResult), Article.class);repository.save(article);return Response.noContent().build();}// JSON Merge Patch (RFC 7386) 示例: 对单个资源进行合并更新@PATCH@Path("{id}")//@Consumes(MediaType.APPLICATION_MERGE_PATCH_JSON) // 在 Jakarta REST 4.0 中添加的常量@Consumes("application/merge-patch+json")// 直接使用媒体类型字符串public Response mergeArticle(@PathParam("id") Integer id, JsonObject patch) {vartargetArticle= repository.findById(id);varmergedResult= Json.createMergePatch(patch).apply(Json.createReader(newStringReader(jsonb.toJson(targetArticle))).readObject());vararticle= jsonb.fromJson(jsonb.toJson(mergedResult), Article.class);repository.save(article);return Response.noContent().build();}
}

为了进行比较,我们还包含了两个 JSON Patch (由 RFC 6902 定义,并在 Java EE 8 / JAX-RS 2.1 中实现) 的示例端点:一个用于处理操作数组,另一个用于处理单个资源实体。

接下来,让我们创建一个 Arquillian 测试来验证这些功能:
(原文此处展示了一个非常长的 Arquillian 测试类 ArticleResourceTest,包含了对 GETPOST, JSON Patch 和 JSON Merge Patch 的完整测试代码,此处为简洁起见,不再重复展示。该测试类的核心作用是:)

  1. 1. 部署测试环境: 使用 Arquillian 和 ShrinkWrap 创建一个包含所有必要类的 .war 测试包,并将其部署到一个嵌入式的应用服务器中。

  2. 2. 客户端测试: 测试代码作为客户端运行,通过 HTTP 请求与部署好的服务进行交互。

  3. 3. 验证 JSON Patch: 发送 Content-Type 为 application/json-patch+json 的 PATCH 请求,并验证资源是否被正确地局部修改(例如,替换某个字段、移除某个字段、添加一个元素到数组中)。

  4. 4. 验证 JSON Merge Patch: 发送 Content-Type 为 application/merge-patch+json 的 PATCH 请求,并验证资源是否被正确地合并更新(例如,author 和 tags 字段被完整替换)。

(原文此处对 testGetArticleByIdAndMergePatch 测试方法进行了详细解释,说明了如何首先获取资源,然后基于修改后的版本创建补丁对象 Json.createMergeDiff(...),接着发送 PATCH 请求,最后再次获取资源以验证补丁是否成功应用。)

警告
Jakarta REST 客户端 API 目前还没有提供一个像现有的 get() 或 post() 那样便捷的 patch() 方法。相关讨论可见:jakartaee/rest#1276

可以从我的 GitHub 仓库获取完整的示例项目


最后的思考

在过去的十年里,我开发了许多后端的 RESTful API 应用。然而,我注意到一个日益增长的趋势:越来越多的客户选择 Spring WebMvc 或 WebFlux 作为他们的首选框架,而不是 Jakarta REST。虽然像 RESTEasy 和 Quarkus 这样的库和框架帮助填补了一些空白,但 Jakarta REST 本身的发展一直很缓慢。像 JSON Patch 以及这个版本中新引入的 JSON Merge Patch 这样的功能,在真实世界的 RESTful API 开发中很少被使用。就连 Spring 也曾孵化过一个名为 Spring Sync 的项目来解决类似的需求,但后来也已被放弃。

http://www.xdnf.cn/news/1288531.html

相关文章:

  • 【element树组件】el-tree实现连接线及hover编辑效果
  • Java进阶学习之不可变集合
  • Ubuntu与Rocky系统安装Java全指南
  • 《软件工程导论》实验报告一 软件工程文档
  • 基于LLVM的memcpy静态分析工具:设计思路与原理解析(C/C++代码实现)
  • Life:Internship in OnSea Day 50, 51
  • 11. React组件插槽用法
  • Flink Python API 提交 Socket 数据源的 WordCount 作业
  • uni-app实战教程 从0到1开发 画图软件 (学会画图)
  • Flutter UI Kits by Olayemi Garuba:免费开源的高质量UI组件库
  • nvm install 14.21.3 时npm 无法下载和识别
  • -bash: ./restart.sh: /bin/bash^M: 坏的解释器: 没有那个文件或目录
  • 1.Ansible 自动化介绍
  • 串口通信“第二次总超时”的复盘
  • ETCD备份
  • aspose word for java 使用书签进行内容填充和更新
  • SM4对称加密算法的加密模式介绍
  • Python Day28 HTML 与 CSS 核心知识点 及例题分析
  • 自动驾驶 HIL 测试:构建 “以假乱真” 的实时数据注入系统
  • 《嵌入式Linux应用编程(四):Linux文件IO系统调用深度解析》
  • GraphQL 原理、应用与实践指南
  • 【Altium designer】快速建立原理图工程的步骤
  • Day05 店铺营业状态设置 Redis
  • MySQL-多表查询
  • 第23章,景深:技术综述
  • 下一代防火墙技术
  • 【KO】android 面试 算法
  • 数字气压传感器,筑牢汽车TPMS胎压监测系统的精准感知基石
  • 西门子S7-200与S7-1200通过PPI以太网模块通讯,赋能汽车制造行业发展
  • 如何在 Ubuntu 24.04 LTS Linux 中安装 JSON Server