抢先一步
VMware 提供培训和认证,助您加速进步。
了解更多今天早些时候,我发布了 Spring Sync 的第一个里程碑版本。这是一个通过采用基于补丁的交换方式,解决客户端应用程序和 Spring 后端之间高效通信的新项目。由于这是一个新项目,我认为现在是时候向您展示 Spring Sync 的功能了。
这里给出的示例参考了 Spring REST Todos 示例和/或该示例项目中的 Todo
类。
在最低层,Spring Sync 提供了一个库,用于生成和应用 Java 对象的补丁。Patch
类是该库的核心,它捕获可以应用于对象以使其与另一个对象同步的更改。
Patch
类旨在通用,不直接与任何特定的补丁表示形式关联。也就是说,它受到 JSON Patch 的启发,Spring Sync 支持将 Patch
实例创建并序列化为 JSON Patch。Spring Sync 的未来版本可能包含对其他补丁表示形式的支持。
创建补丁最简单的方法是计算两个 Java 对象之间的差异
Todo original = ...;
Todo modified = ...;
Patch patch = Diff.diff(original, modified);
在这里,Diff.diff()
方法将比较两个 Todo
对象,并生成一个描述它们之间差异的 Patch
。
一旦有了 Patch
,可以通过将对象传递给 apply()
方法将其应用于对象
Todo patched = patch.apply(original, Todo.class);
请注意,diff()
和 apply()
方法是互逆的。因此,在将补丁应用于原始对象后,这些示例中打了补丁的 Todo
应该与修改后的 Todo
完全相同。
如前所述,Patch
与任何特定的补丁表示形式是解耦的。但 Spring Sync 提供 JsonPatchMaker
作为工具类,用于将 Patch
对象与 Jackson JsonNode
实例进行相互转换,其中 JsonNode
是一个包含零个或多个操作的 ArrayNode
,符合 JSON Patch 规范。例如,将 Patch
转换为包含 JSON Patch 的 JsonNode
JsonNode jsonPatchNode = JsonPatchMaker.toJsonNode(patch);
类似地,可以从 JsonNode
创建 Patch
对象,如下所示
Patch patch = JsonPatchMaker.fromJsonNode(jsonPatchNode);
请注意,JsonPatchMaker
是用于将 Patch
对象与 JSON Patch 进行(反)序列化的临时解决方案。它将在后续版本中被更永久的解决方案取代。
创建补丁需要您拥有对象的“之前”和“之后”实例,以便计算差异。虽然它没有将它们称为“之前”和“之后”,但 Neil Fraser 在一篇论文中描述的 差异同步 (Differential Synchronization) 算法本质上定义了一种控制方式,通过该方式可以在两个或多个网络节点(可能是客户端和服务器,但不一定仅适用于客户端-服务器场景)之间创建、共享和应用补丁。
应用差异同步时,每个节点都维护资源的两个副本
节点可以对其本地资源副本进行任何所需的修改。节点会定期通过比较本地节点与为远程节点维护的影子副本生成补丁。然后将补丁发送到远程节点。一旦发送了补丁,节点就会将其本地副本复制到影子副本上,假设远程节点将应用该补丁,因此其对远程节点资源的理解与本地资源同步。
收到补丁后,节点必须将该补丁应用于它为发送补丁的节点保留的影子副本以及它自己的本地副本(本地副本本身可能也发生了变化)。
Spring Sync 通过其 DiffSync
类支持差异同步。要创建 DiffSync
,必须为其提供 ShadowStore
和它可以应用补丁的对象类型
ShadowStore shadowStore = new MapBasedShadowStore();
shadowStore.setRemoteNodeId("remoteNode");
DiffSync diffSync = new DiffSync(shadowStore, Todo.class);
一旦有了 DiffSync
,您就可以使用它将 Patch
应用于对象
Todo patched = diffSync.apply(patch, todo);
apply()
方法将补丁应用于给定对象以及该对象的影子副本。如果尚未创建影子副本,它将通过深度克隆给定对象来创建一个。
ShadowStore
是 DiffSync
维护远程节点影子副本的地方。对于任何给定节点,可能有多个影子存储,每个远程节点一个。如示例所示,其 remoteNodeId
属性用于唯一标识远程节点。在客户端-服务器拓扑中,服务器可以使用会话 ID 来标识远程节点。同时,客户端(可能仅与一个中心服务器共享资源)可以使用任何想要的标识符来标识服务器节点。
DiffSync
也可以用于从存储的影子副本创建 Patch
Patch patch = diffSync.diff(todo);
创建补丁时,将从 ShadowStore
中检索存储的影子副本并与给定对象进行比较。为了与差异同步流程保持一致,一旦生成补丁,给定对象将被复制到影子副本上。
值得注意的是,DiffSync
使用 Patch
对象,这些对象与任何特定的补丁表示形式是解耦的。因此,DiffSync
本身也与补丁表示形式解耦。
在单个节点上创建和应用补丁意义不大。差异同步真正发挥作用的地方在于,当两个或多个节点共享和操作同一资源时,并且您需要每个节点(在合理范围内)保持同步。因此,Spring Sync 还提供了 DiffSyncController
,这是一个处理 HTTP PATCH 请求的 Spring MVC 控制器,将差异同步应用于资源。
配置 DiffSyncController
最简单的方法是创建一个用 @EnableDifferentialSynchronization
注解并扩展 DiffSyncConfigurerAdapter
类的 Spring 配置类
@Configuration
@EnableDifferentialSynchronization
public class DiffSyncConfig extends DiffSyncConfigurerAdapter {
@Autowired
private PagingAndSortingRepository<Todo, Long> repo;
@Override
public void addPersistenceCallbacks(PersistenceCallbackRegistry registry) {
registry.addPersistenceCallback(new JpaPersistenceCallback<Todo>(repo, Todo.class));
}
}
除其他事项外,@EnableDifferentialSynchronization
声明了一个 DiffSyncController
bean,并为其提供了 PersistenceCallbackRegistry
和 ShadowStore
。
PersistenceCallbackRegistry
是一个 PersistenceCallback
对象的注册表,DiffSyncController
通过它检索和持久化它所打补丁的资源。PersistenceCallback
接口使得 DiffSyncController
可以与资源的应用特定持久化选择解耦。例如,以下是 PersistenceCallback
的一个实现,它使用 Spring Data 的 CrudRepository
来持久化 Todo
对象
package org.springframework.sync.diffsync.web;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.sync.diffsync.PersistenceCallback;
class JpaPersistenceCallback<T> implements PersistenceCallback<T> {
private final CrudRepository<T, Long> repo;
private Class<T> entityType;
public JpaPersistenceCallback(CrudRepository<T, Long> repo, Class<T> entityType) {
this.repo = repo;
this.entityType = entityType;
}
@Override
public List<T> findAll() {
return (List<T>) repo.findAll();
}
@Override
public T findOne(String id) {
return repo.findOne(Long.valueOf(id));
}
@Override
public void persistChange(T itemToSave) {
repo.save(itemToSave);
}
@Override
public void persistChanges(List<T> itemsToSave, List<T> itemsToDelete) {
repo.save(itemsToSave);
repo.delete(itemsToDelete);
}
@Override
public Class<T> getEntityType() {
return entityType;
}
}
对于提供给 DiffSyncController
的 ShadowStore
,默认将是 MapBasedShadowStore
。但您可以覆盖 DiffSyncConfigurerAdapter
中的 getShadowStore()
方法来指定不同的影子存储实现。例如,您可以像这样配置基于 Redis 的影子存储
@Autowired
private RedisOperations<String, Object> redisTemplate;
@Override
public ShadowStore getShadowStore() {
return new RedisShadowStore(redisTemplate);
}
无论您选择哪种 ShadowStore
实现,都将声明一个会话范围的 bean,确保每个客户端都将拥有自己的影子存储实例。
由于它处理 PATCH 请求,DiffSyncController
将应用差异同步流程的一个周期
就像 Patch
和 DiffSync
一样,DiffSyncController
与任何特定的补丁格式是解耦的。然而,Spring Sync 确实提供了 JsonPatchHttpMessageConverter
,这样在内容类型为 "application/json-patch+json" 的情况下,DiffSyncController
可以接收和响应 JSON Patch 格式的补丁。
正如您在此处所见,Spring Sync 旨在提供一种在客户端和服务器(或任何共享资源的节点集)之间进行高效通信和同步的方法。它提供了用于生成和应用补丁的低级支持,以及用于差异同步的高级支持。虽然它支持 JSON Patch,但它很大程度上独立于任何特定的补丁格式。
这仅仅是个开始。除其他事项外,我们正在考虑...
DiffSyncController
基于 HTTP 的差异同步,实现全双工补丁通信。请关注该项目并告诉我们您的想法。欢迎随时 提交错误报告和改进建议,我们也非常欢迎您 分叉代码 并提交拉取请求。
如果您想了解更多关于 Spring Sync 的信息,请查看以下资源