Recipes

本章包含现有内置状态机Recipes的文档。spring-doc.cadn.net.cn

Spring StateMachine 是一个基础框架。也就是说,它并不包含太多高级功能或依赖于其他许多库之外的 Spring Framework。 因此,正确使用状态机可能较为困难。为此,我们创建了一组Recipes模块来解决常见用例问题。spring-doc.cadn.net.cn

什么是Recipes?状态机Recipes是一个模块,用于解决常见用例。本质上,状态机Recipes既是我们尝试制作的一个示例,旨在使您能够轻松重用和扩展,也是一个可供参考的实例。spring-doc.cadn.net.cn

Recipes 是一种向 Spring Statemachine 项目做出外部贡献的好方法。如果你还不准备为框架核心本身做贡献,那么自定义和常见的 Recipes 就是一个很好的方式来与其他用户分享功能。

36. 持久化

The persist recipe 是一个简单的工具,它允许你使用单个状态机实例来持久化并更新仓库中任意项的状态。spring-doc.cadn.net.cn

The recipe’s main class is PersistStateMachineHandler, which makes three assumptions:spring-doc.cadn.net.cn

  • 一个StateMachine<String, String>实例需要与一个PersistStateMachineHandler一起使用。请注意,状态和事件必须是String的类型。spring-doc.cadn.net.cn

  • PersistStateChangeListener 需要注册到处理器 以响应持久化请求。spring-doc.cadn.net.cn

  • The handleEventWithState 方法用于协调状态变化。spring-doc.cadn.net.cn

您可以找到一个样例,演示如何使用此配方: 持久化.spring-doc.cadn.net.cn

37. 任务

The tasks 脚本是运行一个有向无环图(Directed Acyclic Graph)的概念,该图由Runnable个实例组成,并使用状态机。此脚本源自 Tasks 样例中引入的想法。spring-doc.cadn.net.cn

接下来的图片展示了状态机的一般概念。在这个状态图中, 在TASKS下的所有内容展示了一个单一任务执行的通用概念。 由于本Recipes允许您注册一个深度层次化的DAG(即,实际的状态图会是一个嵌套层次非常深的子状态和区域集合),所以我们无需更加精确。spring-doc.cadn.net.cn

例如,如果您只注册了两个任务,则在将TASK_id替换为TASK_1TASK_2(假设注册的任务ID是12)时,以下状态图将是正确的。spring-doc.cadn.net.cn

statechart9

执行一个Runnable可能会导致错误。特别是当涉及复杂的任务有向无环图(DAG)时,您希望有一种方式来处理任务执行错误,并且能够继续执行而不会重新执行已经成功执行的任务。另外,如果某些执行错误可以自动处理就更好了。作为最后的备用方案,如果错误无法自动处理,则状态机将进入一个用户可以手动处理错误的状态。spring-doc.cadn.net.cn

TasksHandler 包含一个构建器方法来配置处理器实例,并遵循简单的构建者模式。你可以使用这个构建器来注册 Runnable 任务和 TasksListener 实例并定义 StateMachinePersist 挂钩。spring-doc.cadn.net.cn

现在我们可以使用一个简单的Runnable来运行一个简单的睡眠操作,如下例所示:spring-doc.cadn.net.cn

private Runnable sleepRunnable() {
	return new Runnable() {

		@Override
		public void run() {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
			}
		}
	};
}
本示例是本章所有示例的基础。

为了执行多个sleepRunnable任务,您可以注册任务并从TasksHandler执行runTasks()方法,如下例所示:spring-doc.cadn.net.cn

TasksHandler handler = TasksHandler.builder()
		.task("1", sleepRunnable())
		.task("2", sleepRunnable())
		.task("3", sleepRunnable())
		.build();

handler.runTasks();

要监听任务执行的情况,您可以注册一个 TasksListener 实例到一个 TasksHandler。此Recipes 提供了一个适配器 TasksListenerAdapter,如果您不想实现完整的接口。监听者提供了多种钩子来 监听任务执行事件。以下示例展示了 MyTasksListener 类的定义:spring-doc.cadn.net.cn

private class MyTasksListener extends TasksListenerAdapter {

	@Override
	public void onTasksStarted() {
	}

	@Override
	public void onTasksContinue() {
	}

	@Override
	public void onTaskPreExecute(Object id) {
	}

	@Override
	public void onTaskPostExecute(Object id) {
	}

	@Override
	public void onTaskFailed(Object id, Exception exception) {
	}

	@Override
	public void onTaskSuccess(Object id) {
	}

	@Override
	public void onTasksSuccess() {
	}

	@Override
	public void onTasksError() {
	}

	@Override
	public void onTasksAutomaticFix(TasksHandler handler, StateContext<String, String> context) {
	}
}

您可以通过构建器注册监听器,或者直接使用一个TasksHandler来注册监听器,如下例所示:spring-doc.cadn.net.cn

MyTasksListener listener1 = new MyTasksListener();
MyTasksListener listener2 = new MyTasksListener();

TasksHandler handler = TasksHandler.builder()
		.task("1", sleepRunnable())
		.task("2", sleepRunnable())
		.task("3", sleepRunnable())
		.listener(listener1)
		.build();

handler.addTasksListener(listener2);
handler.removeTasksListener(listener2);

handler.runTasks();

每一个任务 都需要有一个唯一的标识符,并且(可选地)一个任务可以定义为子任务。实际上,这创建了一个任务的有向无环图(DAG)。 以下示例展示了如何创建一个深度嵌套的任务DAG:spring-doc.cadn.net.cn

TasksHandler handler = TasksHandler.builder()
		.task("1", sleepRunnable())
		.task("1", "12", sleepRunnable())
		.task("1", "13", sleepRunnable())
		.task("2", sleepRunnable())
		.task("2", "22", sleepRunnable())
		.task("2", "23", sleepRunnable())
		.task("3", sleepRunnable())
		.task("3", "32", sleepRunnable())
		.task("3", "33", sleepRunnable())
		.build();

handler.runTasks();

当错误发生并且运行这些任务的状态机进入ERROR状态时,你可以调用fixCurrentProblems处理器方法来重置状态机扩展状态变量中保存的任务当前状态。然后你可以使用continueFromError处理器方法来指示状态机从ERROR状态过渡回READY状态,在这里你又可以再次运行任务。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

TasksHandler handler = TasksHandler.builder()
		.task("1", sleepRunnable())
		.task("2", sleepRunnable())
		.task("3", sleepRunnable())
		.build();

		handler.runTasks();
		handler.fixCurrentProblems();
		handler.continueFromError();