领先一步
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
是根据 JSON Patch 规范包含零个或多个操作的ArrayNode
)。例如,要将Patch
转换为包含 JSON Patch 的JsonNode
:
JsonNode jsonPatchNode = JsonPatchMaker.toJsonNode(patch);
类似地,可以像这样从JsonNode
创建一个Patch
对象:
Patch patch = JsonPatchMaker.fromJsonNode(jsonPatchNode);
请注意,JsonPatchMaker
是将Patch
对象序列化/反序列化为/从 JSON Patch 的临时解决方案。它将在以后的版本中被更永久的解决方案替换。
创建补丁需要您拥有对象的“之前”和“之后”实例,以便从中计算差异。虽然没有将它们称为“之前”和“之后”,但Neil Fraser 在论文差异同步中描述的算法基本上定义了一种控制器方式,通过该方式可以在两个或多个网络节点(可能是客户端和服务器,但不一定仅适用于客户端-服务器场景)之间创建、共享和应用补丁。
应用差异同步时,每个节点维护资源的两个副本:
节点可以对其资源的本地副本进行任何必要的更改。定期地,节点将通过比较本地节点与其为远程节点维护的影子副本,生成一个补丁。然后,它将补丁发送到远程节点。发送补丁后,节点将将其本地副本复制到影子副本上,假设远程节点将应用补丁,因此它对远程节点资源的理解与本地资源同步。
收到补丁后,节点必须将补丁应用于其为发送补丁的节点保留的影子副本及其自己的本地副本(它本身可能已经发生更改)。
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
对象一起工作,Patch
对象与任何特定的补丁表示方式无关。因此,DiffSync
本身也与补丁表示方式无关。
在一个节点上创建和应用补丁有点意义不大。差异同步真正发挥作用的地方是当两个或多个节点共享和操作相同的资源时,并且您需要每个节点保持同步(尽可能合理)。因此,Spring Sync 还提供DiffSyncController
,这是一个 Spring MVC 控制器,它处理HTTP PATCH请求,将差异同步应用于资源。
配置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
能够与资源的特定于应用程序的持久性选择分离。例如,这是一个使用Spring DataCrudRepository
来持久化Todo
对象的PersistenceCallback
实现:
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
,以便DiffSyncController
可以接收和响应使用 JSON Patch 格式的补丁,前提是内容类型为“application/json-patch+json”。
如您所见,Spring Sync旨在提供一种在客户端和服务器(或共享资源的任何一组节点)之间进行高效通信和同步的方法。它提供对生成和应用补丁的底层支持,以及对使用差异同步的高级支持。虽然它支持JSON Patch,但它很大程度上独立于任何特定的补丁格式。
这仅仅是个开始。除此之外,我们还计划:
DiffSyncController
的基于HTTP的差异同步,以实现全双工补丁通信。关注该项目并告诉我们您的想法。欢迎您提交错误报告和改进建议,我们当然也欢迎您fork代码并提交拉取请求。
如果您想了解更多关于Spring Sync的信息,请查看以下资源: