1.15.ApplicationContext 的附加功能

预计阅读时间: 44 分钟

1.15. ApplicationContext 的附加功能

As discussed in the chapter introduction, the org.springframework.beans.factory package provides basic functionality for managing and manipulating beans, including in a programmatic way. The org.springframework.context package adds the ApplicationContext interface, which extends the BeanFactory interface, in addition to extending other interfaces to provide additional functionality in a more application framework-oriented style. Many people use the ApplicationContext in a completely declarative fashion, not even creating it programmatically, but instead relying on support classes such as ContextLoader to automatically instantiate an ApplicationContext as part of the normal startup process of a Java EE web application. 如章节引言中所述, org.springframework.beans.factory 包提供了管理和操作豆的基本功能,包括以编程方式。 org.springframework.context 包添加了 ApplicationContext 接口,该接口扩展了 BeanFactory 接口,此外还扩展了其他接口,以更面向应用程序框架的风格提供额外的功能。许多人以完全声明性的方式使用 ApplicationContext ,甚至不通过编程创建它,而是依赖支持类如 ContextLoader ,在 Java EE 网络应用程序的正常启动过程中自动实例化 ApplicationContext

To enhance BeanFactory functionality in a more framework-oriented style, the context package also provides the following functionality: 为了以更面向框架的风格增强 BeanFactory 功能,上下文包还提供了以下功能:

  • Access to messages in i18n-style, through the MessageSource interface. 通过 MessageSource 接口以 i18n 风格访问消息。

  • Access to resources, such as URLs and files, through the ResourceLoader interface. 通过 ResourceLoader 接口访问资源,如 URL 和文件。

  • Event publication, namely to beans that implement the ApplicationListener interface, through the use of the ApplicationEventPublisher interface. 事件发布,即通过使用 ApplicationEventPublisher 接口对实现 ApplicationListener 接口的豆子进行发布。

  • Loading of multiple (hierarchical) contexts, letting each be focused on one particular layer, such as the web layer of an application, through the HierarchicalBeanFactory interface. 加载多个(分层)上下文,使每个上下文专注于一个特定层,例如应用程序的 Web 层,通过 HierarchicalBeanFactory 接口。

(#context-functionality-messagesource)1.15.1. Internationalization usingMessageSource

1.15.1. 使用 MessageSource 进行国际化

The ApplicationContext interface extends an interface called MessageSource and, therefore, provides internationalization (“i18n”) functionality. Spring also provides the HierarchicalMessageSource interface, which can resolve messages hierarchically. Together, these interfaces provide the foundation upon which Spring effects message resolution. The methods defined on these interfaces include: The ApplicationContext interface extends an interface called MessageSource and, therefore, provides internationalization (“i18n”) functionality. Spring also provides the HierarchicalMessageSource interface, which can resolve messages hierarchically. Together, these interfaces provide the foundation upon which Spring effects message resolution. The methods defined on these interfaces include: ApplicationContext 接口扩展了名为 MessageSource 的接口,因此提供了国际化(“i18n”)功能。Spring 还提供了 HierarchicalMessageSource 接口,可以分层解析消息。这些接口共同构成了 Spring 实现消息解析的基础。在这些接口上定义的方法包括:

  • String getMessage(String code, Object args, String default, Locale loc): The basic method used to retrieve a message from the MessageSource. When no message is found for the specified locale, the default message is used. Any arguments passed in become replacement values, using the MessageFormat functionality provided by the standard library. String getMessage(String code, Object args, String default, Locale loc) :从 MessageSource 中检索消息的基本方法。当未找到指定区域的消息时,使用默认消息。传入的任何参数都成为替换值,使用标准库提供的 MessageFormat 功能。

  • String getMessage(String code, Object args, Locale loc): Essentially the same as the previous method but with one difference: No default message can be specified. If the message cannot be found, a NoSuchMessageException is thrown. String getMessage(String code, Object args, Locale loc) :本质上与之前的方法相同,但有一个区别:不能指定默认消息。如果找不到消息,则抛出 NoSuchMessageException 异常。

  • String getMessage(MessageSourceResolvable resolvable, Locale locale): All properties used in the preceding methods are also wrapped in a class named MessageSourceResolvable, which you can use with this method. String getMessage(MessageSourceResolvable resolvable, Locale locale) : 在前面的方法中使用到的所有属性也都封装在一个名为 MessageSourceResolvable 的类中,你可以使用此方法来使用它们。

When an ApplicationContext is loaded, it automatically searches for a MessageSource bean defined in the context. The bean must have the name messageSource. If such a bean is found, all calls to the preceding methods are delegated to the message source. If no message source is found, the ApplicationContext attempts to find a parent containing a bean with the same name. If it does, it uses that bean as the MessageSource. If the ApplicationContext cannot find any source for messages, an empty DelegatingMessageSource is instantiated in order to be able to accept calls to the methods defined above. 当一个 ApplicationContext 被加载时,它会自动搜索上下文中定义的 MessageSource bean。该 bean 必须具有名称 messageSource 。如果找到这样的 bean,则将前述方法的调用委托给消息源。如果没有找到消息源, ApplicationContext 将尝试找到包含具有相同名称 bean 的父容器。如果找到了,它将使用该 bean 作为 MessageSource 。如果 ApplicationContext 找不到任何消息源,将实例化一个空的 DelegatingMessageSource ,以便能够接受对上述方法的调用。

Spring provides three MessageSource implementations, ResourceBundleMessageSource, ReloadableResourceBundleMessageSource and StaticMessageSource. All of them implement HierarchicalMessageSource in order to do nested messaging. The StaticMessageSource is rarely used but provides programmatic ways to add messages to the source. The following example shows ResourceBundleMessageSource: Spring 提供了三种 MessageSource 实现, ResourceBundleMessageSourceReloadableResourceBundleMessageSourceStaticMessageSource 。它们都实现了 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>

The example assumes that you have three resource bundles called format, exceptions and windows defined in your classpath. Any request to resolve a message is handled in the JDK-standard way of resolving messages through ResourceBundle objects. For the purposes of the example, assume the contents of two of the above resource bundle files are as follows: 示例假设你在类路径中定义了三个资源包,分别称为 formatexceptionswindows 。任何请求解析消息都通过 ResourceBundle 对象以 JDK 标准方式处理消息。为了示例的目的,假设上述两个资源包文件的内容如下:

in format.properties

message=Alligators rock!

in exceptions.properties

argument.required=The {0} argument is required.

The next example shows a program to run the MessageSource functionality. Remember that all ApplicationContext implementations are also MessageSource implementations and so can be cast to the MessageSource interface. 下一个示例展示了运行 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); }
fun main() { val resources = ClassPathXmlApplicationContext("beans.xml") val message = resources.getMessage("message", null, "Default", Locale.ENGLISH) println(message) }

The resulting output from the above program is as follows: 上述程序的结果输出如下:

To summarize, the MessageSource is defined in a file called beans.xml, which exists at the root of your classpath. The messageSource bean definition refers to a number of resource bundles through its basenames property. The three files that are passed in the list to the basenames property exist as files at the root of your classpath and are called format.properties, exceptions.properties, and windows.properties, respectively. 总结来说, MessageSource 定义在一个名为 beans.xml 的文件中,该文件位于你的类路径根目录下。 messageSource 的 bean 定义通过其 basenames 属性引用了多个资源包。传递给 basenames 属性的列表中的三个文件作为文件存在于你的类路径根目录,分别称为 format.propertiesexceptions.propertieswindows.properties

The next example shows arguments passed to the message lookup. These arguments are converted into String objects and inserted into placeholders in the lookup message. 下一个示例显示了传递给消息查找的参数。这些参数被转换为 String 对象,并插入到查找消息的占位符中。

<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); } }
class Example { lateinit var messages: MessageSource fun execute() { val message = messages.getMessage("argument.required", arrayOf("userDao"), "Required", Locale.ENGLISH) println(message) } }

The resulting output from the invocation of the execute() method is as follows: 调用 execute() 方法的结果输出如下:

The userDao argument is required.

With regard to internationalization (“i18n”), Spring’s various MessageSource implementations follow the same locale resolution and fallback rules as the standard JDK ResourceBundle. In short, and continuing with the example messageSource defined previously, if you want to resolve messages against the British (en-GB) locale, you would create files called format_en_GB.properties, exceptions_en_GB.properties, and windows_en_GB.properties, respectively. 关于国际化(“i18n”),Spring 的各种 MessageSource 实现遵循与标准 JDK ResourceBundle 相同的区域设置解析和回退规则。简而言之,继续使用之前定义的示例 messageSource ,如果你想针对英国( en-GB )区域解析消息,你将分别创建名为 format_en_GB.propertiesexceptions_en_GB.propertieswindows_en_GB.properties 的文件。

Typically, locale resolution is managed by the surrounding environment of the application. In the following example, the locale against which (British) messages are resolved is specified manually: 通常,区域设置解析由应用程序的周围环境管理。在以下示例中,针对(英国)消息解析的区域设置是手动指定的:

# 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); }
fun main() { val resources = ClassPathXmlApplicationContext("beans.xml") val message = resources.getMessage("argument.required", arrayOf("userDao"), "Required", Locale.UK) println(message) }

The resulting output from the running of the above program is as follows: 上述程序运行后的输出结果如下:

Ebagum lad, the 'userDao' argument is required, I say, required.

You can also use the MessageSourceAware interface to acquire a reference to any MessageSource that has been defined. Any bean that is defined in an ApplicationContext that implements the MessageSourceAware interface is injected with the application context’s MessageSource when the bean is created and configured. 你还可以使用 MessageSourceAware 接口获取对任何已定义的 MessageSource 的引用。在实现 MessageSourceAware 接口的 ApplicationContext 中定义的任何 bean 在创建和配置 bean 时都会注入应用程序上下文的 MessageSource

Because Spring’s MessageSource is based on Java’s ResourceBundle, it does not merge bundles with the same base name, but will only use the first bundle found. Subsequent message bundles with the same base name are ignored. 因为 Spring 的 MessageSource 基于 Java 的 ResourceBundle ,它不会合并具有相同基本名称的包,而只会使用找到的第一个包。具有相同基本名称的后续消息包将被忽略。

As an alternative to ResourceBundleMessageSource, Spring provides a ReloadableResourceBundleMessageSource class. This variant supports the same bundle file format but is more flexible than the standard JDK based ResourceBundleMessageSource implementation. In particular, it allows for reading files from any Spring resource location (not only from the classpath) and supports hot reloading of bundle property files (while efficiently caching them in between). See the ReloadableResourceBundleMessageSource javadoc for details. 作为 ResourceBundleMessageSource 的替代方案,Spring 提供了一个 ReloadableResourceBundleMessageSource 类。这个变体支持相同的包文件格式,但比基于标准 JDK 的 ResourceBundleMessageSource 实现更灵活。特别是,它允许从任何 Spring 资源位置读取文件(而不仅限于类路径),并支持热加载包属性文件(同时在之间高效缓存它们)。有关详细信息,请参阅 ReloadableResourceBundleMessageSource javadoc。

(#context-functionality-events)1.15.2. Standard and Custom Events

1.15.2. 标准和自定义事件

Event handling in the ApplicationContext is provided through the ApplicationEvent class and the ApplicationListener interface. If a bean that implements the ApplicationListener interface is deployed into the context, every time an ApplicationEvent gets published to the ApplicationContext, that bean is notified. Essentially, this is the standard Observer design pattern. 事件处理在 ApplicationContext 中通过 ApplicationEvent 类和 ApplicationListener 接口提供。如果实现了 ApplicationListener 接口的 bean 被部署到上下文中,每当 ApplicationEvent 发布到 ApplicationContext 时,该 bean 会收到通知。本质上,这是一个标准的观察者设计模式。

As of Spring 4.2, the event infrastructure has been significantly improved and offers an annotation-based model as well as the ability to publish any arbitrary event (that is, an object that does not necessarily extend from ApplicationEvent). When such an object is published, we wrap it in an event for you. 截至 Spring 4.2 版本,事件基础设施得到了显著改进,并提供了基于注解的模型以及发布任何任意事件的能力(即不必然扩展自 ApplicationEvent 的对象)。当发布此类对象时,我们会为你将其包装在事件中。

The following table describes the standard events that Spring provides: 以下表格描述了 Spring 提供的标准事件:

Table 7. Built-in Events 表 7. 内置事件

Event 活动

Explanation 说明

ContextRefreshedEvent

Published when the ApplicationContext is initialized or refreshed (for example, by using the refresh() method on the ConfigurableApplicationContext interface). Here, “initialized” means that all beans are loaded, post-processor beans are detected and activated, singletons are pre-instantiated, and the ApplicationContext object is ready for use. As long as the context has not been closed, a refresh can be triggered multiple times, provided that the chosen ApplicationContext actually supports such “hot” refreshes. For example, XmlWebApplicationContext supports hot refreshes, but GenericApplicationContext does not. 发布于初始化或刷新 ApplicationContext 时(例如,通过在 ConfigurableApplicationContext 接口上使用 refresh() 方法)。在这里,“初始化”意味着所有 bean 都已加载,后处理器 bean 被检测并激活,单例被预实例化, ApplicationContext 对象已准备好使用。只要上下文未关闭,就可以触发多次刷新,前提是所选的 ApplicationContext 实际上支持这种“热”刷新。例如, XmlWebApplicationContext 支持热刷新,但 GenericApplicationContext 不支持。

ContextStartedEvent

Published when the ApplicationContext is started by using the start() method on the ConfigurableApplicationContext interface. Here, “started” means that all Lifecycle beans receive an explicit start signal. 启动时使用 start() 方法在 ConfigurableApplicationContext 接口上启动 ApplicationContext 。在这里,“启动”意味着所有 Lifecycle 豆子都收到一个明确的启动信号。 Typically, this signal is used to restart beans after an explicit stop, but it may also be used to start components that have not been configured for autostart (for example, components that have not already started on initialization). 通常,此信号用于在显式停止后重启豆子,但它也可以用于启动尚未配置为自动启动的组件(例如,在初始化时尚未启动的组件)。

ContextStoppedEvent

Published when the ApplicationContext is stopped by using the stop() method on the ConfigurableApplicationContext interface. Here, “stopped” means that all Lifecycle beans receive an explicit stop signal. A stopped context may be restarted through a start() call. 发布时,通过在 ConfigurableApplicationContext 接口上使用 stop() 方法停止 ApplicationContext 。在这里,“停止”意味着所有 Lifecycle 颗豆都收到一个明确的停止信号。一个停止的上下文可以通过一个 start() 调用来重新启动。

ContextClosedEvent

Published when the ApplicationContext is being closed by using the close() method on the ConfigurableApplicationContext interface or via a JVM shutdown hook. Here, "closed" means that all singleton beans will be destroyed. Once the context is closed, it reaches its end of life and cannot be refreshed or restarted. 发布于使用 close() 方法在 ConfigurableApplicationContext 接口上关闭 ApplicationContext 时,或通过 JVM 关闭钩子。在这里,“关闭”意味着所有单例 bean 将被销毁。一旦上下文被关闭,它就达到了其生命周期结束,无法刷新或重启。

RequestHandledEvent

A web-specific event telling all beans that an HTTP request has been serviced. This event is published after the request is complete. This event is only applicable to web applications that use Spring’s DispatcherServlet. 一个特定于 Web 的事件,通知所有豆子一个 HTTP 请求已被处理。此事件在请求完成后发布。此事件仅适用于使用 Spring 的 DispatcherServlet 的 Web 应用程序。

ServletRequestHandledEvent

A subclass of RequestHandledEvent that adds Servlet-specific context information. 一个添加 Servlet 特定上下文信息的 RequestHandledEvent 子类。

You can also create and publish your own custom events. The following example shows a simple class that extends Spring’s ApplicationEvent base class: 你也可以创建和发布你自己的自定义事件。以下示例展示了一个扩展 Spring 的 ApplicationEvent 基类的简单类:

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... }
class BlockedListEvent(source: Any, val address: String, val content: String) : ApplicationEvent(source)

To publish a custom ApplicationEvent, call the publishEvent() method on an ApplicationEventPublisher. Typically, this is done by creating a class that implements ApplicationEventPublisherAware and registering it as a Spring bean. The following example shows such a class: 要发布一个自定义的 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... } }
class EmailService : ApplicationEventPublisherAware { private lateinit var blockedList: List<String> private lateinit var publisher: ApplicationEventPublisher fun setBlockedList(blockedList: List<String>) { this.blockedList = blockedList } override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) { this.publisher = publisher } fun sendEmail(address: String, content: String) { if (blockedList!!.contains(address)) { publisher!!.publishEvent(BlockedListEvent(this, address, content)) return } // send email... } }

At configuration time, the Spring container detects that EmailService implements ApplicationEventPublisherAware and automatically calls setApplicationEventPublisher(). In reality, the parameter passed in is the Spring container itself. You are interacting with the application context through its ApplicationEventPublisher interface. 在配置时,Spring 容器检测到 EmailService 实现 ApplicationEventPublisherAware 并自动调用 setApplicationEventPublisher() 。实际上,传入的参数是 Spring 容器本身。你通过其 ApplicationEventPublisher 接口与应用上下文进行交互。

To receive the custom ApplicationEvent, you can create a class that implements ApplicationListener and register it as a Spring bean. The following example shows such a class: 要接收自定义的 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... } }
class BlockedListNotifier : ApplicationListener<BlockedListEvent> { lateinit var notificationAddress: String override fun onApplicationEvent(event: BlockedListEvent) { // notify appropriate parties via notificationAddress... } }

Notice that ApplicationListener is generically parameterized with the type of your custom event (BlockedListEvent in the preceding example). This means that the onApplicationEvent() method can remain type-safe, avoiding any need for downcasting. You can register as many event listeners as you wish, but note that, by default, event listeners receive events synchronously. This means that the publishEvent() method blocks until all listeners have finished processing the event. One advantage of this synchronous and single-threaded approach is that, when a listener receives an event, it operates inside the transaction context of the publisher if a transaction context is available. 请注意, ApplicationListener 以你自定义事件的类型(前一个示例中的 BlockedListEvent )进行泛型参数化。这意味着 onApplicationEvent() 方法可以保持类型安全,避免任何需要向下转型的需求。你可以注册任意数量的事件监听器,但请注意,默认情况下,事件监听器会同步接收事件。这意味着 publishEvent() 方法会阻塞,直到所有监听器完成事件处理。这种同步和单线程方法的一个优点是,当监听器接收到事件时,如果存在事务上下文,它将在发布者的交易上下文中操作。 If another strategy for event publication becomes necessary, see the javadoc for Spring’s ApplicationEventMulticaster interface and SimpleApplicationEventMulticaster implementation for configuration options. 如果需要其他事件发布策略,请参阅 Spring 的 ApplicationEventMulticaster 接口和 SimpleApplicationEventMulticaster 实现的 javadoc 以获取配置选项。

The following example shows the bean definitions used to register and configure each of the classes above: 以下示例展示了用于注册和配置上述每个类的 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>

Putting it all together, when the sendEmail() method of the emailService bean is called, if there are any email messages that should be blocked, a custom event of type BlockedListEvent is published. The blockedListNotifier bean is registered as an ApplicationListener and receives the BlockedListEvent, at which point it can notify appropriate parties. 将所有内容整合在一起,当调用 emailServicesendEmail() 方法时,如果有任何应该被阻止的电子邮件消息,则会发布一个类型为 BlockedListEvent 的自定义事件。 blockedListNotifier 被注册为 ApplicationListener ,并接收 BlockedListEvent ,此时它可以通知相关方。

Spring’s eventing mechanism is designed for simple communication between Spring beans within the same application context. However, for more sophisticated enterprise integration needs, the separately maintained Spring Integration project provides complete support for building lightweight, pattern-oriented, event-driven architectures that build upon the well-known Spring programming model. Spring 的事件机制旨在实现同一应用上下文中 Spring beans 之间的简单通信。然而,对于更复杂的业务集成需求,独立维护的 Spring Integration 项目提供了构建基于知名 Spring 编程模型的轻量级、面向模式的、事件驱动的架构的完整支持。

(#context-functionality-events-annotation)Annotation-based Event Listeners

基于注解的事件监听器

You can register an event listener on any method of a managed bean by using the @EventListener annotation. The BlockedListNotifier can be rewritten as follows: 你可以使用 @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... } }
class BlockedListNotifier { lateinit var notificationAddress: String @EventListener fun processBlockedListEvent(event: BlockedListEvent) { // notify appropriate parties via notificationAddress... } }

The method signature once again declares the event type to which it listens, but, this time, with a flexible name and without implementing a specific listener interface. The event type can also be narrowed through generics as long as the actual event type resolves your generic parameter in its implementation hierarchy.

If your method should listen to several events or if you want to define it with no parameter at all, the event types can also be specified on the annotation itself. The following example shows how to do so: 如果你的函数需要监听多个事件或你希望完全不使用参数来定义它,事件类型也可以在注解本身中指定。以下示例展示了如何操作:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class}) public void handleContextStart() { // ... }
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class) fun handleContextStart() { // ... }

It is also possible to add additional runtime filtering by using the condition attribute of the annotation that defines a SpEL expression, which should match to actually invoke the method for a particular event. 也可以通过使用定义 SpEL 表达式的注解的 condition 属性来添加额外的运行时过滤,该属性应与实际调用特定事件的相应方法匹配。

The following example shows how our notifier can be rewritten to be invoked only if the content attribute of the event is equal to my-event: 以下示例展示了如何将我们的通知器重写为仅在事件的 content 属性等于 my-event 时调用:

@EventListener(condition = "#blEvent.content == 'my-event'") public void processBlockedListEvent(BlockedListEvent blEvent) { // notify appropriate parties via notificationAddress... }
@EventListener(condition = "#blEvent.content == 'my-event'") fun processBlockedListEvent(blEvent: BlockedListEvent) { // notify appropriate parties via notificationAddress... }

Each SpEL expression evaluates against a dedicated context. The following table lists the items made available to the context so that you can use them for conditional event processing: 每个 SpEL 表达式都会针对一个专用上下文进行评估。以下表格列出了上下文中可用的项目,以便你可以在条件事件处理中使用它们:

Table 8. Event SpEL available metadata 表 8. 事件 SpEL 可用元数据

Name 姓名

Location 位置

Description 描述

Example 示例

Event 活动

root object 根对象

The actual ApplicationEvent. 实际 ApplicationEvent

#root.event or event``#root.eventevent

Arguments array 参数数组

root object 根对象

The arguments (as an object array) used to invoke the method. 方法调用的参数(作为一个对象数组)。

#root.args or args; args[0] to access the first argument, etc. #root.argsargs ; args[0] 用于访问第一个参数等。

Argument name 参数名称

evaluation context 评估上下文

The name of any of the method arguments. If, for some reason, the names are not available (for example, because there is no debug information in the compiled byte code), individual arguments are also available using the #a<#arg> syntax where <#arg> stands for the argument index (starting from 0). 任何方法参数的名称。如果由于某种原因,名称不可用(例如,因为编译的字节码中没有调试信息),也可以使用 #a<#arg> 语法来访问单个参数,其中 <#arg> 代表参数索引(从 0 开始)。

#blEvent or #a0 (you can also use #p0 or #p<#arg> parameter notation as an alias) #blEvent#a0 (你也可以使用 #p0#p<#arg> 参数表示法作为别名)

Note that #root.event gives you access to the underlying event, even if your method signature actually refers to an arbitrary object that was published. 请注意, #root.event 允许你访问底层事件,即使你的函数签名实际上指的是已发布的任意对象。

If you need to publish an event as the result of processing another event, you can change the method signature to return the event that should be published, as the following example shows: 如果你需要将处理另一个事件的结果发布为事件,你可以更改方法签名以返回应发布的事件,如下例所示:

@EventListener public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) { // notify appropriate parties via notificationAddress and // then publish a ListUpdateEvent... }
@EventListener fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent { // notify appropriate parties via notificationAddress and // then publish a ListUpdateEvent... }

This feature is not supported for asynchronous listeners. 此功能不支持异步监听器。

The handleBlockedListEvent() method publishes a new ListUpdateEvent for every BlockedListEvent that it handles. If you need to publish several events, you can return a Collection or an array of events instead. 该方法为每个处理的 BlockedListEvent 发布一个新的 ListUpdateEvent 。如果你需要发布多个事件,可以返回一个 Collection 或事件数组。

(#context-functionality-events-async)Asynchronous Listeners 异步监听器

If you want a particular listener to process events asynchronously, you can reuse the regular @Async support. The following example shows how to do so: 如果你想使特定的监听器异步处理事件,你可以重用常规的 @Async 支持。以下示例展示了如何操作:

@EventListener @Async public void processBlockedListEvent(BlockedListEvent event) { // BlockedListEvent is processed in a separate thread }
@EventListener @Async fun processBlockedListEvent(event: BlockedListEvent) { // BlockedListEvent is processed in a separate thread }

Be aware of the following limitations when using asynchronous events: 请注意在使用异步事件时的以下限制:

  • If an asynchronous event listener throws an Exception, it is not propagated to the caller. See AsyncUncaughtExceptionHandler for more details. 如果异步事件监听器抛出 Exception ,则不会传播给调用者。有关更多详细信息,请参阅 AsyncUncaughtExceptionHandler

  • Asynchronous event listener methods cannot publish a subsequent event by returning a value. If you need to publish another event as the result of the processing, inject an ApplicationEventPublisher to publish the event manually. 异步事件监听器方法不能通过返回值发布后续事件。如果你需要处理结果后发布另一个事件,请注入 ApplicationEventPublisher 以手动发布事件。

(#context-functionality-events-order)Ordering Listeners 订单列表

If you need one listener to be invoked before another one, you can add the @Order annotation to the method declaration, as the following example shows: 如果你需要先调用一个监听器再调用另一个,你可以在方法声明中添加 @Order 注解,如下例所示:

@EventListener @Order(42) public void processBlockedListEvent(BlockedListEvent event) { // notify appropriate parties via notificationAddress... }
@EventListener @Order(42) fun processBlockedListEvent(event: BlockedListEvent) { // notify appropriate parties via notificationAddress... }

(#context-functionality-events-generics)Generic Events 通用事件

You can also use generics to further define the structure of your event. Consider using an EntityCreatedEvent<T> where T is the type of the actual entity that got created. For example, you can create the following listener definition to receive only EntityCreatedEvent for a Person: 你还可以使用泛型来进一步定义你的事件结构。考虑使用一个 EntityCreatedEvent<T> ,其中 T 是实际创建的实体的类型。例如,你可以创建以下监听器定义来仅接收 EntityCreatedEventPerson

@EventListener public void onPersonCreated(EntityCreatedEvent<Person> event) { // ... }
@EventListener fun onPersonCreated(event: EntityCreatedEvent<Person>) { // ... }

Due to type erasure, this works only if the event that is fired resolves the generic parameters on which the event listener filters (that is, something like class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ }). 由于类型擦除,这仅在触发的事件解决了事件监听器过滤的泛型参数的情况下才有效(即类似于 class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ } )。

In certain circumstances, this may become quite tedious if all events follow the same structure (as should be the case for the event in the preceding example). In such a case, you can implement ResolvableTypeProvider to guide the framework beyond what the runtime environment provides. The following event shows how to do so: 在某些情况下,如果所有事件都遵循相同的结构(如前一个示例中的事件应然),这可能会变得相当繁琐。在这种情况下,你可以实施 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())); } }
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider { override fun getResolvableType(): ResolvableType? { return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource())) } }

This works not only for ApplicationEvent but any arbitrary object that you send as an event. 这不仅适用于 ApplicationEvent ,也适用于你发送的任何任意对象作为事件。

(#context-functionality-resources)1.15.3. Convenient Access to Low-level Resources

1.15.3. 方便访问底层资源

For optimal usage and understanding of application contexts, you should familiarize yourself with Spring’s Resource abstraction, as described in Resources. 为了最佳使用和理解应用场景,你应该熟悉 Spring 的 Resource 抽象,如资源中所述。

An application context is a ResourceLoader, which can be used to load Resource objects. A Resource is essentially a more feature rich version of the JDK java.net.URL class. In fact, the implementations of the Resource wrap an instance of java.net.URL, where appropriate. A Resource can obtain low-level resources from almost any location in a transparent fashion, including from the classpath, a filesystem location, anywhere describable with a standard URL, and some other variations. 应用程序上下文是一个 ResourceLoader ,可以用来加载 Resource 对象。 Resource 本质上是一个功能更丰富的 JDK java.net.URL 类的版本。实际上, Resource 的实现会包装一个 java.net.URL 的实例,在适当的情况下。 Resource 可以从几乎任何位置以透明的方式获取低级资源,包括从类路径、文件系统位置、任何可以用标准 URL 描述的位置,以及一些其他变体。 If the resource location string is a simple path without any special prefixes, where those resources come from is specific and appropriate to the actual application context type. 如果资源位置字符串是一个没有特殊前缀的简单路径,那么这些资源来自的地方是具体且适合实际应用上下文类型的。

You can configure a bean deployed into the application context to implement the special callback interface, ResourceLoaderAware, to be automatically called back at initialization time with the application context itself passed in as the ResourceLoader. You can also expose properties of type Resource, to be used to access static resources. They are injected into it like any other properties. You can specify those Resource properties as simple String paths and rely on automatic conversion from those text strings to actual Resource objects when the bean is deployed. 你可以将部署到应用程序上下文中的 bean 配置为实现特殊的回调接口 ResourceLoaderAware ,以便在初始化时自动调用,并将应用程序上下文本身作为 ResourceLoader 传入。你还可以公开类型为 Resource 的属性,用于访问静态资源。它们像任何其他属性一样被注入。你可以将这些 Resource 属性指定为简单的 String 路径,并在 bean 部署时自动将这些文本字符串转换为实际的 Resource 对象。

The location path or paths supplied to an ApplicationContext constructor are actually resource strings and, in simple form, are treated appropriately according to the specific context implementation. For example ClassPathXmlApplicationContext treats a simple location path as a classpath location. You can also use location paths (resource strings) with special prefixes to force loading of definitions from the classpath or a URL, regardless of the actual context type. 提供的给 ApplicationContext 构造函数的位置路径实际上是资源字符串,在简单形式下,它们会根据特定的上下文实现适当地处理。例如, ClassPathXmlApplicationContext 将简单的位置路径视为类路径位置。你还可以使用具有特殊前缀的位置路径(资源字符串)来强制从类路径或 URL 加载定义,而不管实际的上下文类型如何。

(#context-functionality-startup)1.15.4. Application Startup Tracking

1.15.4. 应用启动跟踪

The ApplicationContext manages the lifecycle of Spring applications and provides a rich programming model around components. As a result, complex applications can have equally complex component graphs and startup phases. ApplicationContext 管理 Spring 应用的整个生命周期,并为组件提供丰富的编程模型。因此,复杂的应用可以拥有同样复杂的组件图和启动阶段。

Tracking the application startup steps with specific metrics can help understand where time is being spent during the startup phase, but it can also be used as a way to better understand the context lifecycle as a whole. 跟踪应用程序启动步骤的具体指标可以帮助了解启动阶段时间花费在哪里,但也可以用作更好地理解整个上下文生命周期的手段。

The AbstractApplicationContext (and its subclasses) is instrumented with an ApplicationStartup, which collects StartupStep data about various startup phases: The AbstractApplicationContext (及其子类)被一个 ApplicationStartup 仪器化,该仪器收集关于各种启动阶段的数据 StartupStep

  • application context lifecycle (base packages scanning, config classes management) 应用程序上下文生命周期(基础包扫描、配置类管理)

  • beans lifecycle (instantiation, smart initialization, post processing) 豆类生命周期(实例化、智能初始化、后处理)

  • application events processing 应用程序事件处理

Here is an example of instrumentation in the AnnotationConfigApplicationContext: 这里是一个在 AnnotationConfigApplicationContext 中的仪表化的例子

// create a startup step and start recording StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan"); // add tagging information to the current step scanPackages.tag("packages", () -> Arrays.toString(basePackages)); // perform the actual phase we're instrumenting this.scanner.scan(basePackages); // end the current step scanPackages.end();
// create a startup step and start recording val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan") // add tagging information to the current step scanPackages.tag("packages", () -> Arrays.toString(basePackages)) // perform the actual phase we're instrumenting this.scanner.scan(basePackages) // end the current step scanPackages.end()

The application context is already instrumented with multiple steps. Once recorded, these startup steps can be collected, displayed and analyzed with specific tools. For a complete list of existing startup steps, you can check out the dedicated appendix section. 应用程序上下文已经通过多个步骤进行了配置。一旦记录,这些启动步骤就可以使用特定工具进行收集、显示和分析。要查看现有启动步骤的完整列表,你可以查看专门的附录部分。

The default ApplicationStartup implementation is a no-op variant, for minimal overhead. This means no metrics will be collected during application startup by default. Spring Framework ships with an implementation for tracking startup steps with Java Flight Recorder: FlightRecorderApplicationStartup. To use this variant, you must configure an instance of it to the ApplicationContext as soon as it’s been created. 默认的 ApplicationStartup 实现是一个无操作变体,以最小化开销。这意味着默认情况下,在应用程序启动期间不会收集任何指标。Spring 框架附带了一个使用 Java Flight Recorder 跟踪启动步骤的实现: FlightRecorderApplicationStartup 。要使用此变体,你必须将其配置实例设置为 ApplicationContext ,一旦它被创建。

Developers can also use the ApplicationStartup infrastructure if they’re providing their own AbstractApplicationContext subclass, or if they wish to collect more precise data. use the ApplicationStartup infrastructure if they are providing their own AbstractApplicationContext subclass, or if they wish to collect more precise data.

ApplicationStartup is meant to be only used during application startup and for the core container; this is by no means a replacement for Java profilers or metrics libraries like Micrometer. ApplicationStartup 仅应在应用程序启动期间以及核心容器中使用;这绝对不是 Java 分析器或 Micrometer 等度量库的替代品。

To start collecting custom StartupStep, components can either get the ApplicationStartup instance from the application context directly, make their component implement ApplicationStartupAware, or ask for the ApplicationStartup type on any injection point. 要开始收集自定义 StartupStep ,组件可以直接从应用程序上下文中获取 ApplicationStartup 实例,让它们的组件实现 ApplicationStartupAware ,或者在任何注入点上请求 ApplicationStartup 类型。

Developers should not use the "spring.*" namespace when creating custom startup steps. This namespace is reserved for internal Spring usage and is subject to change. 开发者不应在创建自定义启动步骤时使用 "spring.*" 命名空间。此命名空间保留供 Spring 内部使用,并可能发生变化。

1.15.5. Web 应用程序的便捷 ApplicationContext 实例化

You can create ApplicationContext instances declaratively by using, for example, a ContextLoader. Of course, you can also create ApplicationContext instances programmatically by using one of the ApplicationContext implementations. 你可以通过使用,例如,一个 ContextLoader 来声明性地创建 ApplicationContext 实例。当然,你也可以通过使用 ApplicationContext 实现之一来程序化地创建 ApplicationContext 实例。

You can register an ApplicationContext by using the ContextLoaderListener, as the following example shows: 你可以使用 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>

The listener inspects the contextConfigLocation parameter. If the parameter does not exist, the listener uses /WEB-INF/applicationContext.xml as a default. When the parameter does exist, the listener separates the String by using predefined delimiters (comma, semicolon, and whitespace) and uses the values as locations where application contexts are searched. Ant-style path patterns are supported as well. Examples are /WEB-INF/*Context.xml (for all files with names that end with Context.xml and that reside in the WEB-INF directory) and /WEB-INF/**/*Context.xml (for all such files in any subdirectory of WEB-INF). 监听器检查 contextConfigLocation 参数。如果参数不存在,监听器使用 /WEB-INF/applicationContext.xml 作为默认值。当参数存在时,监听器使用预定义的分隔符(逗号、分号和空格)来分隔 String ,并将这些值用作搜索应用程序上下文的位置。也支持 Ant 风格的路径模式。例如, /WEB-INF/*Context.xml (对于所有以 Context.xml 结尾且位于 WEB-INF 目录中的文件)和 /WEB-INF/**/*Context.xml (对于任何子目录中的此类文件)。

(#context-deploy-rar)1.15.6. Deploying a SpringApplicationContext as a Java EE RAR File

1.15.6. 将 Spring ApplicationContext 作为 Java EE RAR 文件部署

It is possible to deploy a Spring ApplicationContext as a RAR file, encapsulating the context and all of its required bean classes and library JARs in a Java EE RAR deployment unit. This is the equivalent of bootstrapping a stand-alone ApplicationContext (only hosted in Java EE environment) being able to access the Java EE servers facilities. 可以将 Spring ApplicationContext 作为 RAR 文件部署,将上下文及其所有所需的 bean 类和库 JAR 封装在 Java EE RAR 部署单元中。这相当于启动一个独立的 ApplicationContext (仅托管在 Java EE 环境中),使其能够访问 Java EE 服务器功能。 RAR deployment is a more natural alternative to a scenario of deploying a headless WAR file — in effect, a WAR file without any HTTP entry points that is used only for bootstrapping a Spring ApplicationContext in a Java EE environment. RAR 部署是部署无头 WAR 文件场景的一个更自然的替代方案——实际上,这是一种没有 HTTP 入口点的 WAR 文件,仅用于在 Java EE 环境中启动 Spring ApplicationContext

RAR deployment is ideal for application contexts that do not need HTTP entry points but rather consist only of message endpoints and scheduled jobs. Beans in such a context can use application server resources such as the JTA transaction manager and JNDI-bound JDBC DataSource instances and JMS ConnectionFactory instances and can also register with the platform’s JMX server — all through Spring’s standard transaction management and JNDI and JMX support facilities. Application components can also interact with the application server’s JCA WorkManager through Spring’s TaskExecutor abstraction. RAR 部署非常适合不需要 HTTP 入口点,而仅由消息端点和计划任务组成的应用场景。在这种场景下,Beans 可以使用应用服务器资源,如 JTA 事务管理器和 JNDI 绑定的 JDBC DataSource 实例以及 JMS ConnectionFactory 实例,还可以注册到平台的 JMX 服务器——所有这些都可以通过 Spring 的标准事务管理和 JNDI 以及 JMX 支持设施实现。应用组件还可以通过 Spring 的 TaskExecutor 抽象与应用服务器的 JCA WorkManager 进行交互。

See the javadoc of the SpringContextResourceAdapter class for the configuration details involved in RAR deployment. 查看 SpringContextResourceAdapter 类的 javadoc 以了解 RAR 部署涉及的配置细节。

For a simple deployment of a Spring ApplicationContext as a Java EE RAR file: 为了简单部署 Spring ApplicationContext 作为 Java EE RAR 文件:

  1. Package all application classes into a RAR file (which is a standard JAR file with a different file extension). 将所有应用程序类打包成一个 RAR 文件(这是一个具有不同文件扩展名的标准 JAR 文件)。

  2. Add all required library JARs into the root of the RAR archive. 将所有必需的库 JAR 文件添加到 RAR 存档的根目录中。

  3. Add a META-INF/ra.xml deployment descriptor (as shown in the javadoc for SpringContextResourceAdapter) and the corresponding Spring XML bean definition file(s) (typically META-INF/applicationContext.xml). 添加一个 META-INF/ra.xml 部署描述符(如 SpringContextResourceAdapter 的 javadoc 所示)以及相应的 Spring XML bean 定义文件(通常是 META-INF/applicationContext.xml )。

  4. Drop the resulting RAR file into your application server’s deployment directory. 将生成的 RAR 文件放入你的应用程序服务器的部署目录中。

Such RAR deployment units are usually self-contained. They do not expose components to the outside world, not even to other modules of the same application. Interaction with a RAR-based ApplicationContext usually occurs through JMS destinations that it shares with other modules. A RAR-based ApplicationContext may also, for example, schedule some jobs or react to new files in the file system (or the like). If it needs to allow synchronous access from the outside, it could (for example) export RMI endpoints, which may be used by other application modules on the same machine. 此类 RAR 部署单元通常是自包含的。它们不会将组件暴露给外部世界,甚至不会暴露给同一应用程序的其他模块。与基于 RAR 的 ApplicationContext 的交互通常是通过它与其他模块共享的 JMS 目的地进行的。基于 RAR 的 ApplicationContext 也可能,例如,安排一些作业或对文件系统中的新文件(或类似内容)做出反应。如果它需要允许外部同步访问,它可以通过(例如)导出 RMI 端点来实现,这些端点可能被同一台机器上的其他应用程序模块使用。