标题
正如本章介绍中所讨论的,该org.springframework.beans.factory 包提供了用于管理和操作bean的基本功能,包括以编程方式。该org.springframework.context软件包添加了 ApplicationContext 扩展BeanFactory界面的界面,以及扩展其他界面以提供更多应用程序框架导向风格的附加功能。许多人使用ApplicationContext完全声明的方式,甚至没有以编程方式创建它,而是依赖支持类ContextLoader来自动实例化 ApplicationContextJava EE Web应用程序正常启动过程的一部分。
为了增强BeanFactory面向框架的风格的功能,上下文包还提供以下功能:
- 通过MessageSource界面访问i18n风格的消息。
- 通过ResourceLoader界面访问资源,如URL和文件。
- 事件发布到即实现ApplicationListener接口的bean ,通过使用ApplicationEventPublisher接口。
加载多个(分层)上下文,通过HierarchicalBeanFactory接口允许每个上下文关注某个特定层,例如应用程序的Web层 。
一、使用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则会抛出a。
- String getMessage(MessageSourceResolvable resolvable, Locale locale):在前面的方法中使用的所有属性也都包含在名为的类中 MessageSourceResolvable,您可以使用该方法。
当一个ApplicationContext被加载时,它会自动搜索MessageSource 上下文中定义的一个bean。这个bean必须有名字messageSource。如果找到这样的一个bean,所有对前面方法的调用都被委托给消息源。如果找不到消息源,则ApplicationContext尝试查找包含具有相同名称的bean的父项。如果是这样,它使用该bean作为MessageSource。如果 ApplicationContext无法找到任何消息源,DelegatingMessageSource则会实例化一个空 以便能够接受对上面定义的方法的调用。
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和windows。任何解析消息的请求都将以通过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被定义在一个叫做的文件中beans.xml,它存在于你的类路径的根目录下。该messageSourcebean定义是指通过它的一些资源包的basenames属性。这是在列表中传递的三个文件basenames属性存在于你的classpath根目录的文件,被称为format.properties,exceptions.properties和 windows.properties分别。
下一个示例显示传递给消息查找的参数; 这些参数将转换为字符串并插入查找消息中的占位符。
<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.
关于国际化(i18n),Spring的各种MessageSource 实现遵循与标准JDK相同的区域设置分辨率和回退规则 ResourceBundle。总之,和继续该示例messageSource先前定义的,如果你想解析British(消息en-GB)语言环境中,您将创建文件名为format_en_GB.properties,exceptions_en_GB.properties和 windows_en_GB.properties分别。
通常,区域设置解析由应用程序的周围环境管理。在这个例子中,(英国)消息将被解析的地区是手动指定的。
#在exceptions_en_GB.properties中
argument.required = Ebagum lad,我认为需要{0}参数。
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接口的bean中定义 MessageSource的bean都会在创建和配置bean时注入应用程序上下文。
作为一种选择ResourceBundleMessageSource,Spring提供了一个 ReloadableResourceBundleMessageSource类。该变体支持相同的包文件格式,但比标准的基于JDK的ResourceBundleMessageSource实现更灵活 。特别是,它允许从任何Spring资源位置(而不仅仅是从类路径)读取文件,并支持热重载bundle属性文件(同时有效地缓存它们)。
二、标准和自定义事件
ApplicationContext通过ApplicationEvent 类和ApplicationListener接口提供事件处理。如果实现ApplicationListener接口的beanA 部署到上下文中,则每次 ApplicationEvent发布到该ApplicationContextbean时,都会通知该beanA。实质上,这是标准Observer设计模式。
Spring提供了以下标准事件:
Event | Explanation |
---|---|
ContextRefreshedEvent | 在ApplicationContext上下文中初始化或者刷新。例如,使用ConfigurableApplicationContext中的refresh()方法。这里的“初始化”意味着所有的bean都被加载,检测并激活后处理器bean,单例被预先实例化,并且该ApplicationContext对象已准备好使用。只要上下文尚未关闭,刷新可以多次触发,前提是所选内容ApplicationContext实际上支持“热”刷新。例如,XmlWebApplicationContext支持热点刷新,但GenericApplicationContext不支持 。 |
ContextStartedEvent | 在ApplicationContext启动时发布,使用ConfigurableApplicationContext上下文中的start()方法。这里的“开始”意味着所有的Lifecycle bean都会收到明确的启动信号。通常,此信号用于在显式停止后重新启动Bean,但它也可用于启动尚未配置为自动启动的组件,例如尚未启动初始化的组件。 |
ContextStoppedEvent | 在ApplicationContext停止时发布,使用ConfigurableApplicationContext上下文中的stop()方法。这里“停止”意味着所有的Lifecycle bean都会收到明确的停止信号。停止的上下文可以通过start()呼叫重新启动 。 |
ContextClosedEvent | 在ApplicationContext关闭时发布,使用界面close()上的方法 ConfigurableApplicationContext。这里的“关闭”意味着所有的单例bean被销毁。封闭的环境达到其生命的尽头; 它不能被刷新或重新启动。 |
RequestHandledEvent | 一个特定于web的事件,告知所有bean HTTP请求已被服务。此事件在请求完成后发布。此事件仅适用于使用Spring的Web应用程序DispatcherServlet。 |
您还可以创建和发布自己的自定义事件。这个例子演示了一个扩展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容器将检测到该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它通常用您的自定义事件的类型进行参数化BlackListEvent。这意味着该onApplicationEvent()方法可以保持类型安全,避免任何向下转换的需要。您可以根据需要注册许多事件侦听器,但请注意,默认情况下事件侦听器会同步接收事件。这意味着publishEvent()方法会阻塞,直到所有听众完成处理事件。这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文内部运行。如果需要另一个事件发布策略,请参考Spring ApplicationEventMulticaster界面的javadoc 。
以下示例显示了用于注册和配置上述每个类的bean定义:
<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>
综合起来,当调用bean 的sendEmail()方法时emailService,如果有任何应该被列入黑名单的电子邮件,BlackListEvent则会发布类型的自定义事件 。这个blackListNotifierbean被注册为一个 ApplicationListener并且因此接收到BlackListEvent,在此时它可以通知适当的各方。
Spring的事件机制被设计为在同一个应用程序上下文中的Spring bean之间进行简单的通信。然而,对于更复杂的企业集成需求,单独维护的 Spring Integration项目为构建轻量级,面向模式的事件驱动架构提供完全支持, 该架构基于着名的Spring编程模型。
三、基于注释的事件监听器
从Spring 4.2开始,可以通过@EventListener
注释在托管bean的任何公共方法上注册事件侦听器。该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表达式,该表达式应匹配以实际调用特定事件的方法。
例如,如果test事件的属性等于foo:我们的通知器可以被重写为仅被调用:
@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个SpEL表达式再次评估一个专用的上下文。下表列出了可用于上下文的项目,以便可以将它们用于条件事件处理:
Table 8. Event SpEL available metadata
Name | Location | Description | Example |
---|---|---|---|
Event | root object | The actual ApplicationEvent | root.event |
root.event | root.event | 用于调用目标的参数(如数组) | 用于调用目标的参数(如数组) |
Argument name | Argument name | 任何方法参数的名称。如果由于某种原因名称是不可用(例如,没有调试信息),参数名称也是在现有的#a<#arg> 地方#arg代表的说法指数(从0开始)。 | blEvent或者#a0(也可以使用#p0或#p<#arg>标记作为别名) |
注意#root.event,即使您的方法签名实际引用了已发布的任意对象,也可以访问基础事件。
如果您需要发布一个事件作为处理另一个事件的结果,只需更改方法签名以返回应该发布的事件,如下所示:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步监听器不支持这个东西
这种新方法将为上述方法的ListUpdateEvent每个BlackListEvent处理发布一个新的方法。如果您需要发布多个事件,则只需返回一些Collection事件。
四、异步监听器
如果您希望特定的侦听器异步处理事件,simply reuse the regular @Async
support:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
使用异步事件时请注意以下限制:
如果事件监听器抛出Exception它不会传播给调用者,请检查AsyncUncaughtExceptionHandler更多细节。
这种事件监听器不能发送回复。如果您需要发送另一个事件作为处理结果,请注入ApplicationEventPublisher以手动发送事件。
五、(Ordering listeners)监听器的顺序
如果您需要在另一个之前调用侦听器,只需将该@Order
注释添加到方法声明中:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
六、通用事件
您也可以使用泛型来进一步定义事件的结构。考虑 创建实际实体的类型 EntityCreatedEvent<T>
在哪里T。您可以创建以下侦听器定义以仅接收EntityCreatedEvent
以下内容 Person:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}
由于类型擦除,只有当被触发的事件解析了事件侦听器过滤的泛型参数时(这是类似的class PersonCreatedEvent extends EntityCreatedEvent<Person> { … })
,这才会起作用 。
在某些情况下,如果所有事件都遵循相同的结构(这应该是上述事件的情况),则这可能变得非常乏味。在这种情况下,您可以实现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()));
}
}
This works not only for ApplicationEvent but any arbitrary object that you’d send as an event.
七、方便地访问低水平资源
为了最佳使用和理解应用程序上下文,用户通常应该熟悉Spring的Resource抽象,如“ 资源 ”一章所述 。
应用程序上下文是一个ResourceLoader可以用来加载Resources 的应用程序上下文。A Resource本质上是JDK类的功能更丰富的版本java.net.URL,实际上,在适当Resource的java.net.URL地方包装一个实例。A Resource可以以透明的方式从几乎任何位置获取底层资源,包括类路径,文件系统位置,任何可用标准URL描述的地方以及其他一些变体。如果资源位置字符串是一个没有任何特殊前缀的简单路径,那么这些资源来自特定且适合于实际应用程序上下文类型。
您可以配置一个部署到应用程序上下文中的bean来实现特殊的回调接口,ResourceLoaderAware在初始化时自动调用回应用程序上下文本身作为 ResourceLoader。您还可以公开Resource用于访问静态资源的类型属性; 它们将像其他任何属性一样被注入到它中。您可以将这些Resource属性指定为简单的String路径,并依赖PropertyEditor由上下文自动注册的特殊JavaBean ,以便Resource在部署Bean时将这些文本字符串转换为实际对象。
提供给ApplicationContext构造函数的位置路径实际上是资源字符串,并且以简单形式适当地处理特定的上下文实现。ClassPathXmlApplicationContext将简单的位置路径视为类路径位置。您还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL中加载定义,而不管实际的上下文类型如何。
八、方便的Web应用程序的ApplicationContext实例化
您可以ApplicationContext通过使用例如a来声明性地创建实例 ContextLoader。当然,您也可以ApplicationContext使用其中一种ApplicationContext实现方式编程创建实例。
您可以ApplicationContext使用ContextLoaderListener如下注册一个:
<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>
监听者检查contextConfigLocation参数。如果该参数不存在,则侦听器将/WEB-INF/applicationContext.xml用作默认值。当参数确实存在时,侦听器使用预定义的分隔符(逗号,分号和空白)来分隔字符串,并将这些值用作应用程序上下文将被搜索的位置。也支持Ant风格的路径模式。例子是/WEB-INF/Context.xml名称以“Context.xml”结尾的所有文件,驻留在“WEB-INF”目录中,并且/WEB-INF/*/*Context.xml对于“WEB-INF”的任何子目录中的所有这些文件。
九、将Spring ApplicationContext部署为Java EE RAR文件
可以将Spring ApplicationContext部署为RAR文件,将上下文及其所有必需的bean类和库JAR封装到Java EE RAR部署单元中。这相当于引导了一个独立的ApplicationContext,它只是在Java EE环境中托管,能够访问Java EE服务器设施。RAR部署是部署无头WAR文件的场景中更自然的选择,实际上,WAR文件没有任何HTTP入口点,仅用于在Java EE环境中引导Spring ApplicationContext。
RAR部署非常适合不需要HTTP入口点但仅包含消息端点和预定作业的应用程序上下文。在这种情况下,Bean可以使用应用服务器资源,例如JTA事务管理器和JNDI绑定的JDBC DataSources和JMS ConnectionFactory实例,也可以通过Spring的标准事务管理和JNDI和JMX支持工具向平台的JMX服务器注册。应用程序组件还可以通过Spring的TaskExecutor抽象与应用程序服务器的JCA WorkManager进行交互。
要将Spring ApplicationContext简单部署为Java EE RAR文件:将所有应用程序类打包到RAR文件中,该文件是具有不同文件扩展名的标准JAR文件。将所有必需的库JAR添加到RAR归档的根目录中。添加一个“META-INF / ra.xml”部署描述符(如SpringContextResourceAdapters javadoc中所示)和相应的Spring XML bean定义文件(通常为“META-INF / applicationContext.xml”),并放弃生成的RAR文件到您的应用程序服务器的部署目录。
这种RAR部署单元通常是独立的; 它们不会将组件暴露给外部世界,甚至不会暴露给同一应用程序的其他模块。与基于RAR的ApplicationContext的交互通常通过它与其他模块共享的JMS目标发生。例如,基于RAR的ApplicationContext也可以调度一些作业,对文件系统中的新文件(或诸如此类)作出反应。如果需要允许从外部进行同步访问,则可以导出RMI端点,这当然可以由同一台机器上的其他应用程序模块使用。
好啦,ApplicationContext的附加功能,就是这么多东西,平时多看看,在用的时候,就能得心应手。