一、Spring框架概述
二、SpringFramework4.x的新特性
三、核心技术
7.15 ApplicationContext的附加功能
正如在第一章导言中所讨论的那样,org.springframework.beans.factory包 提供了管理和操作bean的基本功能,包括以程序化的方式。这org.springframework.context包添加了ApplicationContext接口,扩展了BeanFactory接口。除了扩展其他接口以提供更多面向应用程序框架的样式的附加功能之外。许多人使用ApplicationContext以完全声明的方式,甚至不以编程方式创建,而是依赖于支持类,比如ContextLoader 自动实例化ApplicationContext作为JavaEEWeb应用程序正常启动过程的一部分。
增强BeanFactory功能在更面向框架的样式中,context包还提供以下功能:
- 通过MessageSource接口访问i18n样式的消息。
- 通过ResourceLoader接口访问资源,如url和文件。
- 通过使用ApplicationEventPublisher接口,将事件发布到实现ApplicationListener接口的bean。
- 通过HierarchicalBeanFactory接口加载多个(层次)上下文,允许每个上下文集中于一个特定的层,例如应用程序的web层。
7.15.1 使用MessageSource的国际化
7.15.2 标准和自定义事件
ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果将实现ApplicationListener接口的bean部署到上下文中,则每次ApplicationEvent发布到ApplicationContext时,都会通知该bean。本质上,这是标准的观察者设计模式。
从Spring4.2开始,事件基础设施得到了显著的改进,提供了一个基于注释的模型以及发布任意事件的能力,这是一个不一定从ApplicationEvent扩展的对象当这样的对象发布时,我们会为您将其包装在事件中。
Spring提供了以下标准事件:
表7.7。内置事件
事件 | 解释 |
---|---|
ContextRefreshedEvent | 在初始化或刷新ApplicationContext时发布,例如,在ConfigurableApplicationContext接口上使用refresh()方法。”这里的Initialized“意味着加载所有bean,检测并激活后处理器bean,预实例化单例,并且ApplicationContext对象已准备好使用。只要上下文尚未关闭,就可以多次触发刷新,前提是所选的ApplicationContext实际上支持这种“热”刷新。例如,XmlWebApplicationContext支持热刷新,而genericaplicationcontext不支持。 |
ContextStartedEvent | 在启动ApplicationContext时发布,使用ConfigurableApplicationContext接口上的start()方法。“这里的Started意味着所有生命周期bean都接收到一个显式的启动信号。通常,此信号用于在显式停止后重新启动be an,但也可用于启动尚未配置为自动启动的组件,例如,在初始化时尚未启动的组件。 |
ContextStoppedEvent | 在停止ApplicationContext时发布,使用ConfigurableApplicationContext接口上的stop()方法。“这里的Stopped意味着所有生命周期bean都接收到一个显式的停止信号。停止的上下文可以通过start()调用重新启动。 |
ContextClosedEvent | 在关闭ApplicationContext时发布,使用ConfigurableApplicationContext接口上的close()方法。“这里的“Closed”意味着所有的singleton bean都被销毁了。关闭的上下文已到其生命周期的末尾;无法刷新或重新启动它。 |
RequestHandledEvent | 一个特定于web的事件,告诉所有bean HTTP请求已得到服务。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的web应用程序。 |
您还可以创建和发布自己的自定义事件。此示例演示了一个扩展Spring的ApplicationEvent基类的简单类:
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlackListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
若要发布自定义ApplicationEvent,请对ApplicationEventPublisher调用publishEvent()方法。通常,这是通过创建实现ApplicationEventPublisherAware的类并将其注册为SpringBean来完成的。下面的示例演示了此类:
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 content) {
if (blackList.contains(address)) {
publisher.publishEvent(new BlackListEvent(this, address, content));
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通常是用自定义事件blackListenEvent的类型参数化的。这意味着onApplicationEvent()方法可以保持类型安全,避免任何向下转换的需要。您可以注册任意多个事件侦听器,但请注意,默认情况下,事件侦听器同步接收事件。这意味着publishEvent()方法将阻塞,直到所有侦听器都完成对事件的处理。这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它将在发布服务器的事务上下文中操作。如果需要另一个事件发布策略,请参阅JavadocforSpring的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的sendmail()方法时,如果有任何电子邮件应该被列入黑名单,则会发布一个blacklistenvent类型的自定义事件。blackListNotifier bean注册为ApplicationListener,从而接收BlackListEvent,此时它可以通知适当的各方。
Spring的事件机制是为同一应用程序上下文中springbean之间的简单通信而设计的。然而,对于更复杂的企业集成需求,单独维护的Spring集成项目为构建基于众所周知的Spring编程模型的轻量级、面向模式、事件驱动的体系结构提供了完全的支持。
基于注解的事件监听器
从Spring4.2开始,事件监听器可以通过EventListener注释注册到托管be an的任何公共方法上。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...
}
}
正如上面所看到的,方法签名再次声明它所侦听的事件类型,但这次是以灵活的名称,而没有实现特定的侦听器接口。只要实际事件类型在其实现层次结构中解析泛型参数,也可以通过泛型缩小事件类型的范围。
如果您的方法应侦听多个事件,或者如果您希望定义它而不使用任何参数,则也可以在批注本身上指定事件类型:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}
还可以通过注释的condition属性添加额外的运行时筛选,该注释定义了一个SpEL表达式,该表达式应匹配以实际调用特定事件的方法。
例如,只有当事件的content 属性等于foo:
@EventListener(condition = "#blEvent.content == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个SpEL表达式都根据一个专用上下文进行求值。下一个表列出了上下文可用的项,以便可以将它们用于条件事件处理:
表7.8。事件SpEL可用元数据
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
Event | root object | 实际的ApplicationEvent | #root.event |
Arguments array | root object | 用于调用目标的参数(作为数组) | #root.args[0] |
Argument name | evaluation context | 任何方法参数的名称。如果由于某种原因名称不可用(例如没有调试信息),则参数名称也可以在参数索引(从0开始)的#a<#arg>下使用。 | #blEvent或#a0(也可以使用#p0或#p<#arg>符号作为别名)。 |
请注意,#root.event允许您访问底层事件,即使您的方法签名实际上引用了已发布的任意对象。
如果处理另一个事件后需要发布事件,只需更改方法签名以返回应发布的事件,如下所示:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步侦听器不支持此功能。
异步监听器
如果希望特定监听器异步处理事件,只需重用常规的@Async支持:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
使用异步事件时请注意以下限制:
- 如果事件监听器引发异常,则不会将其传播到调用方,有关详细信息,请检查AsyncUncaughtExceptionHandler。
- 此类事件监听器无法发送答复。如果在处理过程中需要发送另一个事件,请插入ApplicationEventPublisher以手动发送事件。
排序监听器
如果需要在另一个监听器之前调用监听器,只需将@Order注释添加到方法声明:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
泛型事件
您还可以使用泛型进一步定义事件的结构。考虑一个EntityCreatedEvent< T >,其中T是创建的实际实体的类型。您可以创建以下侦听器定义以仅接收一个人的EntityCreatedEvent:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}
由于类型擦除,只有在触发的事件解析事件监听器筛选的泛型参数(类似于类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,还适用于您将作为事件发送的任意对象。