ApplicationContext应用级功能
org.springframework.beans.factory 包提供了管理和使用bean的基本功能, org.springframework.context 包下添加了继承BeanFactory的ApplicationContext接口,其提供了额外的应用级功能
ApplicationContext提供了如下功能:
- 通过
MessageSource接口进行国际化 - 通过
ResourceLoader接口获取URLs或者文件等资源 - 事件发布,通过
ApplicationEventPublisher通知实现ApplicationListener接口的bean - 加载多个上下文环境(继承关系),通过
HierarchicalBeanFactory使得每一个context能专注于其功能项,比如web应用的前端控制器层。
使用 MessageSource进行国际化
ApplicationContext接口继承了MessageSource接口,因此ApplicationContext能够提供国际化的功能,Spring也提供HierarchicalMessageSource来支持分层处理国际化,接口定义的方法包括:
String getMessage(String code, Object[] args, String default, Locale loc): 从MessageSource中获取国际化消息的基本方法。如果没有找到相应的国际化消息结果,则使用默认的值。使用MessageFormat进行参数位置替换。String getMessage(String code, Object[] args, Locale loc): 本质上和以上方法一样,唯一不同是不提供默认值,如果未找到则抛出NoSuchMessageExceptionString getMessage(MessageSourceResolvable resolvable, Locale locale): 上述方法使用到的参数被封装到MessageSourceResolvable
当ApplicationContext被加载时,会自动查询容器中的MessageSourcebean,并且这个bean的名字必须为messageSource,如果找到MessageSourcebean,所有相关的方法都会被代理到这个bean实现,如果未找到,ApplicationContext会查询其父容器中的messageSourcebean,如果未找到任何messageSourcebean,将会实例化DelegatingMessageSource进行使用。
spring提供了2个MessageSource实现类: ResourceBundleMessageSource 和 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>以上表示类路径下有3个资源集合: format, exceptions, windows ,任何请求都将通过ResourceBundle – JDK的标准方式进行国际化处理。
国际化文件编写案例:
# in format.properties
message=Alligators rock! # in 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!解析:上述MessageSource定义在beans.xml中,messageSourcebean通过basenames指定国际化资源文件。上述指定了3个文件: format.properties, exceptions.properties, windows.properties
MessageSource获取使用参数:
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- lets inject the above MessageSource into this POJO -->
<bean id="example" class="com.something.Example">
<property name="messages" ref="messageSource"/>
</bean>
</beans>public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.ENGLISH);
System.out.println(message);
}
}结果如下:
The userDao argument is required.如果想要进行英国的国际化(en-GB),需要创建 format_en_GB.properties, exceptions_en_GB.properties, windows_en_GB.properties。比如:
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
} 结果如下:
Ebagum lad, the 'userDao' argument is required, I say, required.也可以使用 MessageSourceAware 接口来获取MessageSource对象,ApplicationContext中任何实现MessageSourceAware接口的对象在创建和被配置的时候都将被注入MessageSource对象。
tips:ResourceBundleMessageSource的一个可选实现是ReloadableResourceBundleMessageSource,ReloadableResourceBundleMessageSource比基于JDK的ResourceBundleMessageSource更加灵活,尤其是支持从任何spring资源加载位置读取(不仅仅是类路径)以及支持热加载, 详情:ReloadableResourceBundleMessageSource
标准和自定义事件
ApplicationContext通过ApplicationEvent和ApplicationListener提供事件处理机制,如果容器中一个bean实现了ApplicationListener接口,每当有ApplicationEvent发布到ApplicationContext中就会通知此bean,本质上这就是观察者模式的实现。
Spring 4.2之后,事件机制得到增强,Spring提供了注解开发模式以及事件不再必须实现ApplicationEvent接口,当发布事件时,Spring自动将其封装为ApplicationEvent。
Spring提供的标准事件:
| Event | 解释 |
|---|---|
ContextRefreshedEvent | 在初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的refresh()方法)。在这里,“已初始化”是指所有Bean均已加载,检测到并激活了后处理器Bean,已预先实例化单例并且可以使用ApplicationContext对象。只要尚未关闭上下文,只要所选的ApplicationContext实际上支持这种“热”刷新,就可以多次触发刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。 |
ContextStartedEvent | 使用ConfigurableApplicationContext接口上的start()方法启动ApplicationContext时发布。在这里,“已启动”是指所有Lifecycle bean都收到一个明确的启动信号。通常,此信号用于在显式停止后重新启动Bean,但也可以用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。 |
ContextStoppedEvent | 通过使用ConfigurableApplicationContext接口上的stop()方法停止ApplicationContext时发布。此处,“已停止”表示所有Lifecycle bean均收到明确的停止信号。停止的上下文可以通过start()调用重新启动。 |
ContextClosedEvent | 通过使用ConfigurableApplicationContext接口上的close()方法或通过JVM关闭钩子关闭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...
}发布事件需要调用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;
}
// 重写 setApplicationEventPublisher方法获取ApplicationEventPublisher
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与Spring容器交互。
为了能监听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带有范型,意味着onApplicationEvent()方法不需要我们进行强转,我们可以注册许多监听器,但是默认情况下监听器是同步接受事件的,也就是说 publishEvent() 在直到所有监听器被执行完之前是一只阻塞的,好处就是能在同一线程中执行,并且如果存在事务环境,也能保持一致。如果需要使用其他策略,考虑 ApplicationEventMulticaster以及实现类 SimpleApplicationEventMulticaster
注册以上的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的 sendEmail() 被调用时,如果有黑名单中的邮件,则会发布BlockedListEvent,blockedListNotifier就会被通知
spring的事件机制的设计是被用来处理同一个
ApllicationContext中的Bean进行交互的,如果需要更加复杂的交互能力需要使用Spring Integration ,能够构建轻量级、基于事件驱动的应用
基于注解的事件监听
Spring 4.2之后能够在任何Bean中的public方法上加上@EventListener注解完成事件监听,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() {
// ...
}还能够使用注解的condition属性进行事件过滤,并且是使用 SpEL expression (Spring表达式)条件配置:
@EventListener(condition = "#blEvent.content == 'my-event'") // 只有当BlockedListEvent事件中的content属性为my-event时才进行调用
public void processBlockedListEvent(BlockedListEvent blockedListEvent) {
// notify appropriate parties via notificationAddress...
}事件上下文环境可用的元数据信息:
| Name | Location | Description | Example |
|---|---|---|---|
| Event | root object | 实际的 ApplicationEvent. | #root.event 或者 event |
| Arguments array | root object | 用于调用方法的参数(作为对象数组) | #root.args 或者 args;使用 args[0] 获取第一个参数 |
| Argument name | evaluation context | 任何方法参数的名称。如果由于某种原因这些名称不可用(例如,由于在编译的字节码中没有调试信息),则也可以使用#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...
}但是此用法不支持异步调用监听器
这个方法在美处理完一个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...
}通用事件
在某些情况下,如果所有事件都遵循相同的结构,这可能会变得很通用性(就像前面示例中的事件一样)。 在这种情况下,可以实现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()));
}
}使用 EntityCreatedEvent<T> 构造通用的事件,其中T是实际的事件实体,比如:只接收EntityCreatedEvent事件的监听器:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}这不光对
ApplicationEvent有效,而且可以是任意事件对象
便捷的资源获取方式
Spring将应用资源抽象为Resource接口以及通过ResourceLoader接口进行加载得到。并且ApplicationContext实现了ResourceLoader接口,Resource抽象相当于是增强版的 JDK java.net.URL 类,实际上,Resource的实现包装了java.net.URL的实例,Resource获取几乎任意位置的资源,包括类路径,文件系统,以及使用标准URL描述的资源。
可以通过实现ResourceLoaderAware接口,并注入到容器中,在初始化时期就能被回掉,获取到ResourceLoader实例(通常就是ApplicationContext)
ApplicationContext的构造器通过接收资源的地址字符串,其需要依赖具体的上下文环境进行加载,比如,ClassPathXmlApplicationContext将其识别为类路径下,也可以使用特定的前缀进行指定,强制其加载为类路径或URL,而不管是什么具体的上下环境。

