ApplicationContext的附加功能

如在章节介绍中讨论的那样,org.springframework.beans.factory包提供了管理和操作bean的基本功能,包括以编程方式。 org.springframework.context包添加了ApplicationContext接口,该接口扩展了BeanFactory接口,除了扩展其他接口以提供更多应用程序框架导向的功能。许多人完全以声明方式使用ApplicationContext,甚至不是以编程方式创建它,而是依赖于支持类(如ContextLoader)来自动实例化ApplicationContext作为Jakarta EE Web应用程序的正常启动过程的一部分。

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

  • 通过MessageSource接口以i18n风格访问消息。

  • 通过ResourceLoader接口访问资源,如URL和文件。

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

  • 加载多个(分层)上下文,让每个上下文专注于特定层,例如应用程序的Web层,通过HierarchicalBeanFactory接口。

使用MessageSource进行国际化

ApplicationContext接口扩展了一个名为MessageSource的接口,因此提供了国际化("i18n")功能。Spring还提供了HierarchicalMessageSource接口,可以按层次解析消息。这些接口共同构成了Spring实现消息解析的基础。这些接口定义的方法包括:

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource中检索消息的基本方法。当找不到指定区域设置的消息时,将使用默认消息。传入的任何参数都将成为替换值,使用标准库提供的MessageFormat功能。

  • String getMessage(String code, Object[] args, Locale loc):与前一个方法本质上相同,但有一个区别:无法指定默认消息。如果找不到消息,将抛出NoSuchMessageException

  • String getMessage(MessageSourceResolvable resolvable, Locale locale):前述方法中使用的所有属性也都封装在一个名为MessageSourceResolvable的类中,您可以在此方法中使用它。

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

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>

该示例假定您的类路径中定义了三个资源包,分别称为formatexceptionswindows。任何解析消息的请求都是通过ResourceBundle对象以JDK标准方式处理的。为了示例的目的,假设上述两个资源包文件的内容如下:

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

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

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

上述程序的输出结果如下:

Alligators rock!

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

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

<beans>

	<!-- 此MessageSource正在用于Web应用程序 -->
	<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
		<property name="basename" value="exceptions"/>
	</bean>

	<!-- 让我们将上述MessageSource注入到此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 userDao argument is required.

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

通常,区域设置解析由应用程序的周围环境管理。在以下示例中,手动指定要针对(英国)解析消息的区域设置:

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

上述程序运行后的输出结果如下:

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

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

因为Spring的MessageSource基于Java的ResourceBundle,它不会合并具有相同基本名称的bundle,而只会使用找到的第一个bundle。后续具有相同基本名称的消息bundle将被忽略。
作为ResourceBundleMessageSource的替代,Spring提供了一个ReloadableResourceBundleMessageSource类。这个变体支持相同的bundle文件格式,但比基于标准JDK的ResourceBundleMessageSource实现更灵活。特别是,它允许从任何Spring资源位置(不仅仅是从类路径)读取文件,并支持热重新加载bundle属性文件(同时在它们之间高效地缓存)。查看ReloadableResourceBundleMessageSource的javadoc以获取详细信息。

标准和自定义事件

ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果部署了一个实现ApplicationListener接口的bean到上下文中,每当一个ApplicationEvent被发布到ApplicationContext时,该bean都会收到通知。本质上,这是标准的观察者设计模式。

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

以下表格描述了Spring提供的标准事件:

表1. 内置事件
事件 说明

ContextRefreshedEvent

ApplicationContext被初始化或刷新时(例如,通过ConfigurableApplicationContext接口上的refresh()方法),发布此事件。这里,“初始化”意味着所有bean都已加载,后处理器bean已被检测并激活,单例bean已被预实例化,并且ApplicationContext对象已准备就绪。只要上下文尚未关闭,就可以多次触发刷新,前提是所选的ApplicationContext确实支持这种“热”刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。

ContextStartedEvent

当通过ConfigurableApplicationContext接口上的start()方法启动ApplicationContext时发布。这里,“启动”意味着所有Lifecycle bean都接收到显式的启动信号。通常,此信号用于在显式停止后重新启动bean,但也可用于启动未配置为自动启动的组件(例如,未在初始化时已启动的组件)。

ContextStoppedEvent

当通过ConfigurableApplicationContext接口上的stop()方法停止ApplicationContext时发布。这里,“停止”意味着所有Lifecycle bean都接收到显式的停止信号。停止的上下文可以通过调用start()重新启动。

ContextClosedEvent

当通过ConfigurableApplicationContext接口上的close()方法或通过JVM关闭挂钩关闭ApplicationContext时发布。这里,“关闭”意味着所有单例bean将被销毁。一旦上下文关闭,它就达到了生命周期的终点,无法再刷新或重新启动。

RequestHandledEvent

一个特定于Web的事件,告知所有bean一个HTTP请求已被处理。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的Web应用程序。

ServletRequestHandledEvent

RequestHandledEvent的子类,添加了Servlet特定的上下文信息。

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

  • 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来完成此操作。以下示例展示了这样一个类:

  • 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...
	}
}
EmailService实现了 ApplicationEventPublisherAware,并自动调用 setApplicationEventPublisher()。实际上,传递的参数是Spring容器本身。您通过其 ApplicationEventPublisher接口与应用程序上下文进行交互。

ApplicationEvent,可以创建一个实现 ApplicationListener的类并将其注册为Spring bean。以下示例展示了这样一个类:

  • Java

  • Kotlin

public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

	private String notificationAddress;

	public void setNotificationAddress(String notificationAddress) {
		this.notificationAddress = notificationAddress;
	}

	public void onApplicationEvent(BlockedListEvent event) {
		// 通过notificationAddress通知适当的方...
class BlockedListNotifier : ApplicationListener<BlockedListEvent> {

	lateinit var notificationAddress: String

	override fun onApplicationEvent(event: BlockedListEvent) {
		// 通过notificationAddress通知适当的方...

请注意,ApplicationListener 使用您自定义事件的类型(在上面的示例中为BlockedListEvent)进行了泛型参数化。这意味着onApplicationEvent()方法可以保持类型安全,避免任何需要进行向下转型的情况。您可以注册任意数量的事件监听器,但请注意,默认情况下,事件监听器会同步接收事件。这意味着publishEvent()方法会阻塞,直到所有监听器完成事件处理。这种同步和单线程方法的一个优点是,当监听器接收到事件时,如果存在事务上下文,则它会在发布者的事务上下文中运行。如果需要另一种事件发布策略,例如默认情况下进行异步事件处理,请参阅Spring的ApplicationEventMulticaster接口和SimpleApplicationEventMulticaster实现的javadoc,了解可应用于自定义"applicationEventMulticaster" bean定义的配置选项。在这些情况下,线程本地变量和日志上下文不会传播到事件处理中。有关Observability方面的更多信息,请参阅@EventListener Observability部分

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

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

   <!-- 可选:自定义ApplicationEventMulticaster定义 -->
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
	<property name="taskExecutor" ref="..."/>
	<property name="errorHandler" ref="..."/>
</bean>

将所有内容整合在一起,当调用emailService bean的sendEmail()方法时,如果有任何应该被阻止的电子邮件消息,将发布一个类型为BlockedListEvent的自定义事件。blockedListNotifier bean被注册为一个ApplicationListener,并接收BlockedListEvent,此时它可以通知相关方。

Spring的事件机制旨在实现Spring应用程序上下文内Spring bean之间的简单通信。然而,对于更复杂的企业集成需求,由独立维护的Spring Integration项目提供了完整支持,用于构建基于轻量级、面向模式、事件驱动架构的解决方案,这些解决方案建立在众所周知的Spring编程模型之上。

基于注解的事件监听器

您可以使用@EventListener注解在托管bean的任何方法上注册事件监听器。 BlockedListNotifier可以重写如下:

  • Java

  • Kotlin

public class BlockedListNotifier {

	private String notificationAddress;

	public void setNotificationAddress(String notificationAddress) {
		this.notificationAddress = notificationAddress;
	}

	@EventListener
	public void processBlockedListEvent(BlockedListEvent event) {
		// 通过notificationAddress通知适当的方​​法...
	}
}
class BlockedListNotifier {

	lateinit var notificationAddress: String

	@EventListener
	fun processBlockedListEvent(event: BlockedListEvent) {
		// 通过notificationAddress通知适当的方​​法...
	}
}

方法签名再次声明了它监听的事件类型,但这次是使用灵活的名称,而无需实现特定的监听器接口。只要实际事件类型在其实现层次结构中解析您的泛型参数,事件类型也可以通过泛型缩小范围。

如果您的方法应该监听多个事件,或者如果您希望定义不带参数的方法,则事件类型也可以在注解本身上指定。以下示例显示了如何执行此操作:

  • Java

  • Kotlin

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

还可以通过使用注解的condition属性添加额外的运行时过滤,该属性定义了一个SpEL表达式,该表达式应匹配实际调用特定事件的方法。

以下示例显示了如何重写我们的通知器,只有当事件的content属性等于my-event时才调用:

  • Java

  • Kotlin

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
	// 通过notificationAddress通知适当的方​​法...
}
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
	// 通过notificationAddress通知适当的方​​法...
}

每个SpEL表达式都针对一个专用上下文进行评估。以下表列出了上下文中提供的项目,以便您可以将它们用于条件事件处理:

表2. 事件SpEL可用元数据
名称 位置 描述 示例

事件

根对象

实际的ApplicationEvent

#root.eventevent

参数数组

根对象

用于调用方法的参数(作为对象数组)。

#root.argsargs; args[0]访问第一个参数,依此类推。

参数名称

评估上下文

任何方法参数的名称。如果由于某种原因名称不可用(例如,因为编译的字节码中没有调试信息),则还可以使用#a<#arg>语法来使用单独的参数,其中<#arg>表示参数索引(从0开始)。

#blEvent#a0(您还可以使用#p0#p<#arg>参数表示法作为别名)

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

如果需要在处理另一个事件的结果上发布事件,可以更改方法签名以返回应该发布的事件,如下例所示:

  • Java

  • Kotlin

@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
	// 通过notificationAddress通知适当的方​​法,然后发布ListUpdateEvent...
}
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
	// 通过notificationAddress通知适当的方​​法,然后发布ListUpdateEvent...
}
此功能不支持异步监听器

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

异步监听器

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

  • Java

  • Kotlin

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
	// BlockedListEvent 在单独的线程中处理
}
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
	// BlockedListEvent 在单独的线程中处理
}

在使用异步事件时,请注意以下限制:

  • 如果异步事件监听器抛出异常,异常不会传播给调用者。请参阅AsyncUncaughtExceptionHandler了解更多详情。

  • 异步事件监听器方法不能通过返回值发布后续事件。如果需要发布另一个事件作为处理结果,请注入一个ApplicationEventPublisher手动发布事件。

  • 默认情况下,事件处理不会传播ThreadLocals和日志上下文。有关事件处理的可观察性问题,请参阅@EventListener 可观察性部分获取更多信息。

监听器排序

如果您需要一个监听器在另一个监听器之前被调用,可以在方法声明中添加@Order注解,如下例所示:

  • Java

  • Kotlin

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
	// 通过通知地址通知适当的方
@EventListener
@Order(42)
fun processBlockedListEvent(event: BlockedListEvent) {
	// 通过通知地址通知适当的方

泛型事件

您还可以使用泛型进一步定义事件的结构。考虑使用EntityCreatedEvent<T>,其中T是实际创建的实体类型。例如,您可以创建以下监听器定义,仅接收PersonEntityCreatedEvent

  • Java

  • Kotlin

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

由于类型擦除,只有在触发事件解析事件监听器过滤的泛型参数时才有效(即,类似class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ })。

在某些情况下,如果所有事件遵循相同的结构(正如前面示例中的事件应该是的情况),这可能变得相当繁琐。在这种情况下,您可以实现ResolvableTypeProvider来指导框架超出运行时环境提供的内容。以下事件展示了如何实现:

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

最后,与经典的ApplicationListener实现一样,实际的多播通过运行时的全局ApplicationEventMulticaster进行。默认情况下,这是一个具有同步事件发布的SimpleApplicationEventMulticaster在调用线程中。可以通过"applicationEventMulticaster" bean定义替换/自定义此行为,例如用于异步处理所有事件和/或处理监听器异常:

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

方便访问低级资源

为了最佳使用和理解应用程序上下文,您应该熟悉Spring的Resource抽象,如资源中所述。

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

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

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

应用程序启动跟踪

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

使用特定指标跟踪应用程序启动步骤可以帮助了解启动阶段花费时间的位置,还可以作为更好理解上下文生命周期整体的一种方式。

AbstractApplicationContext(及其子类)已经使用ApplicationStartup进行了仪器化,该仪器化收集有关各种启动阶段的StartupStep数据:

  • 应用程序上下文生命周期(基本包扫描、配置类管理)

  • bean生命周期(实例化、智能初始化、后处理)

  • 应用程序事件处理

以下是在AnnotationConfigApplicationContext中进行仪器化的示例:

  • Java

  • Kotlin

// 创建一个启动步骤并开始记录
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
// 为当前步骤添加标记信息
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// 执行我们正在仪器化的实际阶段
this.scanner.scan(basePackages);
// 结束当前步骤
scanPackages.end();
// 创建一个启动步骤并开始记录
val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")
// 为当前步骤添加标记信息
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
// 执行我们正在仪器化的实际阶段
this.scanner.scan(basePackages)
// 结束当前步骤
scanPackages.end()

应用程序上下文已经仪器化了多个步骤。一旦记录,这些启动步骤可以使用特定工具进行收集、显示和分析。要查看现有启动步骤的完整列表,您可以查看专用附录部分

默认的ApplicationStartup实现是一个无操作变体,开销最小。这意味着默认情况下不会在应用程序启动期间收集任何指标。Spring Framework附带了一个使用Java Flight Recorder跟踪启动步骤的实现:FlightRecorderApplicationStartup。要使用此变体,您必须在创建ApplicationContext后立即配置其实例。

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

ApplicationStartup仅用于应用程序启动和核心容器;这绝不是Java分析器或类似Micrometer的度量库的替代品。

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

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

为Web应用程序方便地实例化ApplicationContext

您可以通过使用例如ContextLoader声明性地创建ApplicationContext实例。当然,您也可以通过使用ApplicationContext的实现之一来以编程方式创建ApplicationContext实例。

您可以使用ContextLoaderListener注册一个ApplicationContext,如下例所示:

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

监听器检查contextConfigLocation参数。如果参数不存在,则监听器使用/WEB-INF/applicationContext.xml作为默认值。当参数存在时,监听器使用预定义的分隔符(逗号、分号和空格)将String分隔开,并使用这些值作为搜索应用程序上下文的位置。还支持Ant样式路径模式。例如,/WEB-INF/*Context.xml(用于所有以Context.xml结尾且位于WEB-INF目录中的文件)和/WEB-INF/**/*Context.xml(用于WEB-INF的任何子目录中的所有这样的文件)。

将Spring ApplicationContext部署为Jakarta EE RAR文件

可以将Spring ApplicationContext部署为RAR文件,将上下文及其所有必需的bean类和库JAR包封装在Jakarta EE RAR部署单元中。这相当于在Jakarta EE环境中启动一个独立的ApplicationContext(仅托管在Jakarta EE环境中),能够访问Jakarta EE服务器的功能。RAR部署是部署无头WAR文件的更自然选择,实际上是一个没有任何HTTP入口点的WAR文件,仅用于在Jakarta EE环境中启动Spring ApplicationContext

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

查看SpringContextResourceAdapter类的javadoc,了解RAR部署涉及的配置细节。

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

  1. 将所有应用类打包到RAR文件中(这是一个带有不同文件扩展名的标准JAR文件)。

  2. 将所有必需的库JAR包添加到RAR存档的根目录中。

  3. 添加一个META-INF/ra.xml部署描述符(如SpringContextResourceAdapter的javadoc所示)和相应的Spring XML bean定义文件(通常为META-INF/applicationContext.xml)。

  4. 将生成的RAR文件放入应用服务器的部署目录中。

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