Johniya Blog
首页 博客 社区 问答 开发者 约米云盘 注册
Sping-ApplicationContext的标准事件和自定义事件
痞老板 2023-12-28 13:07:21 北京 朝阳 spring java 开发 技术

标准和自定义事件

ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果一个实现 ApplicationListener 接口的Bean被部署到上下文中,每当 ApplicationEvent 被发布到 ApplicationContext 时,该Bean就会被通知。本质上,这是标准的观察者设计模式。

从Spring 4.2开始,事件基础架构得到了极大的改进,它提供了基于 注解的模型 以及发布任何任意事件的能力(也就是不一定从 ApplicationEvent 继承出来的对象)。当这样的对象被发布时,我们会为你把它包装成一个事件。

下表描述了Spring提供的标准事件。

事件 说明
ContextRefreshedEvent 当 ApplicationContext 被初始化或刷新时发布(例如,通过使用 ConfigurableApplicationContext 接口上的 refresh() 方法)。这里,"初始化" 意味着所有的Bean都被加载,后处理器Bean被检测和激活,单例被预先实例化,并且 ApplicationContext 对象可以使用。只要上下文没有被关闭,就可以多次触发刷新,前提是所选的 ApplicationContext 实际上支持这种 "热" 刷新。例如, XmlWebApplicationContext 支持热刷新,但 GenericApplicationContext 不支持。
ContextStartedEvent 当 ApplicationContext 通过使用 ConfigurableApplicationContext 接口上的 start() 方法启动时发布。在这里,"开始" 意味着所有的 Lifecycle Bean都收到一个显式的启动信号。通常,这个信号被用来在显式停止后重新启动Bean,但它也可能被用来启动那些没有被配置为自动启动的组件(例如,在初始化时还没有启动的组件)。
ContextStoppedEvent 当 ApplicationContext 通过使用 ConfigurableApplicationContext 接口上的 stop() 方法而停止时发布。在这里,"停止" 意味着所有的 Lifecycle Bean收到一个明确的停止信号。停止的上下文可以通过调用 start() 重新启动。
ContextClosedEvent 当 ApplicationContext 通过使用 ConfigurableApplicationContext 接口上的 close() 方法或通过 JVM shutdown hook 被关闭时发布。这里,"关闭" 意味着所有的单例Bean将被销毁。一旦上下文被关闭,它的生命就结束了,不能被刷新或重新启动。
RequestHandledEvent 一个针对Web的事件,告诉所有Bean一个HTTP请求已经被处理。该事件在请求完成后被发布。这个事件只适用于使用Spring的 DispatcherServlet 的Web应用程序。
ServletRequestHandledEvent RequestHandledEvent 的一个子类,增加了 Servlet 特定的上下文信息。


你也可以创建和发布你自己的自定义事件。下面的例子展示了一个简单的类,它扩展了Spring的 ApplicationEvent 基类。

public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}

要发布一个自定义的 ApplicationEvent,请在 ApplicationEventPublisher 上调用 publishEvent() 方法。通常情况下,这是通过创建一个实现 ApplicationEventPublisherAware 的类并将其注册为Spring Bean来实现的。下面的例子展示了这样一个类。

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

在配置时,Spring容器检测到 EmailService 实现了 ApplicationEventPublisherAware 并自动调用 setApplicationEventPublisher()。实际上,传入的参数是Spring容器本身。你正在通过它的 ApplicationEventPublisher 接口与 application context 进行交互。

为了接收自定义的 ApplicationEvent,你可以创建一个实现 ApplicationListener 的类并将其注册为Spring Bean。下面的例子展示了这样一个类。

public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

请注意,ApplicationListener 是用你的自定义事件的类型(前面的例子中是 BlockedListEvent)作为泛型参数。这意味着 onApplicationEvent() 方法可以保持类型安全,避免了任何强制向下转型的需要。你可以根据你的需要注册任意多的事件监听器,但是请注意,在默认情况下,事件监听器是同步接收事件的。这意味着 publishEvent() 方法会阻塞,直到所有的监听器都完成对事件的处理。这种同步和单线程的方法的一个好处是,当一个监听器收到一个事件时,如果有一个事务上下文的话,它就在发布者的事务上下文中操作。如果需要使用另一种事件发布策略,请参阅 Spring 的 ApplicationEventMulticaster 接口和 SimpleApplicationEventMulticaster 实现的 javadoc 配置选项。

下面的例子显示了用于注册和配置上述每个类的bean定义。

<bean id="emailService" class="example.EmailService">
    <property name="blockedList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blockedListNotifier" class="example.BlockedListNotifier">
    <property name="notificationAddress" value="blockedlist@example.org"/>
</bean>

把它放在一起,当调用 emailService bean的 sendEmail() 方法时,如果有任何应该被阻止的电子邮件,就会发布一个 BlockedListEvent 类型的自定义事件。 blockedListNotifier Bean被注册为 ApplicationListener,并接收 BlockedListEvent,这时它可以通知相关方。

Spring的事件机制是为同一应用环境下的Spring Bean之间的简单通信而设计的。然而,对于更复杂的企业集成需求,单独维护的 Spring Integration 项目为构建轻量级、 面向模式、事件驱动的架构提供了完整的支持,这些架构建立在众所周知的Spring编程模型之上。

基于注解的事件监听器

你可以通过使用 @EventListener 注解在管理型Bean的任何方法上注册一个事件监听器。 BlockedListNotifier 可以被重写如下。

public class BlockedListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

方法签名再次声明了它所监听的事件类型,但是,这一次,有了一个灵活的名字,并且没有实现一个特定的监听接口。事件类型也可以通过泛型来缩小,只要实际的事件类型在其实现层次中解析你的泛型参数。

如果你的方法应该监听几个事件,或者你想在没有参数的情况下定义它,事件类型也可以在注解本身中指定。下面的例子展示了如何做到这一点。

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}

也可以通过使用定义 SpEL表达式 的注解的 condition 属性来添加额外的运行时过滤,该表达式应该匹配以实际调用特定事件的方法。

下面的例子显示了我们的 notifier 如何被改写成只有在事件的 content 属性等于 my-event 时才会被调用。

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每个 SpEL 表达式都针对一个专门的上下文进行评估。下表列出了提供给上下文的项目,以便你可以使用它们进行条件性事件处理。

名称 位置 说明 示例
事件 root 对象 实际的 ApplicationEvent。 #root.event or event
参数数组 root 对象 用于调用方法的参数(作为一个对象数组)。 #root.args 或 args; args[0] 用于访问第一个参数,等等。
参数名称 evaluation context 任何一个方法参数的名称。如果由于某种原因,这些名称无法使用(例如,因为在编译的字节码中没有debug信息),单个参数也可以使用 #a<#arg> 语法,其中 <#arg> 代表参数索引(从0开始)。 #blEvent 或 #a0(你也可以使用 #p0 或 #p<#arg> 参数符号作为别名)。


请注意,#root.event 让你可以访问底层事件,即使你的方法签名实际上指的是一个被发布的任意对象。

如果你需要发布一个事件作为处理另一个事件的结果,你可以改变方法签名以返回应该被发布的事件,如下例所示。

@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

异步监听器 不支持此功能。

handleBlockedListEvent() 方法为其处理的每个 BlockedListEvent 发布一个新的 ListUpdateEvent。如果你需要发布几个事件,你可以返回一个 Collection 或者一个事件数组来代替。

异步监听器

如果你想让一个特定的监听器异步处理事件,你可以重新使用 常规的 @Async 支持。下面的例子展示了如何做到这一点。

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}

使用异步事件时要注意以下限制。

  • 如果一个异步事件监听器抛出一个异常,它不会被传播给调用者。参见 AsyncUncaughtExceptionHandler 获取更多细节。
  • 异步事件监听器方法不能通过返回一个值来发布后续的事件。如果你需要发布另一个事件作为处理的结果,请注入一个 ApplicationEventPublisher 来手动发布该事件。

监听顺序

如果你需要一个监听器在另一个之前被调用,你可以在方法声明中添加 @Order 注解,如下例所示。

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}

一般性事件

你也可以使用泛型来进一步定义你的事件的结构。考虑使用 EntityCreatedEvent,其中 T 是被创建的实际实体的类型。例如,你可以创建下面的监听器定义,只接收一个 Person 的 EntityCreatedEvent。

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}

由于泛型擦除,只有当被触发的事件解析了事件监听器过滤的泛型参数时,这才起作用(也就是说,像 class PersonCreatedEvent extends EntityCreatedEvent { …​ })。

在某些情况下,如果所有的事件都遵循相同的结构,这可能会变得相当乏味(前面的例子中的事件应该就是这样)。在这种情况下,你可以实现 ResolvableTypeProvider,以引导框架超越运行时环境提供的内容。下面的事件展示了如何做到这一点。

public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

    public EntityCreatedEvent(T entity) {
        super(entity);
    }

    @Override
    public ResolvableType getResolvableType() {
        return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
    }
}

这不仅适用于 ApplicationEvent,而且适用于任何你作为事件发送的任意对象。

云服务器 免费试用 一键搭建个人网站 现在购买,领取免费顶级域名 糯米云
5
1
0
21
评论 0
@Johniya.top