ApplicationContext 的其他功能

章節簡介中所討論,org.springframework.beans.factory 套件提供了管理和操作 Bean 的基本功能,包括以程式化的方式。org.springframework.context 套件新增了 ApplicationContext 介面,除了擴展其他介面以提供更面向應用程式框架風格的其他功能之外,它還擴展了 BeanFactory 介面。許多人以完全宣告式的方式使用 ApplicationContext,甚至不以程式化的方式建立它,而是依賴諸如 ContextLoader 之類的支援類別,以在 Jakarta EE Web 應用程式的正常啟動過程中自動實例化 ApplicationContext

為了以更面向框架的風格增強 BeanFactory 功能,context 套件還提供了以下功能:

  • 透過 MessageSource 介面,存取 i18n 風格的訊息。

  • 透過 ResourceLoader 介面,存取資源,例如 URL 和檔案。

  • 事件發布,即發布到實作 ApplicationListener 介面的 Bean,透過使用 ApplicationEventPublisher 介面。

  • 載入多個(階層式)Context,讓每個 Context 都專注於一個特定的層,例如應用程式的 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 載入時,它會自動搜尋在 Context 中定義的 MessageSource Bean。該 Bean 必須具有名稱 messageSource。如果找到這樣的 Bean,則對先前方法的所有呼叫都委派給訊息來源。如果找不到訊息來源,則 ApplicationContext 會嘗試尋找包含具有相同名稱的 Bean 的父 Context。如果找到,它會將該 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。任何解析訊息的請求都以 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.propertiesexceptions.propertieswindows.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.propertiesexceptions_en_GB.propertieswindows_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,在建立和組態 Bean 時,都會注入應用程式 Context 的 MessageSource

由於 Spring 的 MessageSource 基於 Java 的 ResourceBundle,因此它不會合併具有相同基本名稱的套件,而只會使用找到的第一個套件。具有相同基本名稱的後續訊息套件將被忽略。
作為 ResourceBundleMessageSource 的替代方案,Spring 提供了 ReloadableResourceBundleMessageSource 類別。此變體支援相同的套件檔案格式,但比基於標準 JDK 的 ResourceBundleMessageSource 實作更靈活。特別是,它允許從任何 Spring 資源位置(不僅僅是從類別路徑)讀取檔案,並支援套件屬性檔案的熱重新載入(同時在兩次重新載入之間有效地快取它們)。有關詳細資訊,請參閱 ReloadableResourceBundleMessageSource Javadoc。

標準和自訂事件

ApplicationContext 中的事件處理是透過 ApplicationEvent 類別和 ApplicationListener 介面提供的。如果將實作 ApplicationListener 介面的 Bean 部署到 Context 中,則每次將 ApplicationEvent 發布到 ApplicationContext 時,都會通知該 Bean。本質上,這是標準的觀察者設計模式。

從 Spring 4.2 開始,事件基礎架構已得到顯著改進,並提供了基於註解的模型,以及發布任何任意事件的能力(即,不一定從 ApplicationEvent 擴展的物件)。當發布這樣的物件時,我們會將其包裝在事件中。

下表描述了 Spring 提供的標準事件:

表 1. 內建事件
事件 說明

ContextRefreshedEvent

ApplicationContext 初始化或刷新時發布(例如,透過在 ConfigurableApplicationContext 介面上使用 refresh() 方法)。這裡,“初始化”表示所有 Bean 都已載入、後處理器 Bean 已偵測和啟用、單例 Bean 已預先實例化,並且 ApplicationContext 物件已準備好使用。只要 Context 尚未關閉,就可以多次觸發刷新,前提是所選的 ApplicationContext 實際上支援這種“熱”刷新。例如,XmlWebApplicationContext 支援熱刷新,但 GenericApplicationContext 不支援。

ContextStartedEvent

ApplicationContext 透過在 ConfigurableApplicationContext 介面上使用 start() 方法啟動時發布。這裡,“啟動”表示所有 Lifecycle Bean 都會收到明確的啟動訊號。通常,此訊號用於在明確停止後重新啟動 Bean,但它也可用於啟動尚未組態為自動啟動的組件(例如,尚未在初始化時啟動的組件)。

ContextStoppedEvent

ApplicationContext 透過在 ConfigurableApplicationContext 介面上使用 stop() 方法停止時發布。這裡,“停止”表示所有 Lifecycle Bean 都會收到明確的停止訊號。停止的 Context 可以透過 start() 呼叫重新啟動。

ContextClosedEvent

ApplicationContext 透過在 ConfigurableApplicationContext 介面上使用 close() 方法或透過 JVM 關閉掛鉤關閉時發布。這裡,“關閉”表示所有單例 Bean 都將被銷毀。一旦 Context 關閉,它就會到達生命週期結束,並且無法刷新或重新啟動。

RequestHandledEvent

一個 Web 特定事件,告知所有 Bean HTTP 請求已得到服務。此事件在請求完成後發布。此事件僅適用於使用 Spring 的 DispatcherServlet 的 Web 應用程式。

ServletRequestHandledEvent

RequestHandledEvent 的子類別,它新增了 Servlet 特定的 Context 資訊。

您也可以建立和發布自己的自訂事件。以下範例顯示了一個簡單的類別,它擴展了 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 介面與應用程式 Context 互動。

若要接收自訂 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() 方法會阻塞,直到所有監聽器都完成事件處理。這種同步和單執行緒方法的一個優點是,當監聽器接收到事件時,如果交易 Context 可用,它會在發布者的交易 Context 內運作。如果需要另一種事件發布策略,例如,預設情況下的非同步事件處理,請參閱 Spring 的 ApplicationEventMulticaster 介面和 SimpleApplicationEventMulticaster 實作的 Javadoc,以取得可以應用於自訂 "applicationEventMulticaster" Bean 定義的組態選項。在這些情況下,ThreadLocal 和記錄 Context 不會針對事件處理進行傳播。有關可觀測性問題的更多資訊,請參閱 @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...
	}
}

方法簽章再次宣告它監聽的事件類型,但這次使用彈性的名稱,且無需實作特定的監聽器介面。事件類型也可以透過泛型來縮小範圍,只要實際的事件類型在其實作階層中解析您的泛型參數即可。

如果您的方法應該監聽多個事件,或者如果您想要定義它完全沒有參數,則事件類型也可以在註解本身上指定。以下範例示範如何執行此操作

  • 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 表達式中可用的事件元數據
名稱 位置 描述 範例

事件

根物件

實際的 ApplicationEvent

#root.eventevent

引數陣列

根物件

用於調用方法的引數(作為物件陣列)。

#root.argsargsargs[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 和日誌記錄內容不會針對事件處理進行傳播。 有關可觀察性問題的更多資訊,請參閱 @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 是已建立的實際實體類型。 例如,您可以建立以下監聽器定義,以僅接收 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

// 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 實作是一個 no-op 變體,以實現最小的額外負荷。 這表示預設情況下,在應用程式啟動期間不會收集任何指標。 Spring Framework 隨附一個使用 Java Flight Recorder 追蹤啟動步驟的實作:FlightRecorderApplicationStartup。 若要使用此變體,您必須在建立 ApplicationContext 後立即將其實例配置給它。

開發人員也可以使用 ApplicationStartup 基礎架構,如果他們提供自己的 AbstractApplicationContext 子類別,或者他們希望收集更精確的資料。

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(適用於名稱以 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 檔案情境的更自然替代方案 — 實際上,WAR 檔案沒有任何 HTTP 進入點,僅用於在 Jakarta EE 環境中啟動 Spring ApplicationContext

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

  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 端點,同一機器上的其他應用程式模組可以使用這些端點。