今天,我们将就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.glassfish
javax.json
1.1.2
有趣的是,没有多少人知道Apache CXF和通常的任何JAX-RS兼容框架都与JSON-P紧密集成并支持其基本数据类型。 对于Apache CXF ,只需添加cxf-rt-rs-extension-providers
模块依赖项即可:
org.apache.cxf
cxf-rt-rs-extension-providers
3.2.2
并在服务器工厂bean中注册JsrJsonpProvider ,例如:
@Configuration
public class AppConfig {
@Bean
public 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