文章参考来源:Spring Framework官方文档
前言:
org.springframework.beans.factory包提供了管理和操作bean的基本功能,包括以编程的方式
org.springframework.context包添加了ApplicationContext接口,它扩展了BeanFactory接口,此外还扩展了其他接口,以一种更面向应用程序框架的风格提供额外的功能。
许多人以一种完全声明的方式使用ApplicationContext,甚至不是通过编程来创建它,而是依赖于像ContextLoader这样的支持类来自动实例化ApplicationContext,作为Java EE web应用程序正常启动过程的一部分。
为了以更面向框架的方式增强BeanFactory功能,上下文包还提供了以下功能:
(1)通过MessageSource接口以i18n风格访问消息。
(2)通过ResourceLoader接口访问资源,例如url和文件。
(3)事件发布,即通过使用ApplicationEventPublisher接口发布到实现ApplicationListener接口的bean。
(4)通过HierarchicalBeanFactory接口加载多个(分层)上下文,让每个上下文都集中在一个特定的层上,比如应用程序的web层。
ApplicationContext源码如下:
package org.springframework.context;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.lang.Nullable;
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory,
HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
@Nullable
String getId();
String getApplicationName();
String getDisplayName();
long getStartupDate();
@Nullable
ApplicationContext getParent();
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
1. 使用MessageSource国际化
从ApplicationContext源码看,ApplicationContext接口继承了一个名为MessageSource的接口,因此提供了国际化(“i18n”)功能。Spring还提供了HierarchicalMessageSource接口,该接口可以按层次结构解析消息。这些接口一起提供了Spring实现消息解析的基础。在这些接口上定义的方法包括:
- String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource检索消息的基本方法。如果没有为指定的区域设置找到消息,则使用默认消息。通过使用标准库提供的MessageFormat功能,传入的任何参数都成为替换值。
- String getMessage(String code, Object[] args, Locale loc):本质上与前面的方法相同,但有一点不同:不能指定默认消息。如果找不到消息,则抛出NoSuchMessageException。
- String getMessage(MessageSourceResolvable resolvable, Locale Locale):上述方法中使用的所有属性也被包装在一个名为MessageSourceResolvable的类中,可以与此方法一起使用。
当加载ApplicationContext时,它会自动搜索上下文中定义的MessageSource bean。bean的名称必须是messageSource。如果找到了这样的bean,那么对上述方法的所有调用都将委托给消息源。如果没有找到消息源,ApplicationContext尝试查找包含同名bean的父类。如果是,则使用该bean作为MessageSource。如果ApplicationContext不能找到任何消息源,则实例化一个空的DelegatingMessageSource,以便能够接受对上面定义的方法的调用。
Spring提供了三种MessageSource实现,ResourceBundleMessageSource,ReloadableResourceBundleMessageSource和StaticMessageSource。它们都实现了HierarchicalMessageSource以实现嵌套消息传递。很少使用StaticMessageSource,但它提供了将消息添加到源的编程方式。下面的示例显示ResourceBundleMessageSource:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
以上例子中,假设在类路径中定义了三个资源包,分别是format、exceptions和windows。任何解析消息的请求都以通过ResourceBundle对象解析消息的jdk标准方式处理。为了便于示例,假设上述两个资源包文件的内容如下所示:
format.properties文件
message=Alligators rock!
exceptions.properties
argument.required=The {0} argument is required.
以下示例显示了运行MessageSource功能的程序。请记住,所有ApplicationContext实现也是MessageSource实现,因此可以转换为MessageSource接口:
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
上述程序的结果输出如下:
Alligators rock!
2. 标准和自定义事件
ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果实现ApplicationListener接口的bean被部署到上下文中,那么每当ApplicationEvent被发布到ApplicationContext时,就会通知该bean。本质上,这是标准的Observer设计模式。
Spring 4.2起,事件的发布流程有了显著改进,提供了基于注释的模型以及发布任意事件(即不必从ApplicationEvent扩展的对象)的能力。
以下给出了Spring提供的标准事件:
事件 | 描述 |
---|---|
ContextRefreshedEvent | 当初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的refresh()方法)。在这里,“initialized”意味着加载完所有bean,检测并激活后处理器bean,预实例化单例,并且ApplicationContext对象已经准备好可以使用了。只要上下文没有被关闭,刷新就可以被触发多次,前提是所选的ApplicationContext实际上支持这种“热”刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。 |
ContextStartedEvent | 通过使用ConfigurableApplicationContext接口上的start()方法启动ApplicationContext时发布。这里,“started”意味着所有生命周期bean都接收一个显式的启动信号。通常,这个信号用于在显式停止后重新启动bean,但它也可以用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。 |
ContextStoppedEvent | 在使用ConfigurableApplicationContext接口上的stop()方法停止ApplicationContext时发布。这里,“stopped”意味着所有生命周期bean都接收一个显式的停止信号。可以通过调用start()重新启动已停止的上下文。 |
ContextClosedEvent | 通过使用ConfigurableApplicationContext接口上的close()方法或通过JVM关机hook关闭ApplicationContext时发布。在这里,“关闭”意味着所有单例bean将被销毁。一旦上下文关闭,它就会到达生命的终点,无法刷新或重新启动。 |
RequestHandledEvent | 一个特定于web的事件,告诉所有bean一个HTTP请求已经得到服务。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的web应用程序。 |
ServletRequestHandledEvent | RequestHandledEvent的一个子类,用于添加特定于servlet的上下文信息。 |
当然,也可以创建和发布自己的自定义事件,如下:
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容器检测到实现了ApplicationEventPublisherAware接口的EmailService,并自动调用其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...
}
}
基于注解的事件监听器
Spring 4.2开始,可以使用@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表达式来定义监听条件:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
异步监听器
如果需要一个特定的监听器异步处理事件,可以重用常规的@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> ,其中T是创建的实际实体的类型。例如,可以创建以下侦听器定义来只为Person类接收EntityCreatedEvent:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
在某些情况下,如果所有事件都遵循相同的结构(前面示例中的事件也应该如此),那么这可能会变得非常冗长乏味。
在这种情况下,可以实现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,也适用于任何作为事件发送的对象。
3. 便利地访问底层资源
应用程序 context 是一个ResourceLoader,可用于加载Resource资源对象。Resource实质上是JDK java.net.URL类的功能更丰富版本。事实上,Resource的实现在适当的时候包装了一个java.net.URL的实例。
Resource可以以透明的方式从几乎任何位置获取底层资源,包括类路径、文件系统位置、可用标准URL描述的任何位置,以及其他一些变体。如果resource位置字符串是没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合于实际的应用程序context类型。
提供给ApplicationContext构造方法的位置路径或路径实际上是资源字符串,例如,ClassPathXmlApplicationContext将简单的位置路径视为类路径位置。还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL加载定义。