Additional Capabilities of the ApplicationContext

  • 事件发布,通过 ApplicationEventPublisher 接口发布到实现 ApplicationListener 接口的 bean。

  • 通过 HierarchicalBeanFactory 加载多个(分层的)上下文。 Spring 提供了 Resource 接口,它可以透明地从各种来源获取低级资源。ApplicationContext 作为 ResourceLoader,允许 bean 访问这些资源。 Spring 应用程序上下文管理应用程序生命周期,包括组件关系图和启动阶段。ApplicationStartup 用来监控这些应用程序启动步骤,收集从应用程序上下文生命周期阶段中收集的数据。默认情况下,ApplicationStartup 是非操作的,但 Spring 提供了 FlightRecorderApplicationStartup 实现,可以使用 Java Flight Recorder 追踪启动步骤。 可以在 Web 应用程序中声明性创建 ApplicationContext,方法是使用 ContextLoader 和 ContextLoaderListener。此外,Spring ApplicationsContext 可以部署为 Jakarta EE RAR 文件,在该文件中,上下文及其所有必需的 bean 类和库 JAR 都封装在一个 Jakarta EE RAR 部署单元中。

如同 "@21" 中所讨论的,"@14" 包提供用于管理和操作 Bean 的基本功能,包括以编程的方式。"@15" 包添加 "@16" 接口,它扩展了 "@17" 接口,此外还扩展了其他接口,以便以更面向应用程序框架的方式提供附加功能。许多人以完全声明的方式使用 "@18",甚至不以编程方式创建它,而是依赖于诸如 "@19" 之类的支持类,以便在 Jakarta EE Web 应用程序的正常启动过程中自动实例化 "@20"。

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 Jakarta EE web application.

为了以更面向框架的方式增强 BeanFactory 功能,contextpackage 还提供以下功能:

To enhance BeanFactory functionality in a more framework-oriented style, the context package also provides the following functionality:

  • Access to messages in i18n-style, through the MessageSource interface.

  • Access to resources, such as URLs and files, through the ResourceLoader interface.

  • Event publication, namely to beans that implement the ApplicationListener interface, through the use of the ApplicationEventPublisher interface.

  • 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.

Internationalization using MessageSource

ApplicationContext 接口扩展了一个名为 MessageSource 的接口,因此它提供了国际化(“i18n”)功能。Spring 还提供了 HierarchicalMessageSource 接口,该接口可以分层解析消息。这些接口共同构成了 Spring 进行消息解析的基础。这些接口上定义的方法包括:

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:

  • 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, 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(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.

当加载 ApplicationContext 时,它会自动在上下文中定义的 MessageSource bean 中搜索。bean 必须具有名称 messageSource。如果找到这样的 bean,则对前面方法的所有调用都将委派给消息源。如果没有找到消息源,则 ApplicationContext 尝试查找包含同名 bean 的父级。如果找到,则使用该 bean 作为 MessageSource。如果 ApplicationContext 无法找到任何消息源,则实例化一个空 DelegatingMessageSource,以便能够接受对上述方法的调用。

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.

Spring 提供了三种 MessageSource 实现,ResourceBundleMessageSourceReloadableResourceBundleMessageSourceStaticMessageSource。所有这些实现都实现了 HierarchicalMessageSource 以便进行嵌套消息传递。StaticMessageSource 用得较少,但提供了以编程方式将消息添加到源的方法。以下示例展示了 ResourceBundleMessageSource

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:

<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”的资源包。解决消息的任何请求均以通过 ResourceBundle 对象解决消息的 JDK 标准方式来处理。出于示例目的,假定以上两个资源包文件的内容如下:

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:

# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.

以下示例展示了一个运行 MessageSource 功能的程序。记住所有 ApplicationContext 实现也是 MessageSource 实现,因此可以强制转换为 MessageSource 接口。

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.

  • Java

  • Kotlin

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:

Alligators rock!

综上所述,MessageSource 在名为 beans.xml 的文件中定义,该文件存在于类路径的根目录中。messageSource bean 定义通过其 basenames 属性引用了多个资源包。 传递给 basenames 属性列表中的三个文件都作为文件存在于类路径的根目录中,分别称为 format.propertiesexceptions.propertieswindows.properties

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.

以下示例显示了传递给信息查找的参数。这些参数被转换成 String 对象并插入到查找信息中的占位符中。

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.

<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>
  • Java

  • Kotlin

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)
	}
}

执行“execute()”方法所产生的输出如下:

The resulting output from the invocation of the execute() method is as follows:

The userDao argument is required.

关于国际化(“i18n”),Spring 的各种 MessageSource 实现遵循与标准 JDK ResourceBundle 相同的语言环境解析和后备规则。简而言之,并继续使用前面定义的示例 messageSource,如果您想针对英国(en-GB)语言环境解析消息,则需要分别创建名为 format_en_GB.propertiesexceptions_en_GB.propertieswindows_en_GB.properties 的文件。

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.

通常情况下,本地语言解析由应用程序周围的环境来管理。在下列示例中,针对其解析(英国)消息的本地语言被手动指定:

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.

  • Java

  • Kotlin

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.

您还可以使用 MessageSourceAware 接口来获取对已经定义的任何 MessageSource 的引用。在 ApplicationContext 中定义的任何实现了 MessageSourceAware 接口的 bean 都将在 bean 被创建和配置时被注入应用上下文的 MessageSource

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.

由于 Spring 的 MessageSource 基于 Java 的 ResourceBundle,因此它不会合并具有相同基本名称的包,而只会使用找到的第一个包。之后用相同基本名称的消息包会被忽略。

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.

作为 ResourceBundleMessageSource 的替代品,Spring 提供 ReloadableResourceBundleMessageSource 类。此变体支持相同的 bundle 文件格式,但比基于标准 JDK 的 ResourceBundleMessageSource 实现更灵活。特别是,它允许从任何 Spring 资源位置(不仅是类路径)读取文件,并且支持热重新加载 bundle 属性文件(同时有效地缓存它们)。请参阅 ReloadableResourceBundleMessageSource javadoc 了解更多详情。

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.

Standard and Custom Events

ApplicationEvent”类和`ApplicationListener`接口在“ApplicationContext”中提供事件处理。如果实现`ApplicationListener`接口的bean被部署到上下文中,那么每次`ApplicationEvent`发布到“ApplicationContext”,这个bean都会收到通知。实际上,这是标准观察者设计模式。

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.

从 Spring 4.2 开始,事件基础设施得到了显著改进,并提供 annotation-based model 以及发布任何任意事件(即,不一定要从 ApplicationEvent 扩展出来的对象)的能力。当发布这样的对象时,我们将其包装在事件中。

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 提供的标准事件:

The following table describes the standard events that Spring provides:

Table 1. Built-in Events
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.

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. 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.

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.

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.

ServletRequestHandledEvent

A subclass of RequestHandledEvent that adds Servlet-specific context information.

您还可以创建并发布您自己的自定义事件。以下示例演示了一个扩展 Spring 的 ApplicationEvent 基类的简单类:

You can also create and publish your own custom events. The following example shows a simple class that extends Spring’s ApplicationEvent base class:

  • Java

  • Kotlin

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)

要发布一个自定义的 ApplicationEvent,请在 ApplicationEventPublisher 上调用 publishEvent() 方法。一般情况下,这可以通过创建一个实现 ApplicationEventPublisherAware 并将其注册为一个 Spring Bean 的类来实现。以下示例显示了这样一个类:

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:

  • Java

  • Kotlin

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...
	}
}

在配置时,Spring 容器检测到 EmailService 实施了 ApplicationEventPublisherAware,并自动调用 setApplicationEventPublisher()。事实上,传入的参数是 Spring 容器本身。您通过其 ApplicationEventPublisher 接口与应用程序上下文进行交互。

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.

若要接收自定义的 ApplicationEvent,你可以创建一个实现了 ApplicationListener 的类,并将它作为一个 Spring bean 进行注册。下例展示了这样的一个类:

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:

  • Java

  • Kotlin

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...
	}
}

请注意,"@22" 使用自定义事件的类型 (@23,在前一个示例中) 进行了泛型参数化。这意味着 "@24" 方法可以保持类型安全,避免任何向下转换的需要。你可以随意注册任意数量的事件侦听器,但需要注意的是,默认情况下,事件侦听器会同步接收事件。这意味着,"@25" 方法会阻塞,直到所有侦听器完成对事件的处理。这种同步单线程方法的一个优点是,当侦听器接收事件时,它会在发布者的事务上下文中操作,如果事务上下文可用的话。如果需要采用另一种事件发布策略,例如默认异步事件处理,请参阅 Spring 的 @26" 接口和 @27" 实现的 javadoc 文档,了解可应用于自定义 "applicationEventMulticaster" Bean 定义的配置选项。在这些情况下,不会为事件处理传播 ThreadLocal 和日志上下文。请参阅 "@29",了解有关可观察性问题的详细信息。

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. If another strategy for event publication becomes necessary, e.g. asynchronous event processing by default, see the javadoc for Spring’s ApplicationEventMulticaster interface and SimpleApplicationEventMulticaster implementation for configuration options which can be applied to a custom "applicationEventMulticaster" bean definition. In these cases, ThreadLocals and logging context are not propagated for the event processing. See the @EventListener Observability section for more information on Observability concerns.

下面的示例显示了用于注册和配置以上每个类的 Bean 定义:

The following example shows the bean definitions used to register and configure each of the classes above:

<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>

   <!-- optional: a custom ApplicationEventMulticaster definition -->
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
	<property name="taskExecutor" ref="..."/>
	<property name="errorHandler" ref="..."/>
</bean>

综上所述,当 bean emailServicesendEmail() 方法被调用时,若有应被拦截的邮件信息,则会发布 BlockedListEvent 类型的特定事件。 bean blockedListNotifier 注册为 ApplicationListener 并接收 BlockedListEvent,而后可以通知相关方。

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.

Spring 的事件机制旨在用于同一应用程序上下文中 Spring bean 之间的简单通信。但是,对于更复杂的企业集成需求,单独维护的 Spring Integration 项目完全支持构建基于众所周知的 Spring 编程模型的轻量级、 pattern-oriented 事件驱动的体系结构。

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.

Annotation-based Event Listeners

你可以使用 @EventListener 注释在受管 Bean 的任何方法上注册一个事件监听器。BlockedListNotifier 可以按如下方式重写:

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:

  • Java

  • Kotlin

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:

  • Java

  • Kotlin

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

还可以通过使用定义 SpEL expression 的注解的 condition 属性添加其他运行时筛选,它应当匹配才能实际上针对特定事件调用该方法。

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.

以下示例演示了如何重写通知器,以便仅当事件的“content”属性等于“my-event”时才调用通知器:

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:

  • Java

  • Kotlin

@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...
}

每个 SpEL 表达式都是针对专用上下文进行计算。下表列出了提供给上下文的项目,以便你可以使用它们进行条件事件处理:

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:

Table 2. Event SpEL available metadata
Name Location Description Example

Event

root object

The actual ApplicationEvent.

#root.event or event

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.

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).

#blEvent or #a0 (you can also use #p0 or #p<#arg> parameter notation as an alias)

请注意,#root.event 让你可以访问底层事件,即使你的方法签名实际上指的是已发布的任意对象也是如此。

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.

如果你需要发布事件作为处理另一个事件的结果,则可以将方法签名更改为返回应当发布的事件,如下例所示:

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:

  • Java

  • Kotlin

@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...
}

asynchronous listeners 不支持此功能。

This feature is not supported for asynchronous listeners.

handleBlockedListEvent() 方法为其处理的每个 BlockedListEvent 发布一个新的 ListUpdateEvent。如果你需要发布多个事件,则可以返回一个 Collection 或一系列事件。

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.

Asynchronous Listeners

如果您希望特定监听器异步处理事件,可以重复使用xref:integration/scheduling.adoc#scheduling-annotation-support-async[regular @Async。下面的示例演示如何执行此操作:

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:

  • Java

  • Kotlin

@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.

  • 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.

  • ThreadLocals and logging context are not propagated by default for the event processing. See the @EventListener Observability section for more information on Observability concerns.

Ordering Listeners

如果你需要在另一个前一个侦听器之前调用一个侦听器,则可以将 @Order 注释添加到方法声明中,如下例所示:

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:

  • Java

  • Kotlin

@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...
}

Generic Events

还可以使用泛型进一步定义事件的结构。考虑使用 EntityCreatedEvent<T>,其中 T 是已创建的实际实体的类型。例如,你可以创建以下侦听器定义,仅用于针对 Person 接收 EntityCreatedEvent:

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:

  • Java

  • Kotlin

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

由于类型擦除,此功能仅在触发事件解析侦听器过滤所基于的泛型参数时才能使用(即,类似于 class PersonCreatedEvent extends 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> { …​ }).

在某些情况下,如果所有事件都遵循相同的结构(如前一个示例中的事件所示),则这样做可能会相当繁琐。在这种情况下,你可以实现 ResolvableTypeProvider 以指导框架超越运行时环境提供的功能。以下事件演示了如何执行此操作:

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:

  • Java

  • Kotlin

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()))
	}
}

此功能不仅适用于 ApplicationEvent,还适用于您作为事件发送的任何任意对象。

This works not only for ApplicationEvent but any arbitrary object that you send as an event.

最后,与经典 ApplicationListener 实现类似,实际的多播通过运行时的上下文范围 ApplicationEventMulticaster 实现。默认情况下,这是一个 SimpleApplicationEventMulticaster,在调用者线程中同步发布事件。可以通过“applicationEventMulticaster”bean 定义进行替换/自定义,例如,为所有事件提供异步处理和/或处理侦听器异常:

Finally, as with classic ApplicationListener implementations, the actual multicasting happens via a context-wide ApplicationEventMulticaster at runtime. By default, this is a SimpleApplicationEventMulticaster with synchronous event publication in the caller thread. This can be replaced/customized through an "applicationEventMulticaster" bean definition, e.g. for processing all events asynchronously and/or for handling listener exceptions:

@Bean
ApplicationEventMulticaster applicationEventMulticaster() {
	SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
	multicaster.setTaskExecutor(...);
	multicaster.setErrorHandler(...);
	return multicaster;
}

Convenient Access to Low-level Resources

针对应用程序上下文的最佳使用和理解,你应该熟悉 Spring 的 "@30" 抽象,如 "@31" 中所述。

For optimal usage and understanding of application contexts, you should familiarize yourself with Spring’s Resource abstraction, as described in Resources.

应用程序上下文是一个 ResourceLoader,可用于加载 Resource 对象。Resource 本质上是功能更丰富的 JDK java.net.URL 类的版本。事实上,Resource 的实现封装了 java.net.URL 的一个实例,适当时使用。Resource 可以从几乎任何位置以透明的方式获取低级资源,包括从类路径、文件系统位置、可用标准 URL 描述的任何位置以及其他一些变体。如果资源位置字符串是一个没有特殊前缀的简单路径,则这些资源的来源是特定且适用于实际应用程序上下文类型的。

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. 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.

你可以配置部署到应用程序上下文的 bean 来实现特殊回调接口 ResourceLoaderAware,以便在初始化时使用作为 ResourceLoader 传递的应用程序上下文自动回调该 bean。你还可以公开类型为 Resource 的属性,用于访问静态资源。它们像其他属性一样注入到其中。可以在部署 bean 时将这些 Resource 属性指定为简单的 String 路径,并依赖于从这些文本字符串到实际 Resource 对象的自动转换。

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.

提供给 ApplicationContext 构造函数的位置路径实际是资源字符串,并且在简单形式中,会根据特定的上下文实现进行适当处理。例如,ClassPathXmlApplicationContext 会将一个简单的位置路径视为类路径位置。你还可以将位置路径(资源字符串)与特殊前缀一起使用,以强制从类路径或 URL 加载定义,而不用考虑实际的上下文类型。

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.

Application Startup Tracking

ApplicationContext 管理 Spring 应用程序的生命周期,并在组件周围提供了一个丰富的编程模型。因此,复杂的应用程序可能具有同样复杂的组件关系图和启动阶段。

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.

利用具体度量追踪应用程序启动步骤可以帮助了解在启动阶段花费时间的地方,但也可以用来更好地了解整个上下文生命周期。

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.

AbstractApplicationContext(及其子类)会用一个`ApplicationStartup`进行监控,它会收集`StartupStep`数据,这些数据来自各个启动阶段:

The AbstractApplicationContext (and its subclasses) is instrumented with an ApplicationStartup, which collects StartupStep data about various startup phases:

  • application context lifecycle (base packages scanning, config classes management)

  • beans lifecycle (instantiation, smart initialization, post processing)

  • application events processing

以下是`AnnotationConfigApplicationContext`中的一个监控示例:

Here is an example of instrumentation in the AnnotationConfigApplicationContext:

  • Java

  • Kotlin

// 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()

应用程序上下文已经使用多步骤检测。记录后,这些启动步骤可以使用特定工具收集、显示和分析。有关现有启动步骤的完整列表,您可以查看 dedicated appendix section

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.

默认的`ApplicationStartup`实现是一种无操作变体,开销最小。这意味着默认情况下不会在应用程序启动期间收集任何指标。Spring Framework 提供了一个用于使用 Java Flight Recorder 追踪启动步骤的实现:FlightRecorderApplicationStartup。若要使用此变体,必须将其实例配置到`ApplicationContext`,只要该实例已被创建。

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.

如果开发人员提供了他们自己的`AbstractApplicationContext`子类,或者想要收集更精确的数据,也可以使用`ApplicationStartup`基础设施。

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

ApplicationStartup 仅用于应用程序启动期间和核心容器中; 决不会替代 Java 分析器或像 Micrometer 这样的指标库。

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.

若要开始收集自定义`StartupStep`,组件可以从应用程序上下文中直接获取`ApplicationStartup`实例,使它们的组件实现`ApplicationStartupAware`,或者在任何注入点请求`ApplicationStartup`类型。

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.

开发人员在创建自定义启动步骤时不应使用 "spring.*" 命名空间。此命名空间专用于 Spring 内部使用且如有更改,恕不另行通知。

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.

Convenient ApplicationContext Instantiation for Web Applications

可以使用`ContextLoader`声明性创建`ApplicationContext`实例。当然,还可以使用其中一个`ApplicationContext`实现以编程方式创建`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.

可以使用`ContextLoaderListener`注册`ApplicationContext`,如下例所示:

You can register an ApplicationContext by using the ContextLoaderListener, as the following example shows:

<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`用作默认值。当参数存在时,侦听器会使用预定义的分隔符(逗号、分号和空格)来分隔`String`,并使用这些值作为搜索应用程序上下文的位置。还支持 Ant 样式的路径模式。示例包括`/WEB-INF/Context.xml`(针对所有名称以`Context.xml`结尾且驻留在`WEB-INF`目录中的文件)和`/WEB-INF/*/*Context.xml`(针对`WEB-INF`的任何子目录中的所有此类文件)。

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).

Deploying a Spring ApplicationContext as a Jakarta EE RAR File

可以将 Spring ApplicationContext`部署为 RAR 文件,封装上下文及其所有必需的 bean 类和库 JAR,这些文件都封装在一个 Jakarta EE RAR 部署单元中。这相当于引导独立的`ApplicationContext(仅托管在 Jakarta EE 环境中),该`ApplicationContext`能够访问 Jakarta EE 服务器设施。RAR 部署是无头 WAR 文件部署场景的更自然替代方案,事实上,它是一个没有任何 HTTP 入口点的 WAR 文件,仅用于在 Jakarta EE 环境中引导 Spring ApplicationContext

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 Jakarta EE RAR deployment unit. This is the equivalent of bootstrapping a stand-alone ApplicationContext (only hosted in Jakarta EE environment) being able to access the Jakarta EE servers facilities. 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 Jakarta EE environment.

RAR 部署非常适合不需要 HTTP 入口点,而仅由消息端点和计划作业组成的应用程序上下文。在此类上下文中的 bean 可以使用应用程序服务器资源,例如 JTA 事务管理器和 JNDI 绑定的 JDBC DataSource 实例和 JMS ConnectionFactory 实例,还可以通过 Spring 的标准事务管理和 JNDI 以及 JMX 支持设施向平台的 JMX 服务器进行注册。应用程序组件还可以通过 Spring 的`TaskExecutor`抽象与应用程序服务器的 JCA `WorkManager`进行交互。

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.

参阅https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jca/context/SpringContextResourceAdapter.html[SpringContextResourceAdapter]类的 javadoc 以了解 RAR 部署中涉及的配置详情。

See the javadoc of the SpringContextResourceAdapter class for the configuration details involved in RAR deployment.

若要简单部署 Spring ApplicationContext 作为 Jakarta EE RAR 文件:

For a simple deployment of a Spring ApplicationContext as a Jakarta EE RAR file:

  1. Package all application classes into a RAR file (which is a standard JAR file with a different file extension).

  2. Add all required library JARs into the root of the RAR archive.

  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).

  4. Drop the resulting RAR file into your application server’s deployment directory.

此类 RAR 部署单元通常是独立的。它们不会将组件暴露给外部,甚至不会暴露给同一应用程序的其他模块。基于 RAR 的 ApplicationContext 的交互通常通过它与其他模块共享的 JMS 目标来进行。基于 RAR 的 ApplicationContext 还可以安排一些作业或对文件系统(或类似文件系统)中的新文件做出反应。如果需要允许外部的同步访问,它可以(例如)导出 RMI 端点,这些端点可由同一机器上的其他应用程序模块使用。

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.