内置的事件发布功能从Spring的早期版本开始存在,对于处理同一应用程序上下文中Spring组件之间的基本通信仍然有用。 通常,应用程序可以生成应用程序事件(可以是任意对象)并侦听它们。 整个机制非常简单:使用ApplicationPublisher
发布事件,使用EventListener
处理EventListener
。 我发现特别有用的是异步和事务性事件侦听器 。
可以使用事件的示例之一是组件要发信号通知已创建或修改实体 (就JPA而言),以便其他感兴趣的组件(侦听器)可以对此事件做出反应并触发一些业务逻辑(例如计划)通知)。 通常,此类代码在Spring托管的事务中执行。 例如:
@Service
public class TaskService {
private static final Logger LOG = LoggerFactory.getLogger(TaskService.class);
@Autowired
private TaskRepository taskRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
public Task createTask(String name) {
Task task = new Task();
task.setName(name);
task.setCreated(LocalDateTime.now());
LOG.info("Publishing task created event: {}", task);
eventPublisher.publishEvent(new TaskCreatedEvent(task));
try {
return taskRepository.save(task);
} finally {
LOG.info("Event published. Saving task: {}", task);
}
}
}
上面的服务在实际保存事件之前发布带有任务实例的事件。 在Spring中,可以通过多种方式处理此类事件。 为了演示任务实例会发生什么,我将使用JPA EntityManager
检查其状态:
- 如果是暂时的(未设置ID),
- 如果是由实体经理管理的,或者
- 如果已分离(不是临时的,不是托管的,但是存在)。
让我们看一下监听器:
@Service
public class TaskCreatedEventListener {
private static final Logger LOG = LoggerFactory.getLogger(TaskCreatedEventListener.class);
@Resource
EntityManager entityManager;
// @Async
// @EventListener
// @TransactionalEventListener
public void handleEvent(TaskCreatedEvent taskCreatedEvent) throws InterruptedException {
Task task = taskCreatedEvent.getTask();
LOG.info("Is task transient? {}", isTransient(task));
LOG.info("Is task managed? {}", isManaged(task));
LOG.info("Is task detached? {}", isDetached(task));
}
private boolean isTransient(Task task) {
return task.getId() == null;
}
private boolean isManaged(Task task) {
return entityManager.contains(task);
}
private boolean isDetached(Task task) {
return !isTransient(task)
&& !isManaged(task)
&& exists(task);
}
private boolean exists(Task task) {
return entityManager.find(Task.class, task.getId()) != null;
}
}
让我们来看几个例子。 如果我们有以下行为:
-
@EventListener
这是同步调用的标准侦听器。 该方法将在事务完成之前执行,因此对象将处于过渡状态。
-
@Async @EventListener
将@Async
添加到@EventListener
将使该方法异步执行,因此在不同线程中运行(请不要忘记在应用程序中启用异步方法执行)。 在我们的场景中,对象很可能处于过渡状态,但在现实生活中,由于发布者已经可以保存任务,因此也可以将其拆离 。 因此,实际上,行为不是确定性的。
-
@TransactionalEventListener
当将侦听器标记为事务性事件侦听器时,Spring仅在事务及其后提交阶段(可以通过注释对其进行调整)的边界中调用发布者时才将事件发布给侦听器。 在这种情况下,对象将处于托管状态。
-
@Async @TransactionalEventListener
这是最有趣的情况。 如上所述,Spring仅在事务边界及其提交后阶段中调用发布者时才将事件发布给侦听器,但是事务完成了,因此对象处于分离状态–正在运行另一个事务。