ApplicationContext の追加機能

「入門」の章で説明したように、org.springframework.beans.factory パッケージは、プログラムによる方法など、Bean を管理および操作するための基本機能を提供します。org.springframework.context パッケージは、他のインターフェースを継承して、よりアプリケーションフレームワーク指向のスタイルで追加機能を提供することに加えて、BeanFactory インターフェースを継承する ApplicationContext (Javadoc) インターフェースを追加します。多くの人は、ApplicationContext を完全に宣言的な方法で使用しており、プログラムで作成することさえせず、代わりに ContextLoader などのサポートクラスを利用して、Jakarta EE Web アプリケーションの通常の起動プロセスの一部として ApplicationContext を自動的にインスタンス化します。

よりフレームワーク指向のスタイルで BeanFactory 機能を強化するために、コンテキストパッケージは次の機能も提供します。

  • MessageSource インターフェースを介した i18n スタイルのメッセージへのアクセス。

  • ResourceLoader インターフェースを介した URL やファイルなどのリソースへのアクセス。

  • イベントの公開。つまり、ApplicationEventPublisher インターフェースを使用した、ApplicationListener インターフェースを実装する Bean へ。

  • HierarchicalBeanFactory インターフェースを介して、アプリケーションの Web レイヤーなどの 1 つの特定のレイヤーにそれぞれを集中させる、複数の(階層)コンテキストのロード。

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): 基本的に前の方法と同じですが、1 つの違いがあります: デフォルトのメッセージは指定できません。メッセージが見つからない場合、NoSuchMessageException がスローされます。

  • String getMessage(MessageSourceResolvable resolvable, Locale locale): 上記のメソッドで使用されるすべてのプロパティは、このメソッドで使用できる MessageSourceResolvable という名前のクラスでもラップされます。

ApplicationContext がロードされると、コンテキストで定義された MessageSource Bean を自動的に検索します。Bean の名前は messageSource でなければなりません。そのような Bean が見つかった場合、前述のメソッドへのすべての呼び出しはメッセージソースに委譲されます。メッセージソースが見つからない場合、ApplicationContext は同じ名前の Bean を含む親を見つけようとします。存在する場合、その Bean を MessageSource として使用します。ApplicationContext がメッセージのソースを見つけられない場合、空の DelegatingMessageSource がインスタンス化されて、上記で定義されたメソッドへの呼び出しを受け入れることができます。

Spring は、3 つの 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 という 3 つのリソースバンドルが定義されていることを前提としています。メッセージを解決するリクエストはすべて、ResourceBundle オブジェクトを介してメッセージを解決する JDK 標準の方法で処理されます。例の目的上、上記の 2 つのリソースバンドルファイルの内容が次のとおりであると想定します。

# 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 プロパティに渡される 3 つのファイルは、クラスパスのルートにファイルとして存在し、それぞれ 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 への参照を取得することもできます。MessageSourceAware インターフェースを実装する ApplicationContext で定義されている Bean には、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 が提供する標準イベントを示します。

表 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

HTTP リクエストが処理されたことをすべての Bean に通知する Web 固有のイベント。このイベントは、リクエストが完了すると公開されます。このイベントは、Spring の DispatcherServlet を使用する Web アプリケーションにのみ適用されます。

ServletRequestHandledEvent

サーブレット固有のコンテキスト情報を追加する RequestHandledEvent のサブクラス。

独自のカスタムイベントを作成して公開することもできます。次の例は、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() メソッドは、すべてのリスナーがイベントの処理を完了するまでブロックします。この同期およびシングルスレッドアプローチの利点の 1 つは、リスナーがイベントを受信すると、トランザクションコンテキストが使用可能な場合は、パブリッシャーのトランザクションコンテキスト内で動作する点です。イベント発行の別の戦略 (デフォルトで非同期イベント処理など) が必要になった場合は、カスタム "applicationEventMulticaster" Bean 定義に適用できる構成オプションについて、Spring の ApplicationEventMulticaster (Javadoc) インターフェースおよび SimpleApplicationEventMulticaster (Javadoc) 実装の javadoc を参照してください。これらの場合、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...
	}
}

メソッドシグネチャーは、リッスンするイベント型を再度宣言しますが、今回は、柔軟な名前を使用して、特定のリスナーインターフェースを実装しません。実際のイベント型が実装階層内のジェネリクスパラメーターを解決する限り、ジェネリクスを介してイベント型を絞り込むこともできます。

メソッドが複数のイベントをリッスンする必要がある場合、またはパラメーターをまったく指定せずに定義する場合、アノテーション自体でイベント型を指定することもできます。次の例は、その方法を示しています。

  • 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 or 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 (Javadoc) を参照してください。

  • 非同期イベントリスナーメソッドは、値を返すことで後続のイベントを発行できません。処理の結果として別のイベントを公開する必要がある場合は、ApplicationEventPublisher (Javadoc) を挿入してイベントを手動で公開します。

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

一般的なイベント

ジェネリクスを使用して、イベントの構造をさらに定義することもできます。T が作成された実際のエンティティの型である EntityCreatedEvent<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 を構成して、特殊なコールバックインターフェース ResourceLoaderAware を実装し、初期化時に自動的にコールバックされ、アプリケーションコンテキスト自体が ResourceLoader として渡されます。Resource 型のプロパティを公開して、静的リソースへのアクセスに使用することもできます。それらは他のプロパティと同様にそこに注入されます。これらの Resource プロパティを単純な String パスとして指定し、Bean がデプロイされるときにそれらのテキスト文字列から実際の Resource オブジェクトへの自動変換に依存することができます。

ApplicationContext コンストラクターに提供されるロケーションパスは、実際にはリソース文字列であり、単純な形式では、特定のコンテキスト実装に従って適切に処理されます。たとえば、ClassPathXmlApplicationContext は単純なロケーションパスをクラスパスロケーションとして扱います。また、実際のコンテキスト型に関係なく、クラスパスまたは URL から定義を強制的にロードするために、特別なプレフィックスを持つロケーションパス(リソース文字列)を使用できます。

アプリケーションの起動追跡

ApplicationContext は、Spring アプリケーションのライフサイクルを管理し、コンポーネントに関する豊富なプログラミングモデルを提供します。その結果、複雑なアプリケーションは、同様に複雑なコンポーネントグラフと起動フェーズを持つことができます。

特定のメトリクスを使用してアプリケーションの起動手順を追跡すると、起動フェーズで時間が費やされている場所を理解できますが、コンテキストライフサイクル全体をよりよく理解する方法としても使用できます。

AbstractApplicationContext (およびそのサブクラス)には、さまざまな起動フェーズに関する StartupStep データを収集する ApplicationStartup が装備されています。

  • アプリケーションコンテキストのライフサイクル (基本パッケージのスキャン、構成クラスの管理)

  • 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 に構成する必要があります。

開発者は、独自の AbstractApplicationContext サブクラスを提供している場合、またはより正確なデータを収集したい場合にも、ApplicationStartup インフラストラクチャを使用できます。

ApplicationStartup は、アプリケーションの起動時およびコアコンテナーにのみ使用することを目的としています。これは、Java プロファイラーや Micrometer (英語) のようなメトリクスライブラリに代わるものではありません。

カスタム StartupStep の収集を開始するには、コンポーネントは、アプリケーションコンテキストから ApplicationStartup インスタンスを直接取得するか、コンポーネントに ApplicationStartupAware を実装させるか、任意のインジェクションポイントで ApplicationStartup 型を要求します。

開発者は、カスタム起動ステップを作成するときに "spring.*" 名前空間を使用しないでください。この名前空間は、内部 Spring の使用のために予約されており、変更される可能性があります。

Web アプリケーション用の便利な ApplicationContext インスタンス化

ApplicationContext インスタンスを宣言的に作成するには、たとえば ContextLoader を使用します。もちろん、ApplicationContext 実装の 1 つを使用して、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 File としてデプロイする

Spring ApplicationContext を RAR ファイルとしてデプロイし、コンテキストとそれに必要なすべての Bean クラスおよびライブラリ JAR を Jakarta EE RAR デプロイユニットにカプセル化することができます。これは、スタンドアロン ApplicationContext (Jakarta EE 環境でのみホストされる)をブートストラップして、Jakarta EE サーバー機能にアクセスできるようにすることと同じです。RAR デプロイは、ヘッドレス WAR ファイルをデプロイするシナリオのより自然な代替手段です。実際には、Jakarta EE 環境で Spring ApplicationContext をブートストラップするためにのみ使用される HTTP エントリポイントのない WAR ファイルです。

RAR デプロイは、HTTP エントリポイントを必要とせず、メッセージエンドポイントとスケジュールされたジョブのみで構成されるアプリケーションコンテキストに最適です。このようなコンテキストの Bean は、JTA トランザクションマネージャー、JNDI バインド JDBC DataSource インスタンス、JMS ConnectionFactory インスタンスなどのアプリケーションサーバーリソースを使用できます。また、プラットフォームの JMX サーバーに登録できます。すべて Spring の標準トランザクション管理と JNDI および JMX サポート機能を使用します。アプリケーションコンポーネントは、Spring の TaskExecutor 抽象化を通じてアプリケーションサーバーの JCA WorkManager と対話することもできます。

RAR デプロイに関連する構成の詳細については、SpringContextResourceAdapter (Javadoc) クラスの javadoc を参照してください。

Jakarta EE RAR ファイルとしての Spring ApplicationContext の単純なデプロイの場合:

  1. すべてのアプリケーションクラスを RAR ファイル(ファイル拡張子が異なる標準の JAR ファイル)にパッケージ化します。

  2. 必要なすべてのライブラリ JAR を RAR アーカイブのルートに追加します。

  3. META-INF/ra.xml デプロイ記述子(SpringContextResourceAdapterjavadoc に示されている)と対応する Spring XML Bean 定義ファイル(通常は META-INF/applicationContext.xml)を追加します。

  4. 結果の RAR ファイルをアプリケーションサーバーのデプロイディレクトリにドロップします。

このような RAR デプロイユニットは通常、自己完結型です。コンポーネントを外部に公開することはなく、同じアプリケーションの他のモジュールにも公開しません。RAR ベースの ApplicationContext との相互作用は、通常、他のモジュールと共有する JMS 宛先を介して行われます。また、RAR ベースの ApplicationContext は、たとえば、一部のジョブをスケジュールしたり、ファイルシステム内の新しいファイルに反応したりすることもあります(または同様のもの)。外部からの同期アクセスを許可する必要がある場合、(たとえば)RMI エンドポイントをエクスポートできます。これは、同じマシン上の他のアプリケーションモジュールによって使用される可能性があります。