Spring IOC 之ApplicationContext的其他功能
正如上面章节所介绍的那样, org.springframework.beans.factory 包提供了管理和操作beans的
基本功能。 org.springframework.context包增加了继承了BeanFactory接口的ApplicationContext
接口,它额外了提供了一种更加面向引用框架风格的功能支持。很多人以一种完全声明的风格来使用
ApplicationContext,甚至于不通过程序的方式来创建它而是依赖例如ContextLoader的支持类来作
实例一个ApplicationContext来作为JavaEE web 应用的启动过程的一部分。
为了增强BeanFactory在面向框架风格的功能,context包也提供了下面的功能:
- 通过MessageSource接口来以i18n风格来访问信息
- 通过ResourceLoader接口来访问例如URLs和文件的资源。
- 通过使用ApplicationEventPublisher接口来发布时间到实现了ApplicationContext接口的bean.
- 通过HierarchicalBeanFactory接口来加载多个上下文,而且允许每个上下文都之关注于某一个
指定的层,例如一个应用的Web层。
1.使用MessageSource来实现国际化
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) :所有在上面的方法中
使用的属性都可以封装到一个名为MessageSourceResolable中,那么你就可以通过这个方法来使用。
当一个ApplicationContext本加载的时候,它会自动搜索在上下文中的MessageSource。Bean必须要有
名字为messageSource。如果这样的bean找到了,所有对前面的方法的调用都会委托给一个message source.
如果没有发现消息源,ApplicationContext尝试去父类中找到含有相同名字的bean。如果它找到了,它就会
它就会把这个bean当成MessageSource。如果ApplicationContext找不到消息源,那么为了接受上面方法
的调用一个空的DelegatingMesssageSource会被实例化。
Spring 提供了连个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>
在上面的例子中假设你定义了三个叫做 format,exceptions and windows的资源到你classpath中。任何解析
消息的请求都会通过ResourceBundles以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", null);
System.out.println(message);
}
上面程序的输出结果将是:
Alligators rock!
总的来说,MessageSource定义在一个存在你的根classpath中叫做beans.xml文件中。messageSource bean定义
指向了很多通过它的basenames属性绑定的资源。通过basenames属性列表传递的三个文件时在你的根
classpath 的文件存在的,而且分别叫做format.properties, exceptions.properties,windows.properties。
下面的例子显示传递给消息的参数:这些参数将会转成字符串,在查看消息中放到placeholders中。
<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.foo.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", null);
System.out.println(message);
}
}
上面调用execute()方法的输出结果是:
The userDao argument is required.
至于国际化,Spring的不同的MessageSource实现都会和标准的JDK ResourceBundle一样遵循相同的的区
域解决方案。简而言之,继续前面的定义的messageSource例子,如果你想解析英国地区的消息,那么你
需要分别创建 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。但bean被创建和配置的时候
任何定义的ApplicationContext中的bean实 现了MessageSourceAware接口会通过引用程序的上下文的
MessageSource被注入。
注意:作为ResourceBundleMessageSource的其他替代选项,Spring提供了一个ReloadableResourceBundleMessageSource。
这个提供了绑定文件格式的功能但是比标准的基于ResourceBundleMessageSource实现的JDK更加的灵活。特别
是,它允许通过Spring的资源位置读取文件(而不仅仅是通过classpath),而且支持绑定属性文件的热加载。
2.标准的和自定义事件
在ApplicationContext中的事件处理时通过ApplicationEvent类和ApplicationListener接口来提供的。如果一个实现了
ApplicationListener接口的bean被部署到上下文中,每次一个ApplicationEvent发布到ApplicationContext的时候,那么
bean就会被通知到。需要说明的是,这是标准的观察者模式:Spring提供了下面的标准事件:
事件 | 解释 |
ContextRefreshedEvent | 当ApplicationContext初始化和重新刷新的时候发布,例如,使用ConfigurableApplicationContext接口 中的refresh()方法。这儿的"初始化"意味着所有的bean被加载,post-processor bean被发现和激活,单例会 被提供实例化,而且ApplicationContext对象以及准备好可以被使用了。只要上下文没有被关闭,一个refresh 都可以被多次出发,考虑到选中的ApplicationContext实际上支持这样的“热”刷新。例如,XMLWebApplication Context支持热刷新,但是GenericApplicationContext不支持。 |
ContextStartedEvent | 在ApplicationContext启动的时候,使用CongratulationApplicatioinContext接口的start()方法的时候发布的。 这儿的“started”意味找所有的Lifecycle bean 接受到一个明确的开始信号。一般来讲,这个信号在一个明确的停止 之后用于重新开启bean,但是它还是可能用于启动还没有配置自动开启的组件。例如,还没有在初始化的时候启动的组件。 |
ContextStoppedEvent | 当使用ConfigurationApplicationContext接口的stop()方法时,ApplicationContext停止的时候发布。这人的“stoppded” 意味着所有的bean会收到明确的stop信息。一个停止的上下文可能通过一个start()调用来被重启 |
ContextClosedEvent | 使用在ConfigurableApplicationContext接口的close()方法的时,ApplicationContext被关闭的时候发布,这儿的“closed” 意味找所有的单例都会被销毁,一个关闭的上下文会到达生命周期的重点,它不可以不刷新和重启 |
RequestHandledEvent | 一个web规范事件通知所有的bean 收到了一个HTTP请求。这个事件在请求完成以后才被发布。这个事件只有在web应用中使用Spring的 DispatherServlet的时候才是可用的 |
你可以创建和发布自己自定义事件。这个例子描述了一个继承了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容器将会发现实现了ApplicationEventPublisherAwar的EmailService 并且自动的调用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方法能够保持类型安全,从而避免了向下转型的需求。只要只愿意你可以注册尽可能多的事件监听器,但是需要注意的是默认情况下监听器是同步的方式来接受事件。这意味着publishEvent()方法在所有的监听器结束处理时间之前都是阻塞的。这个同步和单线程的方式的优势就是当一个监听器收到一个时间的时候,如果一个事务上下文可用那么在发布这的事务上下文的里面它可以进行操作。如果其他的对于时间发布的策略有必要的话,可以看ApplicationEventMulticaster接口。
下面的例子显示了用于注册的bean的ing一并且把每个上面的每一个类注册了:
<bean id="emailService" class="example.EmailService">
<property name="blackList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>
<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="blacklist@example.org"/>
</bean>
当emailService的sendEmail()方法被调用的时候,把它都组装在了一起,如果如果有任何的email被加入了很名单了。一个自定义的BlackListEvent时间就会被发布。blackListNotifer bean被注册为一个ApplicationListner并且因为接收了BlackListEvent,在这点了它可以通知需要知道的当事者。
注意:Spring的事件机制是为了在相同应用上下的Spring的bean之间进行简单通信的。但是,为了更加复杂的企业级整合需求,相对独立的Spring Integration项目为构建轻量级、面向模式、事件驱动的架构提供了完整的支持(构建知名的Spring程序模型)。
3.更方便的访问低级资源
为了更佳的使用和理解应用程序的上下文,用户自己应该熟悉Spring的Resource抽象。
一个应用上下文就是可以用于加载Resources的ResourceLoader。一个Resource必须是一个功能丰富的JDK类java.net.URL,实际上,当需要的时候一个Resource的实现会被封装在一个java.net.URL的实例中。一个Resource可以一种透明的风格操作在几乎任何位置的低级资源,包括在classpath、一个文件系统、人在用标准的URL标识的和其他的地方。如果资源位置的字符串是一个没有特殊前缀的简单路径,这些资源的来源是确定的而且是适合于正式的应用程序上下文类型。
你可以配置一个部署在应用上下文中的bean实现特殊回调接口的ResourceLoaderAware,它在引用程序上下文自身被作为ResourceLoader被传入初始化的时候能够自动的被回调。你还可以暴露Resource类型的属性用于访问静态资源:他们将会和其他属性一样被注入到里面。你可以指明这些资源属性用间可能简单的字符串路径,而且当一个bean部署的时候,你可以依赖一个字自动被上下注册的JavaBean PropertyEditor来吧这些文本字符串转成实际的Resource对象。
提供给ApplicationContext构造器的本地路径实际上都是资源字符串,并且以一种简单的形式被当成是一个指定的上下文实现,ClassPathXmlApplicatioinContext对待一个简单的本地路径是一个classpath位置。你可以使用特殊的前缀来锁定路径来强迫classpath或者一个URL的定义而不管实际的上下问类型。
4.对于web应用中的方便的上下文实例化
你可以通过声明的方式,例如一个contextLoader来创建的一个ApplicationContext实例。当然了你也可以通过使用ApplicationContext的任何一个实现来通过程序的方式来穿件一个ApplicationContext实例。
你可以通过ContextLoaderListener来注册一个ApplicationContext,就像下面一样:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- or use the ContextLoaderServlet instead of the above listener
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
-->
监听器会检查contextConfigLocation参数。如果这个参数不存在的话,那么监听器会使用默认的/WEB-INF/applicationContext.xml。当这个参数确实存在的时候,监听器会通过提前定义好的分隔符(逗号、分号、和空格符)来讲字符串分开,人后使用这些值来作为ApplicationContext会搜索的路径。ANT的路径风格也是被支持的,就像/WEB-INF/Context.xml对于所有的在WEB-INF文件夹下的以Context.xml结尾的文件而/WEB-INF/**/Context.xml是对于任何在WEB-INF子目录下的所有的这样文件。
你可以使用ContextLoaderServlet来代替ContextLoaderListener。这个Servlet使用了像监听器一样的参数的contextConfigLocation。
5.把一个Spring 应用作为一个JavaEE RAR文件来部署
把上下文和所有的在一个JavaEE RAR 部署单元中它需要的类和库jars封装起来后,是可以把一个把一个Spring 应用上下文作为一个JavaEE RAR文件来部署的。这个和启动一个独立的ApplicationCotext是一样的,只需要在JavaEEl环境中就可以访问JavaEE服务器资源。RAR部署在一些没有头文件的war文件中是更加正常的选择,实际上,在JavaEEl环境中一个没有任何HTTP实体店的war文件只是用于启动一个Spring的ApplicationContext。
RAR对于那些不是需要任何HTTP实体点而是由一些信息点和已计划的工作组成的场景下是理想的选择。在这样的上下文中的Bean 能使用应用服务器资源,比如 JTA事务管理器 、绑定JNDI的JDBC数据源,和JMS连接工厂实例,还可以通过Spring的标准事务管理和 JNDI和JMX支持来注册平台的JMX服务器。Application组件还可以通过Spring的TaskExecutor来和应用服务器的JCA 工作管理器来进行交互。
对于简单的作为一个JavaEE EAR文件的SPring应用的部署:把所有的应用 的类打包到一个RAR文件,这个EAR包是使用了不同的文件扩展的标准Jar文件。把所有的需要的库jar添加到RAR归档的跟路径中。添加一个“META-INF/ra.xml”部署描述和相应的Spring XML bean定义文件(一般是“META-INF/applicationContext.xml”)而且把它丢到应用服务器的部署目录下。
注意:这样的RAR部署单元通常是独立的;他们不会向外界暴露组件,甚至不会向同一个应用的其他模块。和一个机遇RAR应用上下文进行交互通过会通过和其他模块共享的JMS来进行的。一个基于RAR的应用上下文可能是这样的,比如说,安排一些工作、和文件系统中的其他新的文件进行关联交互的。如果它只是允许从外界同步的来访问,那么它可以导出RMI端点,这个过程可以通过在相同机器中的其他应用模块来被利用。