为了以更面向框架的风格来增强BeanFactory的功能,上下文包还提供了以下功能:
通过MessageSource接口访问i18n(国际化)风格的信息。通过ResourceLoader接口访问资源,如URL和文件。通过使用ApplicationEventPublisher接口的发布器把事件通知给实现ApplicationListener接口的Bean监听器,来达到事件发布。通过HierarchicalBeanFactory接口加载多个上下文,并让每一个上下文都关注于一个特定的层,如web层。通过实现MessageSource接口定制国际化功能(也可以使用Spring已提供的实现类),Spring还提供了其子接口HierarchicalMessageSource,该子接口可以分层地解析消息。这两个接口一起提供了Spring实现消息解析的基础。
MessageSource接口定义了三个重载方法:
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;其参数含义为:
code:要获取的消息字符串。args:一个参数数组,用于给消息中的参数中进行填充。defaultMessage:在没发现消息情况下返回设置的默认消息。locale:本地化。resolvable:集成包含code、args、defaultMessage这三个参数。当容器初始化时,会先去搜索有无定义的MessageSource类型的Bean在容器中,其Bean名称必须为messageSource。如果发现了此Bean,会将前面三个方法委托给此消息源调用;若没有发现,则会继续搜索包含同名Bean的父消息源,若还是没有发现,则容器会实例化一个空的DelegatingMessageSource,以便能调用上述方法。
Spring总共提供了4个MessageSource实现类:ResourceBundleMessageSource、StaticMessageSource、DelegatingMessageSource和ReloadableResourceBundleMessageSource。它们都实现了HierarchicalMessageSource接口以执行嵌套消息传递。最常用的则是ResourceBundleMessageSource实现类。
假设我们准备了以下国际化资源文件:
# myi18n.properties name=default my name is {0} # myi18n_en_US.properties name=english my name is {0} # myi18n_zh_CN.properties name=chinese my name is {0}然后我们在配置类中注册MessageSourceBean,显然,我们得使用ResourceBundleMessageSource实现类。
@Configuration public class MyConfig { @Bean("messageSource") public MessageSource ResourceBundleMessageSource(){ //注意:引用类型是ResourceBundleMessageSource,否则无法使用下面的方法 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); //添加单个或多个国际化资源文件,通过基名。 messageSource.setBasenames("myi18n"); return messageSource; } }由于ApplicationContext接口是MessageSource接口的子接口,则我们可以直接通过容器上下文对象调用上述三个方法。
public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class); //获取zh_CN地区的消息 String message = ac.getMessage("name", new Object[]{"张三"},"default", Locale.SIMPLIFIED_CHINESE); //打印:chinese my name is 张三 System.out.println(message); }你也可以通过实现MessageSourceAware接口来获取已定义的任何MessageSource引用,使用方法和ApplicationContextAware一致,在Bean初始化时,任何实现xxxAware接口的Bean都会被注入相应的引用。
ResourceBundleMessageSource还有其它替代方案,Spring提供了ReloadableResourceBundleMessageSource实现类,它同样支持bundle文件格式,但是比基于JDK标准的ResourceBundleMessageSource更加灵活。它还能读取外部文件(不仅仅是类路径下),此外,它还支持bundle属性文件的热加载。
容器中的事件发布依托ApplicationEventPublisher接口,而容器中的事件创建和监听则分别是通过ApplicationEvent抽象类和ApplicationListener接口实现的。如果一个Bean实现ApplicationListener接口并且注册到容器中,那么每一个ApplicationEvent实现类被发布到容器中时(即触发事件然后进行发布),该Bean都会得到通知并回调相应方法。在设计模式中这是典型的观察者模式。
自Spring4.2后,关于事件基础方面的功能已得到改善,并加入了基于注解策略以及发布任意事件的能力,无需再继承其ApplicationEvent。
关于Spring所提供的标准事件:
事件描述ContextRefreshedEvent当容器初始化或刷新时发布事件(刷新指容器上下文对象的refresh()方法),只要容器上下文对象未关闭,可重复刷新。但部分容器上下文对象不支持刷新功能。ContextStartedEvent当容器上下文对象使用start()方法显式的启动时发布事件,一般用于容器停止后再重新启动。ContextStoppedEvent当容器上下文对象使用stop()方法显式的停止时发布事件。所有生命周期内的Bean都将会接收一个停止的信号。ContextClosedEvent当容器上下文对象使用close()方法时发布事件。容器关闭,销毁所有单例Bean,并且无法重新开启和刷新。RequestHandledEvent应用于web中,当请求完成后发布事件。ServletRequestHandledEventRequestHandledEvent子类,用于添加特定的servlet上下文信息。你也可以创建和发布自定义事件。通过继承ApplicationEvent实现自定义事件。
public class AddUserEvent extends ApplicationEvent { private final int id; private final String name; public AddUserEvent(Object source, int id, String name) { super(source); this.id = id; this.name = name; } }然后由ApplicationEventPublisherAware 获取其事件发布对象引用,再调用publishEvent()方法即可完成自定义事件发布。
@Service public class UserdService implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void AddUser(User user) { if (// ...) { //事件发布 publisher.publishEvent(new AddUserEvent(this, user.getId(), user.getName())); // Add User... } } }最后通过注册实现ApplicationListener接口的Bean监听接收事件。
@Component public class AddUserEventListener implements ApplicationListener<AddUserEvent> { public void onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... } }Spring的事件机制是为同一容器上下文中的Bean之间的简单通讯而设计的。然而,在大型且复杂的企业级需求下,会使用单独维护的项目,它有着更完整更轻量的支持,且往往在Spring构建之上。
通过方法级的@EventListener注解注册事件监听器,并且不再需要实现ApplicationListener接口。
@Component public class AddUserEventListener { @EventListener public void onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... } }一般情况下,@EventListener注解通过其方法参数匹配对应事件。当然,也可以通过其注解属性指定单个或多个事件类型。
@Component public class AddUserEventListener { @EventListener({AddUserOneEvent.class, AddUserTwoEvent.class}) public void onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... } }甚至通过SpEL表达式进行逻辑匹配。
@Component public class AddUserEventListener { @EventListener(condition = "#event.content == 'my-event'") public void onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... } }请注意,#root.event允许你访问底层事件,及时你方法参数中实际引用了任意的事件对象。
具体SpEL表达式的使用请参阅官方文档
你也可以通过这个监听器为跳板,发布下一个或多个事件集合。
@Component public class AddUserEventListener { @EventListener public OtherEvent onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... //发布其他事件 return new OtherEvent(...); } }此外,你还可以添加@Async和@Order注解实现异步事件监听和事件监听排序。
使用异步事件监听需要注意的问题:
若异步事件监听出现异常,它不会响应给调用者。异步事件监听不支持通过返回值发布后续事件。你可以使用声明的方式实例化容器上下文,如ContextLoader。也可以使用ApplicationContext编程地实例化一个ApplicationContext实例。
通过ContextLoaderListener注册一个ApplicationContext。
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>ContextLoaderListener通过检查上下文参数,若参数不存在,则默认采用/WEB-INF/applicationContext.xml路径的配置文件;若存在,则采用其参数中的一个或多个配置文件(空格、分号和逗号区分),并且路径还支持Ant风格。