Spring Framework 4.2 中更好的应用事件

工程 | Stéphane Nicoll | 2015 年 2 月 11 日 | ...

应用事件自 Spring 框架诞生之初就已存在,作为松耦合组件交换信息的一种手段。应用事件最知名的用法之一如下所示

@Component
public class MyListener 
        implements ApplicationListener<ContextRefreshedEvent> {
  
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ...
    }
}

这使得 MyListener 可以在上下文刷新时收到通知,从而可以在应用上下文完全启动时运行任意代码。

在 Spring Framework 4.2 中,我们改进了事件基础设施,主要涉及三个方面,我将在本文中进行解释。

泛型支持

现在,可以在事件类型中使用嵌套泛型信息来定义您的 ApplicationListener 实现,例如

public class MyListener 
        implements ApplicationListener<MyEvent<Order>> { ... }

分发事件时,会使用监听器的签名来确定其是否与传入事件匹配。

由于类型擦除,您需要发布一个解析您希望过滤的泛型参数的事件,例如 MyOrderEvent extends MyEvent<Order>。可能存在其他变通方法,如果社区认为值得,我们乐意重新审视签名匹配算法。

注解驱动的事件监听器

最大的新特性是支持注解驱动的事件监听器,类似于我们在 Spring Framework 4.1 中关于 JMS 和 AMQP 端点的最新工作。简而言之,现在只需使用 @EventListener 注解托管 bean 的方法,即可自动注册与该方法签名匹配的 ApplicationListener。上面的示例可以重写如下

@Component
public class MyListener {
  
    @EventListener
    public void handleContextRefresh(ContextRefreshedEvent event) {
        ...
    }
}

@EventListener 是一个核心注解,其处理方式与 @Autowired 等注解类似,是透明的:使用 Java 配置时无需额外配置,现有的 <context:annotation-driven/> 元素可完全启用对其支持。

方法签名定义了您感兴趣的事件类型。还可以定义一个 SpEL 表达式,只有匹配该表达式才会处理事件。例如,考虑以下事件

public class OrderCreatedEvent implements CreationEvent<Order> { ... }

    private boolean awesome;
   
    public boolean isAwesome() { return this.awesome; }
    ....
}

以下示例展示了一个事件监听器,它仅针对 Order 的一个很棒的 CreationEvent 被调用(即如果 awesome 标志为 true

@Component
public class MyComponent {
  
  @EventListener(condition = "#creationEvent.awesome")
  public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
    ... 
  }

}

从上面的示例可以看出,如果可以发现方法参数信息,则通过其名称暴露方法参数。条件表达式还通过一个“root”变量暴露了原始的 ApplicationEvent (#root.event) 和实际的方法参数 (#root.args)。

发布事件

您可以为任何使用 @EventListener 注解的方法定义一个非 void 的返回类型。如果您在处理特定事件后返回一个非 null 值,我们会将该结果作为新事件发送。

您可能注意到我们的 OrderCreatedEvent 没有继承 ApplicationEvent;我们认为现在是时候让您灵活地发布任意事件,而不是强制您继承 ApplicationEventApplicationEventPublisher 接口已扩展,允许您发布任何对象;当该对象不是 ApplicationEvent 时,我们会将其包装在一个 PayloadApplicationEvent 中。如果您想使用常规的 ApplicationListener 实现来监听此类任意事件,请记住这一点。

以下示例展示了如何使用 ApplicationEventPublisher 发送 OrderCreatedEvent

@Component
public class MyComponent {

    private final ApplicationEventPublisher publisher;
    
    @Autowired
    public MyComponent(ApplicationEventPublisher publisher) { ... }
    
    public void createOrder(Order order) {
        // ....
        this.publisher.publishEvent(new OrderCreatedEvent(order)); 
    }

}

事务绑定事件

另一项受欢迎的改进是将事件监听器绑定到事务的某个阶段的能力。典型的例子是在事务成功完成时处理事件:这使得事件在当前事务的结果对监听器确实重要时,可以更灵活地使用。

Spring Framework 目前的结构使得上下文不感知事务支持,我们显然不想偏离这一非常合理的原则,因此我们构建了一个开放基础设施,允许注册额外组件并影响事件监听器的创建方式。

事务模块实现了一个 EventListenerFactory,它查找新的 @TransactionalEventListener 注解。当存在此注解时,将注册一个感知事务的扩展事件监听器,而不是默认的监听器。

让我们重用上面的示例,并将其改写,使得只有当生产者运行的事务成功完成时,才会处理订单创建事件

@Component
public class MyComponent {
  
  @TransactionalEventListener(condition = "#creationEvent.awesome")
  public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) { 
    ...
  }

}

没什么可多说的,对吧? @TransactionalEventListener 一个常规的 @EventListener,并且还暴露了一个 TransactionPhase,默认值为 AFTER_COMMIT。您还可以关联事务的其他阶段(BEFORE_COMMITAFTER_ROLLBACKAFTER_COMPLETION,后者是 AFTER_COMMITAFTER_ROLLBACK 的别名)。

默认情况下,如果没有事务正在运行,事件根本不会发送,因为我们显然无法遵守请求的阶段,但在 @TransactionalEventListener 中有一个 fallbackExecution 属性,它告诉 Spring 如果没有事务,则立即调用监听器。

试试看!

如果您想在 4.2 的第一个里程碑版本发布前试用此功能,请通过我们的snapshot 仓库获取每夜构建的 SNAPSHOT 版本。您也可以使用最新的 Spring Boot snapshot 版本,通过start.spring.io创建一个示例项目,或者如果您非常懒,可以直接将以下内容复制粘贴到您的 shell 中

$ curl https://start.spring.io/starter.tgz -d artifactId=events-demo \
    -d baseDir=events-demo -d bootVersion=1.2.2.BUILD-SNAPSHOT | tar -xzvf -

并将项目更新为使用 Spring Framework 4.2.0.BUILD-SNAPSHOT

<properties>
  ...
  <spring.version>4.2.0.BUILD-SNAPSHOT</spring.version>
</properties>

一如既往,我们欢迎社区反馈,请试用这些功能,如果您遇到任何问题,请告知我们。

订阅 Spring 新闻简报

保持与 Spring 新闻简报的连接

订阅

领先一步

VMware 提供培训和认证,助您加速前进。

了解更多

获取支持

Tanzu Spring 在一项简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部