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,所有對前面方法的呼叫都會委託給該訊息源。如果沒有找到訊息源,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
的檔案中,該檔案位於類路徑的根目錄下。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
的引用。在 ApplicationContext
中定義的任何實現 MessageSourceAware
介面的 Bean,在建立和配置時都會被注入應用程式上下文的 MessageSource
。
因為 Spring 的 MessageSource 基於 Java 的 ResourceBundle ,所以它不會合並具有相同基本名稱的捆綁包,而只會使用找到的第一個捆綁包。後續具有相同基本名稱的訊息捆綁包將被忽略。 |
作為 ResourceBundleMessageSource 的替代方案,Spring 提供了一個 ReloadableResourceBundleMessageSource 類。這個變體支援相同的捆綁包檔案格式,但比標準的基於 JDK 的 ResourceBundleMessageSource 實現更靈活。特別是,它允許從任何 Spring 資源位置(不僅僅是類路徑)讀取檔案,並支援捆綁包屬性檔案的熱過載(同時在兩次過載之間高效地快取它們)。有關詳細資訊,請參閱 ReloadableResourceBundleMessageSource 的 Javadoc。 |
標準和自定義事件
ApplicationContext
中的事件處理透過 ApplicationEvent
類和 ApplicationListener
介面提供。如果在上下文中部署了一個實現 ApplicationListener
介面的 Bean,那麼每次有 ApplicationEvent
釋出到 ApplicationContext
時,該 Bean 都會收到通知。本質上,這是標準的 Observer(觀察者)設計模式。
從 Spring 4.2 開始,事件基礎設施得到了顯著改進,提供了基於註解的模型,以及釋出任何任意事件(即不一定繼承自 ApplicationEvent 的物件)的能力。當釋出此類物件時,我們會將其包裝在一個事件中。 |
下表描述了 Spring 提供的標準事件:
事件 | 說明 |
---|---|
|
當 |
|
當 |
|
當 |
|
當 |
|
一個 Web 特定的事件,通知所有 Bean 已處理完一個 HTTP 請求。此事件在請求完成後釋出。此事件僅適用於使用 Spring 的 |
|
|
你還可以建立和釋出自己的自定義事件。下面的示例展示了一個繼承自 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>[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>
總之,當呼叫 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 定義為延遲初始化(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
表示式都在一個專用上下文中進行評估。下表列出了可用於上下文的項,以便您可以使用它們進行條件事件處理
名稱 | 位置 | 描述 | 示例 |
---|---|---|---|
事件 |
根物件 |
實際的 |
|
引數陣列 |
根物件 |
用於呼叫方法的引數(作為物件陣列)。 |
|
引數名稱 |
評估上下文 |
特定方法引數的名稱。如果名稱不可用(例如,因為程式碼編譯時沒有使用 |
|
請注意,即使您的方法簽名實際引用的是釋出的任意物件,#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 和日誌上下文。有關可觀察性問題的更多資訊,請參閱
@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>) {
// ...
}
由於型別擦除,這僅在觸發的事件解析了事件監聽器過濾所基於的泛型引數時才起作用(即,類似於 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 來實現特殊的 callback 介面 ResourceLoaderAware
,以便在初始化時自動回撥,並將應用程式上下文字身作為 ResourceLoader
傳入。您還可以暴露 Resource
型別的屬性,用於訪問靜態資源。它們像其他任何屬性一樣被注入。您可以將這些 Resource
屬性指定為簡單的 String
路徑,並在部署 bean 時依靠自動轉換將這些文字字串轉換為實際的 Resource
物件。
提供給 ApplicationContext
建構函式的位置路徑實際上是資源字串,以簡單形式表示時,會根據具體的上下文實現進行適當處理。例如,ClassPathXmlApplicationContext
將簡單的位置路徑視為類路徑位置。您還可以使用帶有特殊字首的位置路徑(資源字串)來強制從類路徑或 URL 載入定義,無論實際上下文型別如何。
應用程式啟動跟蹤
ApplicationContext
管理 Spring 應用程式的生命週期,並提供圍繞元件的豐富程式設計模型。因此,複雜的應用程式可以擁有同樣複雜的元件圖和啟動階段。
使用特定指標跟蹤應用程式啟動步驟有助於理解啟動階段的時間花費在哪裡,它也可以作為更好地理解整個上下文生命週期的一種方式。
AbstractApplicationContext
(及其子類)透過 ApplicationStartup
進行檢測,它收集有關各種啟動階段的 StartupStep
資料
-
應用程式上下文生命週期(基礎包掃描、配置類管理)
-
beans 生命週期(例項化、智慧初始化、後處理)
-
應用程式事件處理
以下是 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
建立後立即為其配置一個例項。
如果開發者提供自己的 AbstractApplicationContext
子類,或者希望收集更精確的資料,他們也可以使用 ApplicationStartup
基礎設施。
ApplicationStartup 僅用於應用程式啟動期間和核心容器;它絕不是 Java 分析器或像 Micrometer 這樣的指標庫的替代品。 |
要開始收集自定義的 StartupStep
,元件可以直接從應用程式上下文獲取 ApplicationStartup
例項,讓其元件實現 ApplicationStartupAware
,或在任何注入點請求 ApplicationStartup
型別。
開發者在建立自定義啟動步驟時,不應使用 "spring.*" 名稱空間。此名稱空間保留用於 Spring 內部使用,並且可能會更改。 |
Web 應用中便捷的 ApplicationContext 例項化
您可以透過宣告方式建立 ApplicationContext
例項,例如使用 ContextLoader
。當然,您也可以透過使用 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
(用於 WEB-INF
目錄下所有以 Context.xml
結尾的檔案)和 /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 檔案場景的更自然替代方案——實際上,無頭 WAR 檔案是一個沒有任何 HTTP 入口點,僅用於在 Jakarta EE 環境中引導 Spring ApplicationContext
的 WAR 檔案。
RAR 部署非常適合不需要 HTTP 入口點,而僅包含訊息端點和計劃任務的應用程式上下文。此類上下文中的 bean 可以使用應用伺服器資源,例如 JTA 事務管理器以及 JNDI 繫結的 JDBC DataSource
例項和 JMS ConnectionFactory
例項,並且還可以向平臺的 JMX 伺服器註冊——所有這些都透過 Spring 的標準事務管理、JNDI 和 JMX 支援設施實現。應用程式元件還可以透過 Spring 的 TaskExecutor
抽象與應用伺服器的 JCA WorkManager
進行互動。
有關 RAR 部署所涉及的配置詳細資訊,請參閱 SpringContextResourceAdapter
類的 javadoc。
簡單地將 Spring ApplicationContext 部署為 Jakarta EE RAR 檔案
-
將所有應用程式類打包到 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 端點,這些端點可供同一機器上的其他應用程式模組使用。 |