3.15.2 Standard and Custom Events

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

[Tip]
As of Spring 4.2, the event infrastructure has been significantly improved and offer an annotation-based model as well as the ability to publish any arbitrary event, that is an object that does not necessarily extend from ApplicationEvent. When such an object is published we wrap it in an event for you.
从4.2开始,事件基础结构已经得到显着改进,并提供了一个基于注解的模型使其有发布任意事件的能力,即便这个对象不一定是从ApplicationEvent扩展的。 当这样的对象被发布时,我们会将它包装在一个事件中。

Spring提供以下标准事件:

Table 3.7. Built-in Events

Event Explanation
ContextRefreshedEvent ApplicationContext初始化或者刷新时发布,比如,使用ConfigurableApplicationContext接口的refresh()方法。这里"初始化"的意思是指,所有的bean已经被加载、post-processor后处理bean已经被探测到并激活,单例bean已经pre-instantiated预先初始化,并且ApplicationContext对象已经可用。只要context上下文未关闭,可以多次触发刷新动作, 某些ApplicationContext支持"热"刷新。比如,XmlWebApplicationContext支持热刷新,GenericApplicationContext就不支持。
ContextStartedEvent ApplicationContext启动时候发布,使用ConfiruableApplicationContext接口的start()方法。这里的“启动”意思是指,所有的Lifecycle生命周期bean接收到了明确的启动信号。通常,这个信号用来在明确的“停止”指令之后重启beans,不过也可能是使用了启动组件,该组件并未配置自动启动,比如:组件在初始化的时候并未启动。
ContextStoppedEvent ApplicationContext停止时发布,使用ConfigurableApplicationContext接口的stop()方法。这里的“停止”的意思是指所有的Lifecycle生命周期bean接收到了明确的停止信号。一个停止了的context上下文可以通过start()调用来重启。
ContextClosedEvent ApplicationContext关闭时候发布,使用ConfigurableApplicationContext接口的close()方法。这里“关闭”的意思是所有的单例bean已经销毁。一个关闭的context上下文达到的生命周期的重点。不能刷新,不能重启。
RequestHandledEvent 是一个web专用事件,告诉所有的beans:一个HTTP request正在处理。这个时间在reqeust处理完成之后发布。该事件仅适用于使用Spring的DispatcherServlet的web应用。

您还可以创建和发布自己的自定义事件。 这个例子演示了一个简单的类,它继承了Spring的ApplicationEvent类:

public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String test;

    public BlackListEvent(Object source, String address, String test) {
        super(source);
        this.address = address;
        this.test = test;
    }

    // accessor and other methods...

}

要发布一个自定义的ApplicationEvent,在ApplicationEventPublisher上调用publishEvent()方法。 通常这是通过创建一个实现ApplicationEventPublisherAware并将它注册为Spring bean的类来实现的。看样例:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

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

    public void sendEmail(String address, String text) {
        if (blackList.contains(address)) {
            BlackListEvent event = new BlackListEvent(this, address, text);
            publisher.publishEvent(event);
            return;
        }
        // send email...
    }

}

在配置期间,Spring容器将检测到EmailService实现了 ApplicationEventPublisherAware ,并将自动调用setApplicationEventPublisher()。 实际上,传入的参数将是Spring容器本身; 你只需通过ApplicationEventPublisher接口就可以很简单的在应用程序上下文进行交互。

要接收自定义的ApplicationEvent,首先创建一个实现ApplicationListener的类然后注册为一个Spring bean。 看例子:

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

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

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

}

注意,ApplicationListener一般是用你自定义事件的类型BlackListEvent作为范型类参数化的。 这意味着onApplicationEvent()方法可以保持类型安全,无需向下转型。 event listener事件监听想注册多少就注册多少,但请注意,默认情况下事件侦听器同步接收事件。 这意味着publishEvent()方法阻塞,直到所有侦听器都完成处理事件。 这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它在发布者的事务上下文中操作。 如果需要其他的事件发布策略,请参考JavaDoc for Spring的ApplicationEventMulticaster接口。

下面的样例展示了之前提到过的类如何定义bean并注册、配置:

<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="[email protected]"/>
</bean>

综合起来,当emailService bean的sendEmail()方法被调用时,如果有任何电子邮件应该被列入黑名单,则会发布一个BlackListEvent类型的自定义事件。 blackListNotifier bean被注册为一个ApplicationListener,因此接收BlackListEvent,此时它可以通知适当的对象。

[Note]
Spring的事件机制被设计用于在同一应用程序上下文中的Spring bean之间进行简单的通信。 然而,对于更复杂的企业集成需求,有单独维护的Spring Integration 项目提供了完整的支持并可用于构建轻量级,pattern-oriented(面向模式),依赖Spring编程模型的事件驱动架构

Annotation-based Event Listeners

从Spring 4.2开始,事件监听器可以通过EventListener注解在托管bean的任何公共方法上注册。 BlackListNotifier可以改写如下:

public class BlackListNotifier {

    private String notificationAddress;

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

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

}

As you can see above, the method signature actually infer which even type it listens to. This also works for nested generics as long as the actual event resolves the generics parameter you would filter on.

If your method should listen to several events or if you want to define it with no parameter at all, the event type(s) can also be specified on the annotation itself:

正如你上面所看到的,方法签名实际上用来推断它所侦听的类型。 这也适用于嵌套泛型,只要实际事件解析了你将过滤的泛型参数。

如果你的方法应该监听几个事件,或者如果你想定义的这个方法没有任何参数,也可以在注解本身上指定事件类型:

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

}

It is also possible to add additional runtime filtering via the condition attribute of the annotation that defines a SpEL expression that should match to actually invoke the method for a particular event.

For instance, our notifier can be rewritten to be only invoked if the test attribute of the event is equal to foo:

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

例如,如果事件的test属性等于foo,我们的通知器可以重写为仅被调用:

@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

Each SpEL expression evaluates again a dedicated context. The next table lists the items made available to the context so one can use them for conditional event processing:

每个“SpEL”表达式再次评估一个专用上下文。 下一个表列出了可用于上下文的项目,因此可以使用它们进行条件事件处理:

Table 3.8. Event SpEL available metadata

Name Location Description Example
Event root object The actual ApplicationEvent #root.event
Arguments array root object The arguments (as array) used for invoking the target #root.args[0]
Argument name evaluation context Name of any of the method arguments. If for some reason the names are not available (e.g. no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0). #blEvent or #a0 (one can also use #p0 or #p<#arg>notation as an alias).

注意,#root.event允许你访问底层事件,即使你的方法签名实际上引用了一个被发布的任意对象。

如果你需要发布一个事件作为处理另一个事件的结果,只需更改方法签名返回应该发布的事件(即更改方法返回类型),如:

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
[Note]
异步侦听器.不支持此功能。

这将通过上述方法处理每个“BlackListEvent”并发布一个新的“ListUpdateEvent”。 如果你需要发布几个事件,只需返回一个Collection的事件。

Asynchronous Listeners

If you want a particular listener to process events asynchronously, simply reuse the regular @Async support:

如果你想要一个特定的监听器异步处理事件,只需重复使用regular @Async support:

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

使用异步事件时请注意以下限制:

如果事件监听器抛出一个Exception,它不会传播给调用者,请检查AsyncUncaughtExceptionHandler以获取更多细节。 2.此类事件侦听器无法发送回复。 如果你需要将处理的结果发送另一个事件,注入ApplicationEventPublisher来手动发送此事件。

Ordering Listeners

如果你需要在另一个监听器之前调用某个监听器,只需在方法声明中添加@Order注解:

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

Generic Events

你还可以使用泛型来进一步定义事件的结构。 考虑一个EntityCreatedEvent,其中T是创建的实际实体的类型。 你可以创建以下侦听器定义,以便只接收PersonEntityCreatedEvent:

@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()));
       }
   }
[Tip]
这不仅适用于ApplicationEvent,而且可以作为一个事件发送任何一个任意对象。

results matching ""

    No results matching ""