今天,我们将就REST(ful)服务和API进行一次对话,更准确地说,围绕许多经验丰富的开发人员正在努力解决的一个独特主题。 为了使事情更直观,我们将讨论Web API,其中REST(ful)原则遵循HTTP协议并大量利用HTTP方法的语义,(通常但不一定)使用JSON表示状态。
一种特殊的HTTP方法非常引人注目,尽管其含义听起来很简单,但实现方法远非如此。 是的,我们正在寻找您, PATCH 。 那到底是什么问题呢? 这只是更新,对不对? 是的,实质上,在基于HTTP的REST(ful)Web服务的上下文中, PATCH方法的语义是资源的部分更新。 现在,Java开发人员将如何做到这一点? 这就是乐趣的开始。
让我们来看一个非常简单的图书管理API示例,该示例使用最新的JSR 370:RESTful Web服务的Java API(JAX-RS 2.1)规范(最终包括@PATCH注释!)和出色的Apache CXF框架进行建模 。 我们的资源只是一个非常简单的Book类。
public class Book {private String title;private Collection>String< authors;private String isbn;
}
您将如何使用PATCH方法实施部分更新? 可悲的是,强力解决方案,即null
宴席,在这里显然是赢家。
@PATCH
@Path("/{isbn}")
@Consumes(MediaType.APPLICATION_JSON)
public void update(@PathParam("isbn") String isbn, Book book) {final Book existing = bookService.find(isbn).orElseThrow(NotFoundException::new);if (book.getTitle() != null) {existing.setTitle(book.getTitle());}if (book.getAuthors() != null) {existing.setAuthors(book.getAuthors());}// And here it goes on and on ...// ...
}
简而言之,这是空保护的PUT克隆。 可能有人会声称这行得通,并在这里宣布胜利。 但是希望对我们大多数人来说,这种方法显然存在很多缺陷,因此永远不应该采用。 备择方案? 是的,绝对是RFC-6902:JSON补丁 ,目前还不是官方标准,但已经实现了。
RFC-6902:JSON修补程序通过表达一系列操作以应用于JSON文档,从而彻底改变了游戏。 为了说明这一思想的实际效果,让我们从更改书名的简单示例入手,以所需的结果来描述。
{ "op": "replace", "path": "/title", "value": "..." }
看起来很干净,那么添加作者呢? 简单 …
{ "op": "add", "path": "/authors", "value": ["...", "..."] }
太棒了,卖完了,但是……从实现角度看,这似乎需要很多工作,不是吗? 如果我们依赖最新和最强大的JSR 374:JSON处理1.1的Java API,它完全支持RFC-6902:JSON Patch,则不是真的。 有了合适的工具,这次让我们做对了。
org.glassfishjavax.json1.1.2
有趣的是,没有多少人知道Apache CXF和通常的任何JAX-RS兼容框架都与JSON-P紧密集成并支持其基本数据类型。 对于Apache CXF ,只需添加cxf-rt-rs-extension-providers
模块依赖项即可:
org.apache.cxfcxf-rt-rs-extension-providers3.2.2
并在服务器工厂bean中注册JsrJsonpProvider ,例如:
@Configuration
public class AppConfig {@Beanpublic Server rsServer(Bus bus, BookRestService service) {JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();endpoint.setBus(bus);endpoint.setAddress("/");endpoint.setServiceBean(service);endpoint.setProvider(new JsrJsonpProvider());return endpoint.create();}
}
将所有部分连接在一起,我们的PATCH操作可以使用JSR 374:仅用于JSON Processing 1.1的Java API来实现,仅需几行:
@Service
@Path("/catalog")
public class BookRestService {@Inject private BookService bookService;@Inject private BookConverter converter;@PATCH@Path("/{isbn}")@Consumes(MediaType.APPLICATION_JSON)public void apply(@PathParam("isbn") String isbn, JsonArray operations) {final Book book = bookService.find(isbn).orElseThrow(NotFoundException::new);final JsonPatch patch = Json.createPatch(operations);final JsonObject result = patch.apply(converter.toJson(book));bookService.update(isbn, converter.fromJson(result));}
}
BookConverter执行Book类及其JSON表示之间的转换(反之亦然),我们正在手工进行操作,以说明JSR 374:JSON处理1.1的Java API提供的另一种功能。
@Component
public class BookConverter {public Book fromJson(JsonObject json) {final Book book = new Book();book.setTitle(json.getString("title"));book.setIsbn(json.getString("isbn"));book.setAuthors(json.getJsonArray("authors").stream().map(value -> (JsonString)value).map(JsonString::getString).collect(Collectors.toList()));return book;}public JsonObject toJson(Book book) {return Json.createObjectBuilder().add("title", book.getTitle()).add("isbn", book.getIsbn()).add("authors", Json.createArrayBuilder(book.getAuthors())).build();}
}
最后,让我们将这个简单的JAX-RS 2.1 Web API包装到漂亮的Spring Boot信封中。
@SpringBootApplication
public class BookServerStarter { public static void main(String[] args) {SpringApplication.run(BookServerStarter.class, args);}
}
并运行它。
mvn spring-boot:run
结束讨论,让我们通过在目录中故意添加一本不完整的书 ,来探讨一些更实际的例子。
$ curl -i -X POST http://localhost:19091/services/catalog -H "Content-Type: application\json" -d '{"title": "Microservice Architecture","isbn": "978-1491956250","authors": ["Ronnie Mitra","Matt McLarty"]}'HTTP/1.1 201 Created
Date: Tue, 20 Feb 2018 02:30:18 GMT
Location: http://localhost:19091/services/catalog/978-1491956250
Content-Length: 0
在本书的描述中,我们要纠正一些错误,即将标题设置为完整的“微服务体系结构:原则,实践和文化的 一致性 ” ,并包括失踪的合著者Irakli Nadareishvili和Mike Amundsen 。 借助我们不久前开发的API,这很容易。
$ curl -i -X PATCH http://localhost:19091/services/catalog/978-1491956250 -H "Content-Type: application\json" -d '[{ "op": "add", "path": "/authors/0", "value": "Irakli Nadareishvili" },{ "op": "add", "path": "/authors/-", "value": "Mike Amundsen" },{ "op": "replace", "path": "/title", "value": "Microservice Architecture: Aligning Principles, Practices, and Culture" }]'HTTP/1.1 204 No Content
Date: Tue, 20 Feb 2018 02:38:48 GMT
前两个操作的路径引用可能看起来有些混乱,但是不用担心,让我们澄清一下。 因为authors
是一个集合(或就JSON数据类型而言,是一个数组),所以我们可以使用RFC-6902:JSON Patch数组索引符号来指定要插入新元素的确切位置。 第一个操作使用索引'0'
来表示头部位置,而第二个操作使用'-'
占位符来简化说“添加到集合的末尾”。 如果我们在更新后立即检索到该书,则应该看到我们所做的修改完全按照我们的要求进行了应用。
$ curl http://localhost:19091/services/catalog/978-1491956250{"title": "Microservice Architecture: Aligning Principles, Practices, and Culture","isbn": "978-1491956250","authors": ["Irakli Nadareishvili","Ronnie Mitra","Matt McLarty","Mike Amundsen"]
}
干净,简单而强大。 公平地说,要付出代价是一种额外的JSON操作形式(以应用补丁程序),但是值得付出努力吗? 我相信是……
下次您要设计新颖的REST(ful)Web API时 ,请认真考虑RFC-6902:JSON补丁以支持资源的PATCH实现。 我相信还将与JAX-RS进行更紧密的集成(如果还没有的话),以直接支持JSONPatch类及其家族。
最后但并非最不重要的一点是,在本文中,我们仅涉及服务器端实现,但JSR 374:用于JSON处理1.1的Java API也包括方便的客户端支架,从而对补丁提供了完整的编程控制。
final JsonPatch patch = Json.createPatchBuilder().add("/authors/0", "Irakli Nadareishvili").add("/authors/-", "Mike Amundsen").replace("/title", "Microservice Architecture: Aligning Principles, Practices, and Culture").build();
完整的项目资源可在Github上找到 。
翻译自: https://www.javacodegeeks.com/2018/02/run-away-null-checks-feast-patch-properly-json-patch.html