对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

ApplicationContext 的附加功能

正如在章节引言中所讨论的,org.springframework.beans.factory 包提供了用于以编程方式管理和操作 Bean 的基本功能。org.springframework.context包增加了 ApplicationContext 接口,该接口扩展了BeanFactory接口,同时还扩展了其他接口,以便以更面向应用框架的风格提供额外功能。许多人完全以声明式方式使用ApplicationContext,甚至不以编程方式创建它,而是依赖诸如ContextLoader之类的支持类,在 Jakarta EE Web 应用程序的正常启动过程中自动实例化一个ApplicationContextspring-doc.cadn.net.cn

为了以更加面向框架的风格增强 BeanFactory 的功能,context 包还提供了以下功能:spring-doc.cadn.net.cn

  • 通过 MessageSource 接口以国际化(i18n)风格访问消息。spring-doc.cadn.net.cn

  • 通过 ResourceLoader 接口访问资源,例如 URL 和文件。spring-doc.cadn.net.cn

  • 事件发布,即通过 ApplicationListener 接口向实现 ApplicationEventPublisher 接口的 Bean 发布事件。spring-doc.cadn.net.cn

  • 通过 HierarchicalBeanFactory 接口加载多个(分层的)上下文,使每个上下文专注于某一层,例如应用程序的 Web 层。spring-doc.cadn.net.cn

使用进行国际化MessageSource

ApplicationContext 接口扩展了一个名为 MessageSource 的接口,因此提供了国际化(“i18n”)功能。Spring 还提供了 HierarchicalMessageSource 接口,该接口可以分层解析消息。这些接口共同构成了 Spring 实现消息解析的基础。这些接口中定义的方法包括:spring-doc.cadn.net.cn

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource中检索消息的基本方法。当在指定的区域设置(locale)下未找到对应消息时,将使用默认消息。传入的任何参数都将作为替换值,利用标准库提供的MessageFormat功能进行处理。spring-doc.cadn.net.cn

  • String getMessage(String code, Object[] args, Locale loc):本质上与前一个方法相同,但有一个区别:无法指定默认消息。如果找不到该消息,则会抛出 NoSuchMessageException 异常。spring-doc.cadn.net.cn

  • String getMessage(MessageSourceResolvable resolvable, Locale locale):前面方法中使用的所有属性也都被封装在一个名为 MessageSourceResolvable 的类中,你可以将该类与本方法一起使用。spring-doc.cadn.net.cn

当加载 ApplicationContext 时,它会自动在上下文中搜索一个名为 MessageSourcemessageSource bean。如果找到这样的 bean,所有对上述方法的调用都会被委托给该消息源。如果没有找到消息源,ApplicationContext 会尝试在其父上下文中查找是否存在同名的 bean。如果存在,则使用该 bean 作为 MessageSource。如果 ApplicationContext 无法找到任何消息源,则会实例化一个空的 DelegatingMessageSource,以便能够接受对上述方法的调用。spring-doc.cadn.net.cn

Spring 提供了三种 MessageSource 实现:ResourceBundleMessageSourceReloadableResourceBundleMessageSourceStaticMessageSource。它们都实现了 HierarchicalMessageSource 接口,以支持嵌套消息功能。StaticMessageSource 很少使用,但它提供了以编程方式向消息源添加消息的途径。以下示例展示了 ResourceBundleMessageSourcespring-doc.cadn.net.cn

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

该示例假定您在类路径中定义了三个名为 formatexceptionswindows 的资源包。 任何解析消息的请求都将通过 JDK 标准方式,使用 ResourceBundle 对象来解析消息。 为便于说明,假设上述资源包文件中的两个文件内容如下:spring-doc.cadn.net.cn

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

下一个示例展示了一个用于运行 MessageSource 功能的程序。 请记住,所有 ApplicationContext 实现同时也是 MessageSource 实现,因此可以强制转换为 MessageSource 接口。spring-doc.cadn.net.cn

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

上述程序的输出结果如下:spring-doc.cadn.net.cn

Alligators rock!

简而言之,MessageSource 定义在一个名为 beans.xml 的文件中,该文件位于类路径的根目录下。messageSource 的 bean 定义通过其 basenames 属性引用了多个资源包。传递给 basenames 属性的列表中包含三个文件,它们分别位于类路径的根目录下,名称依次为 format.propertiesexceptions.propertieswindows.propertiesspring-doc.cadn.net.cn

下一个示例展示了传递给消息查找的参数。这些参数会被转换为String对象,并插入到查找消息中的占位符位置。spring-doc.cadn.net.cn

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

调用 execute() 方法后产生的输出如下所示:spring-doc.cadn.net.cn

The userDao argument is required.

关于国际化(“i18n”),Spring 的各种 MessageSource 实现遵循与标准 JDK ResourceBundle 相同的区域设置解析和回退规则。简而言之,继续沿用前面定义的 messageSource 示例,如果你想针对英国(en-GB)区域设置解析消息,则应分别创建名为 format_en_GB.propertiesexceptions_en_GB.propertieswindows_en_GB.properties 的文件。spring-doc.cadn.net.cn

通常,区域设置(locale)的解析由应用程序所处的环境进行管理。在以下示例中,用于解析(英式)消息的区域设置是手动指定的:spring-doc.cadn.net.cn

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

运行上述程序后产生的输出结果如下:spring-doc.cadn.net.cn

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

你也可以使用 MessageSourceAware 接口来获取对任意已定义的 MessageSource 的引用。在 ApplicationContext 中定义的任何实现了 MessageSourceAware 接口的 bean,在创建和配置时都会被注入该应用上下文的 MessageSourcespring-doc.cadn.net.cn

由于 Spring 的 MessageSource 基于 Java 的 ResourceBundle,它不会合并具有相同基名称的资源包,而只会使用找到的第一个资源包。 具有相同基名称的后续消息资源包将被忽略。
作为 ResourceBundleMessageSource 的替代方案,Spring 提供了一个 ReloadableResourceBundleMessageSource 类。该变体支持相同的捆绑包 文件格式,但比基于标准 JDK 的 ResourceBundleMessageSource 实现更加灵活。特别是,它允许从任何 Spring 资源位置(不仅限于类路径)读取文件,并支持捆绑包属性文件的热重载(同时在两次加载之间高效缓存它们)。 有关详细信息,请参阅 ReloadableResourceBundleMessageSource Javadoc。

标准事件和自定义事件

ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果一个实现了 ApplicationListener 接口的 Bean 被部署到上下文中,那么每当有 ApplicationEvent 发布到该 ApplicationContext 时,该 Bean 就会收到通知。本质上,这就是标准的观察者(Observer)设计模式。spring-doc.cadn.net.cn

从 Spring 4.2 开始,事件基础设施得到了显著改进,提供了基于注解的模型, 以及发布任意事件(即不一定继承自ApplicationEvent的对象)的能力。 当发布此类对象时,我们会自动将其包装为一个事件。

下表描述了 Spring 提供的标准事件:spring-doc.cadn.net.cn

表1. 内置事件
事件 说明

ContextRefreshedEventspring-doc.cadn.net.cn

ApplicationContext被初始化或刷新时发布(例如,通过refresh()接口上的ConfigurableApplicationContext方法)。 此处的“初始化”是指所有 Bean 均已加载,后处理器 Bean 已被检测并激活,单例 Bean 已预先实例化,并且ApplicationContext对象已准备就绪可供使用。只要上下文尚未关闭,只要所选的ApplicationContext实际支持此类“热”刷新,就可以多次触发刷新操作。例如,XmlWebApplicationContext支持热刷新,而GenericApplicationContext则不支持。spring-doc.cadn.net.cn

ContextStartedEventspring-doc.cadn.net.cn

当通过 ApplicationContext 接口的 start() 方法启动 ConfigurableApplicationContext 时发布此事件。这里的“已启动”表示所有 Lifecycle Bean 都收到了显式的启动信号。通常,该信号用于在显式停止后重新启动 Bean,但也可用于启动那些未配置为自动启动的组件(例如,在初始化阶段尚未启动的组件)。spring-doc.cadn.net.cn

ContextStoppedEventspring-doc.cadn.net.cn

当通过 ApplicationContext 接口上的 stop() 方法停止 ConfigurableApplicationContext 时发布此事件。此处的“已停止”表示所有 Lifecycle Bean 都收到了显式的停止信号。已停止的上下文可通过调用 start() 方法重新启动。spring-doc.cadn.net.cn

ContextClosedEventspring-doc.cadn.net.cn

当通过 ApplicationContext 接口的 close() 方法或通过 JVM 关闭钩子关闭 ConfigurableApplicationContext 时发布此事件。 此处的“关闭”表示所有单例 bean 都将被销毁。一旦上下文被关闭, 它就到达了生命周期的终点,无法再被刷新或重新启动。spring-doc.cadn.net.cn

RequestHandledEventspring-doc.cadn.net.cn

一个特定于 Web 的事件,用于通知所有 Bean 一个 HTTP 请求已被处理完毕。该事件在请求完成后发布。此事件仅适用于使用 Spring 的 DispatcherServlet 的 Web 应用程序。spring-doc.cadn.net.cn

ServletRequestHandledEventspring-doc.cadn.net.cn

RequestHandledEvent 的一个子类,增加了 Servlet 特定的上下文信息。spring-doc.cadn.net.cn

你也可以创建并发布自己的自定义事件。以下示例展示了一个简单的类,该类继承了 Spring 的 ApplicationEvent 基类:spring-doc.cadn.net.cn

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,请在 publishEvent() 上调用 ApplicationEventPublisher 方法。通常,这是通过创建一个实现 ApplicationEventPublisherAware 接口的类,并将其注册为 Spring Bean 来完成的。以下示例展示了这样一个类:spring-doc.cadn.net.cn

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 接口与应用上下文进行交互。spring-doc.cadn.net.cn

要接收自定义的 ApplicationEvent,您可以创建一个实现 ApplicationListener 接口的类,并将其注册为 Spring Bean。以下示例展示了这样一个类:spring-doc.cadn.net.cn

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

请注意,ApplicationListener 使用您的自定义事件类型(在前面的示例中为 BlockedListEvent)进行泛型参数化。这意味着 onApplicationEvent() 方法可以保持类型安全,避免任何向下转型的需要。您可以注册任意数量的事件监听器,但请注意,默认情况下,事件监听器以同步方式接收事件。这意味着 publishEvent() 方法会阻塞,直到所有监听器完成事件处理。这种同步且单线程方法的一个优点是,当监听器接收到事件时,如果存在事务上下文,它将在发布者的事务上下文中执行。如果需要采用另一种事件发布策略,例如。g.默认情况下进行异步事件处理,请参阅 Spring 的 ApplicationEventMulticaster 接口 和 SimpleApplicationEventMulticaster 实现的 Javadoc,以获取可应用于自定义 "applicationEventMulticaster" Bean 定义的配置选项。spring-doc.cadn.net.cn

以下示例展示了用于注册和配置上述每个类的 bean 定义:spring-doc.cadn.net.cn

<bean id="emailService" class="example.EmailService">
	<property name="blockedList">
		<list>
			<value>[email protected]</value>
			<value>[email protected]</value>
			<value>[email protected]</value>
		</list>
	</property>
</bean>

<bean id="blockedListNotifier" class="example.BlockedListNotifier">
	<property name="notificationAddress" value="[email protected]"/>
</bean>

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

综合起来,当调用 sendEmail() bean 的 emailService 方法时,如果存在任何应被阻止的电子邮件消息,就会发布一个类型为 BlockedListEvent 的自定义事件。blockedListNotifier bean 被注册为一个 ApplicationListener,并接收该 BlockedListEvent 事件,此时它可以通知相关方。spring-doc.cadn.net.cn

Spring 的事件机制旨在实现同一应用上下文内 Spring Bean 之间的简单通信。然而,对于更复杂的企业集成需求,独立维护的 Spring Integration 项目提供了完整支持,可用于构建轻量级、面向模式的事件驱动架构,该架构基于广为人知的 Spring 编程模型。

基于注解的事件监听器

你可以通过使用 @EventListener 注解,在任何托管 Bean 的方法上注册一个事件监听器。BlockedListNotifier 可以重写如下:spring-doc.cadn.net.cn

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

该方法签名再次声明了它所监听的事件类型, 但这次使用了灵活的方法名,且无需实现特定的监听器接口。 只要实际的事件类型在其实现层次结构中能够解析泛型参数, 就可以通过泛型进一步限定事件类型。spring-doc.cadn.net.cn

如果你的方法需要监听多个事件,或者你希望完全不使用参数来定义该方法,也可以直接在注解本身上指定事件类型。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

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

还可以通过使用定义 SpEL 表达式 的注解中的 condition 属性来添加额外的运行时过滤,该表达式需要匹配成功才会实际调用特定事件的方法。spring-doc.cadn.net.cn

以下示例展示了如何重写我们的通知器,使其仅在事件的 content 属性等于 my-event 时才被调用:spring-doc.cadn.net.cn

@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 表达式都在一个专用的上下文中进行求值。下表列出了提供给该上下文的项,以便您在条件事件处理中使用它们:spring-doc.cadn.net.cn

表2. 事件 SpEL 可用的元数据
姓名 位置 <description> </description> 例举

事件spring-doc.cadn.net.cn

根对象spring-doc.cadn.net.cn

实际的ApplicationEventspring-doc.cadn.net.cn

#root.event or eventspring-doc.cadn.net.cn

参数数组spring-doc.cadn.net.cn

根对象spring-doc.cadn.net.cn

用于调用该方法的参数(作为对象数组)。spring-doc.cadn.net.cn

#root.argsargs;使用 args[0] 访问第一个参数,依此类推。spring-doc.cadn.net.cn

参数名称spring-doc.cadn.net.cn

评估上下文spring-doc.cadn.net.cn

任意方法参数的名称。如果由于某些原因名称不可用(例如,因为编译后的字节码中没有调试信息),也可以使用 #a<#arg> 语法来访问各个参数,其中 <#arg> 表示参数的索引(从 0 开始)。spring-doc.cadn.net.cn

#blEvent#a0(你也可以使用 #p0#p<#arg> 参数表示法作为别名)spring-doc.cadn.net.cn

请注意,#root.event 可让您访问底层事件,即使您的方法签名实际上引用的是所发布的任意对象。spring-doc.cadn.net.cn

如果你需要在处理另一个事件的结果中发布一个事件,可以更改方法签名以返回应发布的事件,如下例所示:spring-doc.cadn.net.cn

@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...
}
此功能不支持 异步监听器

handleBlockedListEvent() 方法会为其处理的每个 ListUpdateEvent 发布一个新的 BlockedListEvent。如果你需要发布多个事件,可以改为返回一个 Collection 或事件数组。spring-doc.cadn.net.cn

异步监听器

如果您希望特定的监听器异步处理事件,可以复用 常规 @Async 支持。 以下示例展示了如何实现:spring-doc.cadn.net.cn

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

使用异步事件时,请注意以下限制:spring-doc.cadn.net.cn

排序监听器

如果你需要让一个监听器在另一个监听器之前被调用,可以在方法声明上添加 @Order 注解,如下例所示:spring-doc.cadn.net.cn

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

通用事件

你也可以使用泛型来进一步定义事件的结构。考虑使用 EntityCreatedEvent<T>,其中 T 是实际被创建实体的类型。例如,你可以创建以下监听器定义,仅接收针对 EntityCreatedEventPersonspring-doc.cadn.net.cn

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

由于类型擦除,这种方式仅在所发布的事件能够解析事件监听器所过滤的泛型参数时才有效(例如:class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ })。spring-doc.cadn.net.cn

在某些情况下,如果所有事件都遵循相同的结构(如前例中的事件那样),这种做法可能会变得相当繁琐。在这种情况下,您可以实现 ResolvableTypeProvider 接口,以向框架提供超出运行时环境所提供的类型信息。以下事件展示了如何实现这一点:spring-doc.cadn.net.cn

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,也适用于你作为事件发送的任意对象。

最后,与传统的 ApplicationListener 实现一样,实际的事件广播在运行时通过一个上下文范围的 ApplicationEventMulticaster 来完成。默认情况下,这是一个 SimpleApplicationEventMulticaster,它会在调用者线程中同步发布事件。可以通过定义一个名为 “applicationEventMulticaster” 的 bean 来替换或自定义该行为,例如用于异步处理所有事件和/或处理监听器异常:spring-doc.cadn.net.cn

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

便捷访问底层资源

为了最佳地使用和理解应用上下文,您应熟悉 Spring 的 Resource 抽象,如资源一节所述。spring-doc.cadn.net.cn

应用程序上下文是一个 ResourceLoader,可用于加载 Resource 对象。 Resource 本质上是 JDK 中 java.net.URL 类的一个功能更丰富的版本。 实际上,Resource 的实现会在适当的情况下包装一个 java.net.URL 实例。 Resource 能以透明的方式从几乎任何位置获取底层资源,包括类路径、文件系统位置、任何可通过标准 URL 描述的位置,以及其他一些变体。 如果资源位置字符串是一个不带任何特殊前缀的简单路径,则这些资源的具体来源取决于实际的应用程序上下文类型。spring-doc.cadn.net.cn

您可以配置部署到应用上下文中的 Bean,使其实现特殊的回调接口 ResourceLoaderAware,这样在初始化时会自动回调该 Bean,并将应用上下文本身作为 ResourceLoader 传入。 您还可以暴露类型为 Resource 的属性,用于访问静态资源。 这些属性会像其他任何属性一样被注入。您可以将这些 Resource 属性指定为简单的 String 路径,在 Bean 部署时依赖从这些字符串自动转换为实际的 Resource 对象。spring-doc.cadn.net.cn

提供给 ApplicationContext 构造函数的位置路径(一个或多个)实际上是资源字符串,以简单形式表示时,会根据具体的上下文实现进行相应处理。例如,ClassPathXmlApplicationContext 会将一个简单的路径视为类路径(classpath)位置。您还可以在位置路径(资源字符串)前加上特殊前缀,以强制从类路径或 URL 加载定义,而不管实际的上下文类型是什么。spring-doc.cadn.net.cn

应用程序启动跟踪

ApplicationContext 管理 Spring 应用程序的生命周期,并围绕组件提供丰富的编程模型。因此,复杂的应用程序可能会拥有同样复杂的组件图和启动阶段。spring-doc.cadn.net.cn

通过特定指标跟踪应用程序的启动步骤,有助于了解在启动阶段时间消耗在哪些环节,同时也可以作为一种更好地理解整个上下文生命周期的方式。spring-doc.cadn.net.cn

AbstractApplicationContext(及其子类)被植入了一个 ApplicationStartup,用于收集有关各个启动阶段的StartupStep数据:spring-doc.cadn.net.cn

以下是 AnnotationConfigApplicationContext 中的插装(instrumentation)示例:spring-doc.cadn.net.cn

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

应用程序上下文已经集成了多个步骤的监控。 一旦记录下来,这些启动步骤就可以通过特定工具进行收集、展示和分析。 有关现有启动步骤的完整列表,请参阅 专门的附录章节spring-doc.cadn.net.cn

默认的 ApplicationStartup 实现是一个无操作(no-op)变体,以保持最低的开销。 这意味着在默认情况下,应用程序启动期间不会收集任何指标。 Spring Framework 提供了一个使用 Java Flight Recorder 跟踪启动步骤的实现: FlightRecorderApplicationStartup。要使用此变体,您必须在 ApplicationContext 创建后立即为其配置一个该类的实例。spring-doc.cadn.net.cn

如果开发者提供了自己的 ApplicationStartup 子类,或者希望收集更精确的数据,也可以使用 AbstractApplicationContext 基础设施。spring-doc.cadn.net.cn

ApplicationStartup 仅用于应用程序启动期间和核心容器;它绝不是 Java 性能分析工具或 Micrometer 等指标库的替代品。

要开始收集自定义的 StartupStep,组件可以直接从应用上下文中获取 ApplicationStartup 实例,也可以让其组件实现 ApplicationStartupAware 接口,或者在任意注入点请求 ApplicationStartup 类型。spring-doc.cadn.net.cn

开发者在创建自定义启动步骤时,不应使用 "spring.*" 命名空间。 该命名空间保留供 Spring 内部使用,并可能随时更改。

为 Web 应用程序提供便捷的 ApplicationContext 实例化

你可以通过声明式的方式创建 ApplicationContext 实例,例如使用 ContextLoader。当然,你也可以通过编程方式,使用某个 ApplicationContext 的实现类来创建 ApplicationContext 实例。spring-doc.cadn.net.cn

你可以通过使用 ApplicationContext 来注册一个 ContextLoaderListener,如下例所示:spring-doc.cadn.net.cn

<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 目录下任意子目录中所有此类文件)。spring-doc.cadn.net.cn

部署 SpringApplicationContext作为 Jakarta EE RAR 文件

可以将 Spring ApplicationContext 以 RAR 文件的形式进行部署,将该上下文及其所需的所有 Bean 类和库 JAR 文件封装在一个 Jakarta EE RAR 部署单元中。这种方式相当于在 Jakarta EE 环境中引导一个独立的 ApplicationContext(仅托管于 Jakarta EE 环境中),并能够访问 Jakarta EE 服务器所提供的各项功能。与部署一个无头 WAR 文件(即没有 HTTP 入口点、仅用于在 Jakarta EE 环境中引导 Spring ApplicationContext 的 WAR 文件)的场景相比,RAR 部署是一种更为自然的替代方案。spring-doc.cadn.net.cn

RAR 部署非常适合那些不需要 HTTP 入口点,而仅由消息端点和定时任务组成的应用上下文。此类上下文中的 Bean 可以使用应用服务器资源,例如 JTA 事务管理器、JNDI 绑定的 JDBC DataSource 实例以及 JMS ConnectionFactory 实例,还可以通过 Spring 提供的标准事务管理、JNDI 和 JMX 支持功能注册到平台的 JMX 服务器。此外,应用组件还可以通过 Spring 的 WorkManager 抽象与应用服务器的 JCA TaskExecutor 进行交互。spring-doc.cadn.net.cn

有关 RAR 部署所涉及的配置详情,请参阅 SpringContextResourceAdapter 类的 Javadoc。spring-doc.cadn.net.cn

将 Spring ApplicationContext 简单部署为 Jakarta EE RAR 文件:spring-doc.cadn.net.cn

  1. 将所有应用程序类打包到一个 RAR 文件中(RAR 文件实际上是一个标准的 JAR 文件,只是文件扩展名不同)。spring-doc.cadn.net.cn

  2. 将所有必需的库 JAR 文件添加到 RAR 归档文件的根目录中。spring-doc.cadn.net.cn

  3. 添加一个 META-INF/ra.xml 部署描述符(如 SpringContextResourceAdapter 的 Javadoc 中所示) 以及相应的 Spring XML Bean 定义文件(通常为 META-INF/applicationContext.xml)。spring-doc.cadn.net.cn

  4. 将生成的 RAR 文件放入您的应用服务器的部署目录中。spring-doc.cadn.net.cn

此类 RAR 部署单元通常是自包含的。它们不会向外部暴露组件,甚至不会向同一应用程序的其他模块暴露。基于 RAR 的 ApplicationContext 通常通过与其它模块共享的 JMS 目的地进行交互。例如,基于 RAR 的 ApplicationContext 还可以调度某些任务,或对文件系统中新文件(或类似事件)作出响应。如果它需要允许来自外部的同步访问,则可以(例如)导出 RMI 端点,供同一台机器上的其他应用模块使用。