ApplicationContext
的附加功能
ApplicationContext
应用程序上下文的附加功能
如在 章节介绍 中讨论的,org.springframework.beans.factory
包提供了管理和操作 beans 的基本功能,包括以编程方式进行操作。org.springframework.context
包添加了 ApplicationContext 接口,该接口扩展了 BeanFactory
接口,并扩展了其他接口,以提供更面向应用程序框架风格的附加功能。许多人以完全声明的方式使用 ApplicationContext
,甚至不以编程方式创建它,而是依赖于支持类,如 ContextLoader
,在 Jakarta EE Web 应用程序的正常启动过程中自动实例化 ApplicationContext
。
为了以更框架导向的风格增强 BeanFactory
的功能,context 包还提供了以下功能:
-
通过
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
实现,ResourceBundleMessageSource
、ReloadableResourceBundleMessageSource
和 StaticMessageSource
。它们都实现了 HierarchicalMessageSource
以支持嵌套消息。StaticMessageSource
很少使用,但提供了以编程方式向源添加消息的方法。以下示例展示了 ResourceBundleMessageSource
:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
该示例假设您在类路径中定义了三个资源包,分别为 format
、exceptions
和 windows
。任何请求解析消息的操作都通过 ResourceBundle
对象以 JDK 标准方式处理。为了示例的目的,假设上述两个资源包文件的内容如下:
# in format.properties
message=Alligators rock!
# in 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.properties
、exceptions.properties
和 windows.properties
。
下面的示例展示了传递给消息查找的参数。这些参数被转换为 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>
- 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
实现遵循与标准 JDK ResourceBundle
相同的区域设置解析和回退规则。简而言之,继续使用之前定义的示例 messageSource
,如果您想要针对英国(en-GB
)区域解析消息,您需要分别创建名为 format_en_GB.properties
、exceptions_en_GB.properties
和 windows_en_GB.properties
的文件。
通常,区域设置解析由应用程序的周围环境管理。在以下示例中,手动指定了解析(英国)消息的区域设置:
# 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)
}
运行上述程序的结果输出如下:
Ebagum lad, the 'userDao' argument is required, I say, required.
您还可以使用 MessageSourceAware
接口来获取对任何已定义的 MessageSource
的引用。任何在实现了 MessageSourceAware
接口的 ApplicationContext
中定义的 bean 在创建和配置时都会注入应用程序上下文的 MessageSource
。
因为 Spring 的 MessageSource
基于 Java 的 ResourceBundle
,它不会合并具有相同基本名称的资源包,而只会使用找到的第一个资源包。后续具有相同基本名称的消息资源包将被忽略。
作为 ResourceBundleMessageSource
的替代方案,Spring 提供了 ReloadableResourceBundleMessageSource
类。这个变体支持相同的捆绑文件格式,但比基于标准 JDK 的 ResourceBundleMessageSource
实现更灵活。特别是,它允许从任何 Spring 资源位置读取文件(不仅限于类路径),并支持捆绑属性文件的热重载(同时在两者之间有效缓存)。有关详细信息,请参见 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 | 一个特定于网页的事件,通知所有 bean 已处理 HTTP 请求。此事件在请求完成后发布。此事件仅适用于使用 Spring 的 DispatcherServlet 的网页应用程序。 |
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...
}
}
在配置时,Spring 容器检测到 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) {
// 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()
方法会阻塞,直到所有监听器完成事件处理。这种同步和单线程的方法的一个优点是,当监听器接收到事件时,如果有可用的事务上下文,它将在发布者的事务上下文中操作。如果需要另一种事件发布策略,例如默认的异步事件处理,请参阅 Spring 的 ApplicationEventMulticaster 接口和 SimpleApplicationEventMulticaster 实现的 javadoc,以获取可以应用于自定义 "applicationEventMulticaster" bean 定义的配置选项。在这些情况下,ThreadLocals 和日志上下文不会在事件处理过程中传播。有关可观察性问题的更多信息,请参见 @EventListener 可观察性部分。
以下示例展示了用于注册和配置上述每个类的 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>
<!-- optional: a custom ApplicationEventMulticaster definition -->
<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 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) {
// notify appropriate parties via notificationAddress...
}
}
class BlockedListNotifier {
lateinit var notificationAddress: String
@EventListener
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
不要将这样的 bean 定义为懒加载,因为 ApplicationContext
会遵循这一点,并且不会注册该方法以监听事件。
方法签名再次声明了它监听的事件类型,但这次使用了灵活的名称,并且没有实现特定的监听器接口。事件类型也可以通过泛型进行细化,只要实际的事件类型在其实现层次结构中解析了你的泛型参数。
如果您的方法应该监听多个事件,或者您希望完全不带参数地定义它,则事件类型也可以在注解本身上指定。以下示例展示了如何做到这一点:
- Java
- Kotlin
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
// ...
}
还可以通过使用定义 SpEL 表达式 的注解的 condition
属性来添加额外的运行时过滤,这应该与特定事件实际调用该方法相匹配。
以下示例展示了如何重写我们的通知器,以便仅在事件的 content
属性等于 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
表达式都在一个专用的上下文中进行评估。下表列出了可用于上下文的项目,以便您可以将它们用于条件事件处理:
表 2. 可用于 SpEL 表达式的事件元数据
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
事件 | 根对象 | 实际的 ApplicationEvent 。 | #root.event 或 event |
参数数组 | 根对象 | 用于调用方法的参数(作为对象数组)。 | #root.args 或 args ;args[0] 访问第一个参数,等等。 |
参数名称 | 评估上下文 | 特定方法参数的名称。如果名称不可用(例如,因为代码在没有 -parameters 标志的情况下编译),可以使用 #a<#arg> 语法访问单个参数,其中 <#arg> 代表参数索引(从 0 开始)。 | #blEvent 或 #a0 (您还可以使用 #p0 或 #p<#arg> 参数符号作为别名) |
请注意,#root.event
让您可以访问底层事件,即使您的方法签名实际上引用的是一个被发布的任意对象。
如果您需要发布一个事件作为处理另一个事件的结果,您可以更改方法签名以返回应该发布的事件,如以下示例所示:
- 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...
}
此功能不支持 异步监听器。
handleBlockedListEvent()
方法为它处理的每个 BlockedListEvent
发布一个新的 ListUpdateEvent
。如果您需要发布多个事件,可以返回一个 Collection
或一个事件数组。
异步监听器
如果您希望特定的监听器异步处理事件,可以重用 常规 @Async 支持。以下示例展示了如何做到这一点:
- 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
}
使用异步事件时,请注意以下限制:
-
如果一个异步事件监听器抛出
Exception
,它不会被传播到调用者。有关更多细节,请参见 AsyncUncaughtExceptionHandler。 -
异步事件监听器方法不能通过返回值发布后续事件。如果您需要在处理结果中发布另一个事件,请注入 ApplicationEventPublisher 以手动发布事件。
-
默认情况下,ThreadLocals 和日志上下文在事件处理过程中不会被传播。有关可观察性问题的更多信息,请参见 the @EventListener Observability section。
排序监听器
如果您需要一个监听器在另一个监听器之前被调用,可以在方法声明中添加 @Order
注解,如下例所示:
- 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...
}
通用事件
您还可以使用泛型进一步定义事件的结构。考虑使用 EntityCreatedEvent<T>
,其中 T
是实际创建的实体的类型。例如,您可以创建以下监听器定义,仅接收 Person
的 EntityCreatedEvent
:
- 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
抽象,如 Resources 中所述。
应用程序上下文是一个 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
// 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()
应用程序上下文已经通过多个步骤进行了仪器化。一旦记录,这些启动步骤可以使用特定工具进行收集、显示和分析。有关现有启动步骤的完整列表,您可以查看 专门的附录部分。
默认的 ApplicationStartup
实现是一个无操作变体,以最小化开销。这意味着在应用程序启动期间默认不会收集任何指标。Spring Framework 提供了一个用于跟踪启动步骤的实现,使用 Java Flight Recorder:FlightRecorderApplicationStartup
。要使用此变体,您必须在 ApplicationContext
创建后尽快配置它的实例。
开发人员还可以使用 ApplicationStartup
基础设施,如果他们提供自己的 AbstractApplicationContext
子类,或者如果他们希望收集更精确的数据。
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 部署单元中。这相当于引导一个独立的 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 文件:
-
将所有应用程序类打包到一个 RAR 文件中(这是一个标准的 JAR 文件,文件扩展名不同)。
-
将所有必需的库 JAR 文件添加到 RAR 存档的根目录。
-
添加一个
META-INF/ra.xml
部署描述符(如 SpringContextResourceAdapter 的 javadoc 中所示)和相应的 Spring XML bean 定义文件(通常是META-INF/applicationContext.xml
)。 -
将生成的 RAR 文件放入您的应用服务器的部署目录中。
这样的 RAR 部署单元通常是自包含的。它们不向外部世界暴露组件,甚至不向同一应用程序的其他模块暴露。与基于 RAR 的 ApplicationContext
的交互通常通过它与其他模块共享的 JMS 目标进行。基于 RAR 的 ApplicationContext
还可以,例如,调度一些作业或对文件系统中的新文件(或类似内容)作出反应。如果它需要允许外部的同步访问,它可以(例如)导出 RMI 端点,这些端点可以被同一机器上的其他应用模块使用。