ApplicationContext的附加功能
ApplicationContext
如章节介绍中所述,org.springframework.beans.factory包提供了管理和操作bean的基本功能,包括以编程方式来进行管理。org.springframework.context包则增加了ApplicationContext接口,该接口继承自BeanFactory接口,并且还扩展了其他接口,从而以更加面向应用程序框架的方式提供了额外的功能。许多人以完全声明性的方式使用ApplicationContext,甚至不通过编程方式创建它,而是依赖于诸如ContextLoader这样的支持类,在Jakarta EE Web应用程序的正常启动过程中自动实例化ApplicationContext。
为了以更面向框架的方式增强BeanFactory的功能,上下文(context)包还提供了以下功能:
- 通过
MessageSource接口以 i18n 样式访问消息。 - 通过
ResourceLoader接口访问资源,如 URL 和文件。 - 通过使用
ApplicationEventPublisher接口来发布事件,即向实现ApplicationListener接口的 Bean 发送事件。 - 通过
HierarchicalBeanFactory接口加载多个(层次化的)上下文,每个上下文可以专注于特定的层面,例如应用程序的 Web 层面。
使用 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。任何解析消息的请求都按照 JDK 标准方式通过 ResourceBundle 对象来处理。为了示例的需要,假设上述两个资源包文件的内容如下:
# 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的文件中定义的,该文件位于你的类路径(classpath)的根目录下。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相同的区域设置(locale)解析和回退规则。简而言之,继续之前定义的messageSource示例,如果你想针对英国(en-GB)区域设置来解析消息,你需要分别创建名为format_en_GB.properties、exceptions_en_GB.properties和windows_en_GB.properties的文件。
通常,区域设置(locale)的解析由应用程序的周围环境来管理。在以下示例中,(英国)消息所针对的区域设置是手动指定的:
# 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的引用。在任何在ApplicationContext中定义的、实现了MessageSourceAware接口的Bean中,当该Bean被创建和配置时,都会注入应用程序上下文的MessageSource。
由于Spring的MessageSource基于Java的ResourceBundle,它不会合并具有相同基础名称的bundle,而只会使用找到的第一个bundle。随后具有相同基础名称的消息bundle将被忽略。
作为 ResourceBundleMessageSource 的替代方案,Spring 提供了 ReloadableResourceBundleMessageSource 类。这种变体支持相同的资源文件格式,但比基于 JDK 的标准 ResourceBundleMessageSource 实现更加灵活。特别是,它允许从任何 Spring 资源位置(而不仅是从类路径)读取文件,并支持热重载资源文件属性(同时能在其间高效地进行缓存)。详情请参阅 ReloadableResourceBundleMessageSource 的 Java 文档。
标准事件和自定义事件
在ApplicationContext中,事件处理是通过ApplicationEvent类和ApplicationListener接口来实现的。如果有一个实现了ApplicationListener接口的bean被部署到上下文中,那么每当有ApplicationEvent被发布到ApplicationContext时,这个bean就会收到通知。本质上,这就是标准的观察者(Observer)设计模式。
从Spring 4.2开始,事件基础设施得到了显著改进,提供了基于注解的模型,并且能够发布任何任意的事件(即,不一定继承自ApplicationEvent的对象)。当这样的对象被发布时,我们会为您将其包装成事件。
下表描述了Spring提供的标准事件:
表1. 内置事件
| 事件 | 说明 |
|---|---|
ContextRefreshedEvent | 当 ApplicationContext 被初始化或刷新时发布(例如,通过使用 ConfigurableApplicationContext 接口的 refresh() 方法)。这里,“初始化”意味着所有 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 节点来完成的。以下示例展示了一个这样的类:
- 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容器本身。你是通过EmailService的ApplicationEventPublisher接口与应用程序上下文进行交互的。
要接收自定义的ApplicationEvent,你可以创建一个实现ApplicationListener的类,并将其注册为Springbean。以下示例展示了这样一个类:
- 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 实现,其中提供了可以应用于自定义 “applicationEventMulticaster” bean 定义的配置选项。在这些情况下,事件处理过程中不会传递 ThreadLocal 和日志上下文。有关可观察性(Observability)的更多信息,请参阅 @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>
总而言之,当调用emailServiceBean的sendEmail()方法时,如果有任何电子邮件需要被阻止,就会发布一个类型为BlockedListEvent的自定义事件。blockedListNotifierBean被注册为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定义为懒加载的(lazy),因为ApplicationContext会尊重这一设置,且不会注册用于监听事件的方法。
方法签名再次声明了它所监听的事件类型,但这一次,使用了一个更为灵活的名称,并且没有实现特定的监听器接口。只要实际的事件类型在其实现层次结构中能够满足泛型参数的要求,就可以通过泛型来进一步限定事件类型。
如果你的方法需要监听多个事件,或者你希望完全不使用参数来定义它,那么也可以在注解本身上指定事件类型。以下示例展示了如何实现这一点:
- 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) {
// notify appropriate parties via notificationAddress...
}
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
每个SpEL表达式都是在特定的上下文中进行评估的。下表列出了该上下文中可用的元素,以便您可以利用它们来进行条件事件处理:
表2. SpEL表达式中可用的事件元数据
| 名称 | 位置 | 描述 | 示例 |
|---|---|---|---|
| Event | 根对象 | 实际的 ApplicationEvent。 | #root.event 或 event |
| Arguments array | 根对象 | 用于调用方法的参数(以对象数组的形式)。 | #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 和日志上下文不会被传递。有关可观察性(Observability)的更多信息,请参见 @EventListener 可观察性部分。
排序监听器
如果需要一个监听器在另一个监听器之前被调用,你可以在方法声明上添加@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>) {
// ...
}
由于类型擦除(type erasure)机制,这种方法只有在触发的事件能够解析出事件监听器所依赖的泛型参数时才能正常工作(也就是说,需要满足类似 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来完成的。默认情况下,这个ApplicationEventMulticaster是一个SimpleApplicationEventMulticaster,它会在调用线程中同步发布事件。可以通过定义一个“applicationEventMulticaster”bean来替换或自定义它,例如,以便异步处理所有事件和/或处理监听器异常:
@Bean
ApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(...);
multicaster.setErrorHandler(...);
return multicaster;
}
方便地访问低级资源
为了最佳地使用和理解应用程序上下文,你应该熟悉Spring的Resource抽象类,如Resources中所描述的。
应用程序上下文(application context)实际上是一个ResourceLoader,它可以用来加载Resource对象。Resource本质上是对JDK中的java.net.URL类的一个功能更丰富的扩展。事实上,在适当的情况下,Resource的实现会封装一个java.net.URL的实例。Resource能够以一种透明的方式从几乎任何位置获取低级资源,包括类路径(classpath)、文件系统中的位置、任何可以用标准URL描述的位置,以及其他一些变体。如果资源位置的字符串是一个没有特殊前缀的简单路径,那么这些资源的来源就是具体的,并且与实际的应用程序上下文类型相适应。
你可以配置一个部署到应用程序上下文(application context)中的bean,使其实现一个特殊的回调接口ResourceLoaderAware,这样在初始化时该bean就会自动被调用,并且应用程序上下文本身会作为ResourceLoader参数传递给它。你还可以暴露类型为Resource的属性,以便用来访问静态资源。这些属性就像其他属性一样被注入到bean中。你可以将这些Resource属性指定为简单的String路径,并依靠在bean部署时从这些文本字符串到实际Resource对象的自动转换机制。
提供给ApplicationContext构造函数的路径实际上都是资源字符串(resource strings),根据具体的上下文实现,这些路径会以适当的方式被处理。例如,ClassPathXmlApplicationContext会将简单的路径视为类路径(classpath)路径。你还可以使用带有特殊前缀的路径(资源字符串),强制从类路径或URL加载定义,而无需考虑实际的上下文类型。
应用启动跟踪
ApplicationContext 管理着 Spring 应用程序的生命周期,并围绕组件提供了一个丰富的编程模型。因此,复杂的应用程序可以拥有同样复杂的组件图和启动阶段。
通过特定的指标来跟踪应用程序的启动步骤,有助于了解在启动阶段时间消耗在了哪些地方,但这也能够作为一种方式,更好地理解整个应用程序的生命周期。
AbstractApplicationContext(及其子类)配备了一个ApplicationStartup组件,该组件会收集关于各种启动阶段的StartupStep数据:
- 应用上下文生命周期(基础包扫描、配置类管理)
- Bean 生命周期(实例化、智能初始化、后处理)
- 应用事件处理
以下是AnnotationConfigApplicationContext中的一种配置示例:
- Java
- Kotlin
// create a startup step and start recording
try (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);
}
// create a startup step and start recording
try (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);
}
应用程序上下文已经通过多个步骤进行了工具化处理。一旦这些启动步骤被记录下来,就可以使用特定的工具来收集、显示和分析它们。有关现有启动步骤的完整列表,您可以查看专门的附录部分。
默认的ApplicationStartup实现是一个无操作(no-op)版本,以减少开销。这意味着在应用程序启动过程中,默认情况下不会收集任何指标数据。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作为默认值。当参数存在时,监听器会使用预定义的分隔符(逗号、分号和空白字符)来分割字符串,并将这些值作为搜索应用上下文的位置。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环境中),但该ApplicationContext能够访问Jakarta EE服务器的功能。与部署无头WAR文件(即没有HTTP入口点的WAR文件,仅用于在Jakarta EE环境中启动SpringApplicationContext)相比,RAR部署是一种更自然的替代方案。
RAR部署非常适合那些不需要HTTP入口点,而只包含消息端点和定时作业的应用场景。在这样的环境中,Bean可以使用应用程序服务器的资源,如JTA事务管理器、通过JNDI绑定的JDBC DataSource实例和JMS ConnectionFactory实例,还可以通过Spring的标准事务管理和JNDI/JMX支持功能注册到平台的JMX服务器上。应用程序组件还可以通过Spring的TaskExecutor抽象与应用程序服务器的JCA WorkManager进行交互。
有关RAR部署所涉及的配置细节,请参阅SpringContextResourceAdapter类的Javadoc。
对于简单地将Spring ApplicationContext作为Jakarta EE RAR文件进行部署:
- 将所有应用程序类打包成一个RAR文件(这是一个具有不同文件扩展名的标准JAR文件)。
- 将所有所需的库JAR文件添加到RAR压缩包的根目录中。
- 添加一个
META-INF/ra.xml部署描述符(如SpringContextResourceAdapter的javadoc中所示),以及相应的Spring XMLbean定义文件(通常是META-INF/applicationContext.xml)。 - 将生成的RAR文件放入应用程序服务器的部署目录中。
此类RAR部署单元通常是自包含的。它们不会将组件暴露给外部世界,甚至不会暴露给同一应用程序的其他模块。与基于RAR的ApplicationContext的交互通常是通过它与其他模块共享的JMS目的地来进行的。基于RAR的ApplicationContext还可以例如安排某些任务或对文件系统中的新文件(或类似情况)做出反应。如果需要允许外部进行同步访问,它可以(例如)导出RMI端点,这些端点可以被同一机器上的其他应用程序模块使用。