コンテナー拡張ポイント

通常、アプリケーション開発者は ApplicationContext 実装クラスをサブクラス化する必要はありません。代わりに、Spring IoC コンテナーは、特別な統合インターフェースの実装をプラグインすることにより拡張できます。次のいくつかのセクションでは、これらの統合インターフェースについて説明します。

BeanPostProcessor を使用して Bean をカスタマイズする

BeanPostProcessor インターフェースは、独自の(またはコンテナーのデフォルトをオーバーライドする)インスタンス化ロジック、依存関係解決ロジックなどを提供するために実装できるコールバックメソッドを定義します。Spring コンテナーが Bean のインスタンス化、構成、初期化を完了した後にカスタムロジックを実装する場合は、1 つ以上のカスタム BeanPostProcessor 実装をプラグインできます。

複数の BeanPostProcessor インスタンスを構成でき、order プロパティを設定することにより、これらの BeanPostProcessor インスタンスの実行順序を制御できます。このプロパティを設定できるのは、BeanPostProcessor が Ordered インターフェースを実装している場合のみです。独自の BeanPostProcessor を作成する場合は、Ordered インターフェースの実装も検討する必要があります。詳細については、BeanPostProcessor (Javadoc) および Ordered (Javadoc) インターフェースの javadoc を参照してください。BeanPostProcessor インスタンスのプログラムによる登録に関する注意も参照してください。

BeanPostProcessor インスタンスは、Bean(またはオブジェクト)インスタンスで動作します。つまり、Spring IoC コンテナーが Bean インスタンスをインスタンス化してから、BeanPostProcessor インスタンスが作業を実行します。

BeanPostProcessor インスタンスは、コンテナーごとにスコープされます。これは、コンテナー階層を使用する場合にのみ関係します。1 つのコンテナーで BeanPostProcessor を定義すると、そのコンテナー内の Bean のみが後処理されます。つまり、1 つのコンテナーで定義された Bean は、両方のコンテナーが同じ階層の一部であっても、別のコンテナーで定義された BeanPostProcessor によって後処理されません。

実際の Bean 定義(つまり、Bean を定義する設計図)を変更するには、代わりに BeanFactoryPostProcessor を使用した構成メタデータのカスタマイズで説明されている BeanFactoryPostProcessor を使用する必要があります。

org.springframework.beans.factory.config.BeanPostProcessor インターフェースは、正確に 2 つのコールバックメソッドで構成されています。このようなクラスをポストプロセッサーとしてコンテナーに登録すると、コンテナーによって作成される Bean インスタンスごとに、ポストプロセッサーはコンテナー初期化メソッド (InitializingBean.afterPropertiesSet() や公表されている init 法など) が呼び出される前と Bean 初期化コールバックの後の両方で、コンテナーからコールバックを取得します。ポストプロセッサーは、コールバックを完全に無視するなど、Bean インスタンスに対して任意のアクションを実行できます。Bean ポストプロセッサーは通常、コールバックインターフェースをチェックするか、プロキシで Bean をラップします。一部の Spring AOP インフラストラクチャクラスは、プロキシ折り返しロジックを提供するために Bean ポストプロセッサーとして実装されます。

ApplicationContext は、BeanPostProcessor インターフェースを実装する構成メタデータで定義されている Bean を自動的に検出します。ApplicationContext は、これらの Bean をポストプロセッサーとして登録し、後で Bean の作成時に呼び出すことができるようにします。Bean ポストプロセッサーは、他の Bean と同じ方法でコンテナーにデプロイできます。

構成クラスで @Bean ファクトリメソッドを使用して BeanPostProcessor を宣言する場合、ファクトリメソッドの戻り値の型は、実装クラス自体または少なくとも org.springframework.beans.factory.config.BeanPostProcessor インターフェースである必要があり、その Bean のポストプロセッサーの性質を明確に示すことに注意してください。そうしないと、ApplicationContext は完全に作成する前に型ごとに自動検出できません。コンテキスト内の他の Bean の初期化に適用するには、BeanPostProcessor を早期にインスタンス化する必要があるため、この早期型検出は重要です。

BeanPostProcessor インスタンスをプログラムで登録する
BeanPostProcessor 登録の推奨アプローチは ApplicationContext 自動検出によるものですが(前述)、addBeanPostProcessor メソッドを使用して、ConfigurableBeanFactory に対してプログラムで登録できます。これは、登録前に条件付きロジックを評価する必要がある場合、または階層内のコンテキスト間で Bean ポストプロセッサーをコピーする場合にも役立ちます。ただし、プログラムで追加された BeanPostProcessor インスタンスは Ordered インターフェースを考慮しないことに注意してください。ここで、実行の順序を決定するのは登録の順序です。また、プログラムで登録された BeanPostProcessor インスタンスは、明示的な順序に関係なく、自動検出によって登録されたインスタンスの前に常に処理されることに注意してください。
BeanPostProcessor インスタンスと AOP 自動プロキシ

BeanPostProcessor インターフェースを実装するクラスは特別であり、コンテナーによって異なる方法で処理されます。BeanPostProcessor インスタンスとそれらが直接参照する Bean は、ApplicationContext の特別な起動フェーズの一部として、起動時にインスタンス化されます。次に、すべての BeanPostProcessor インスタンスがソートされた方法で登録され、コンテナー内の他のすべての Bean に適用されます。AOP 自動プロキシは BeanPostProcessor 自体として実装されているため、BeanPostProcessor インスタンスも直接参照する Bean も自動プロキシの対象ではなく、それらに織り込まれたアスペクトはありません。

そのような Bean の場合、情報ログメッセージ Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying) が表示されます。

オートワイヤーまたは @Resource (オートワイヤーにフォールバックする可能性があります)を使用して BeanPostProcessor に Bean を接続している場合、Spring は型一致の依存関係の候補を検索するときに予期しない Bean にアクセスする可能性があるため、自動プロキシまたはその他の種類の資格がありません Bean 後処理。例: フィールドまたは setter 名が Bean の宣言された名前に直接対応せず、name 属性が使用されていない @Resource アノテーションが付けられた依存関係がある場合、Spring は他の Bean にアクセスして型ごとに一致させます。

以下の例は、ApplicationContext で BeanPostProcessor インスタンスを作成、登録、使用する方法を示しています。

サンプル: Hello World、BeanPostProcessor スタイル

この最初の例は、基本的な使用箇所を示しています。この例は、コンテナーによって作成された各 Bean の toString() メソッドを呼び出し、結果の文字列をシステムコンソールに出力するカスタム BeanPostProcessor 実装を示しています。

次のリストは、カスタム BeanPostProcessor 実装クラス定義を示しています。

  • Java

  • Kotlin

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

	// simply return the instantiated bean as-is
	public Object postProcessBeforeInitialization(Object bean, String beanName) {
		return bean; // we could potentially return any object reference here...
	}

	public Object postProcessAfterInitialization(Object bean, String beanName) {
		System.out.println("Bean '" + beanName + "' created : " + bean.toString());
		return bean;
	}
}
package scripting

import org.springframework.beans.factory.config.BeanPostProcessor

class InstantiationTracingBeanPostProcessor : BeanPostProcessor {

	// simply return the instantiated bean as-is
	override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
		return bean // we could potentially return any object reference here...
	}

	override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
		println("Bean '$beanName' created : $bean")
		return bean
	}
}

次の beans 要素は InstantiationTracingBeanPostProcessor を使用します。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:lang="http://www.springframework.org/schema/lang"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/lang
		https://www.springframework.org/schema/lang/spring-lang.xsd">

	<lang:groovy id="messenger"
			script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
		<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
	</lang:groovy>

	<!--
	when the above bean (messenger) is instantiated, this custom
	BeanPostProcessor implementation will output the fact to the system console
	-->
	<bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

InstantiationTracingBeanPostProcessor が単に定義されていることに注目してください。名前さえも持たず、Bean であるため、他の Bean と同様に依存関係を注入できます。(上記の構成では、Groovy スクリプトによってサポートされる Bean も定義しています。Spring 動的言語サポートについては、動的言語サポートというタイトルの章で詳しく説明しています。)

次の Java アプリケーションは、前述のコードと構成を実行します。

  • Java

  • Kotlin

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

	public static void main(final String[] args) throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
		Messenger messenger = ctx.getBean("messenger", Messenger.class);
		System.out.println(messenger);
	}

}
   import org.springframework.beans.factory.getBean

fun main() {
	val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
	val messenger = ctx.getBean<Messenger>("messenger")
	println(messenger)
}

上記のアプリケーションの出力は次のようになります。

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

サンプル: AutowiredAnnotationBeanPostProcessor

カスタム BeanPostProcessor 実装と組み合わせてコールバックインターフェースまたはアノテーションを使用することは、Spring IoC コンテナーを継承する一般的な手段です。例として、Spring の AutowiredAnnotationBeanPostProcessor があります。BeanPostProcessor の実装には、Spring ディストリビューションが付属しており、アノテーション付きフィールド、setter メソッド、任意の構成メソッドがオートワイヤーされます。

BeanFactoryPostProcessor を使用した構成メタデータのカスタマイズ

次に見る拡張ポイントは org.springframework.beans.factory.config.BeanFactoryPostProcessor です。このインターフェースのセマンティクスは BeanPostProcessor のセマンティクスと似ていますが、大きな違いが 1 つあります。BeanFactoryPostProcessor は Bean 構成メタデータで動作します。つまり、Spring IoC コンテナーは、BeanFactoryPostProcessor インスタンス以外の Bean をコンテナーがインスタンス化する前にBeanFactoryPostProcessor が構成メタデータを読み取り、潜在的にそれを変更できるようします。

複数の BeanFactoryPostProcessor インスタンスを構成でき、order プロパティを設定することにより、これらの BeanFactoryPostProcessor インスタンスの実行順序を制御できます。ただし、BeanFactoryPostProcessor が Ordered インターフェースを実装している場合にのみ、このプロパティを設定できます。独自の BeanFactoryPostProcessor を作成する場合は、Ordered インターフェースの実装も検討する必要があります。詳細については、BeanFactoryPostProcessor (Javadoc) および Ordered (Javadoc) インターフェースの javadoc を参照してください。

実際の Bean インスタンス(つまり、構成メタデータから作成されたオブジェクト)を変更する場合は、代わりに BeanPostProcessor (前述の BeanPostProcessor を使用して Bean をカスタマイズするで説明)を使用する必要があります。BeanFactoryPostProcessor 内で Bean インスタンスを操作することは技術的には可能ですが(たとえば、BeanFactory.getBean() を使用して)、そうすると、早すぎる Bean インスタンス化が発生し、標準のコンテナーライフサイクルに違反します。これにより、Bean 後処理のバイパスなど、マイナスの副作用が生じる可能性があります。

また、BeanFactoryPostProcessor インスタンスはコンテナーごとにスコープされます。これは、コンテナー階層を使用する場合にのみ関係します。1 つのコンテナーで BeanFactoryPostProcessor を定義すると、そのコンテナーの Bean 定義にのみ適用されます。両方のコンテナーが同じ階層の一部である場合でも、1 つのコンテナー内の Bean 定義は、別のコンテナー内の BeanFactoryPostProcessor インスタンスによって後処理されません。

Bean ファクトリポストプロセッサーは、コンテナーを定義する構成メタデータに変更を適用するために、ApplicationContext 内で宣言されたときに自動的に実行されます。Spring には、PropertyOverrideConfigurer や PropertySourcesPlaceholderConfigurer など、事前定義された多数の Bean ファクトリポストプロセッサーが含まれています。カスタム BeanFactoryPostProcessor を使用して、たとえば、カスタムプロパティエディターを登録することもできます。

ApplicationContext は、BeanFactoryPostProcessor インターフェースを実装する Bean にデプロイされた Bean を自動的に検出します。適切なタイミングで、これらの Bean を Bean ファクトリポストプロセッサーとして使用します。これらのポストプロセッサー Bean は、他の Bean と同様にデプロイできます。

BeanPostProcessor の場合と同様、通常、遅延初期化用に BeanFactoryPostProcessor を構成することは望ましくありません。他の Bean が Bean(Factory)PostProcessor を参照していない場合、そのポストプロセッサーはインスタンス化されません。遅延初期化のマークは無視され、<beans /> 要素の宣言で default-lazy-init 属性を true に設定しても、Bean(Factory)PostProcessor は即座にインスタンス化されます。

サンプル: クラス名の置換 PropertySourcesPlaceholderConfigurer

PropertySourcesPlaceholderConfigurer を使用して、標準の Java Properties 形式を使用することにより、別のファイルの Bean 定義からプロパティ値を外部化できます。そうすることで、アプリケーションをデプロイする人は、複雑な、またはコンテナーの XML 定義ファイルを変更するリスクなしに、データベース URL やパスワードなどの環境固有のプロパティをカスタマイズできます。

プレースホルダー値を持つ DataSource が定義されている次の XML ベースの構成メタデータフラグメントを検討してください。

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
	<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="${jdbc.driverClassName}"/>
	<property name="url" value="${jdbc.url}"/>
	<property name="username" value="${jdbc.username}"/>
	<property name="password" value="${jdbc.password}"/>
</bean>

この例は、外部 Properties ファイルから構成されたプロパティを示しています。実行時に、DataSource の一部のプロパティを置き換える PropertySourcesPlaceholderConfigurer がメタデータに適用されます。置換する値は、Ant、log4j、JSP EL スタイルに従う ${property-name} 形式のプレースホルダーとして指定されます。

実際の値は、標準 Java Properties 形式の別のファイルから取得されます。

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

${jdbc.username} 文字列は実行時に値 "sa" に置き換えられ、プロパティファイル内のキーと一致する他のプレースホルダー値にも同じことが当てはまります。PropertySourcesPlaceholderConfigurer は、Bean 定義のほとんどのプロパティと属性のプレースホルダーをチェックします。さらに、プレースホルダーのプレフィックスとサフィックスをカスタマイズできます。

Spring 2.5 で導入された context 名前空間を使用すると、専用の構成要素でプロパティプレースホルダーを構成できます。次の例に示すように、1 つ以上の場所を location 属性のコンマ区切りリストとして指定できます。

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertySourcesPlaceholderConfigurer は、指定した Properties ファイルのプロパティを探すだけではありません。デフォルトでは、指定されたプロパティファイルでプロパティが見つからない場合、Spring Environment プロパティおよび通常の Java System プロパティに対してチェックします。

必要なプロパティを持つ特定のアプリケーションに対して、そのような要素を 1 つだけ定義する必要があります。個別のプレースホルダー構文 (${…​}) がある限り、いくつかのプロパティプレースホルダーを構成できます。

置換に使用されるプロパティのソースをモジュール化する必要がある場合は、複数のプロパティプレースホルダーを作成しないでください。むしろ、使用するプロパティを集めた独自の PropertySourcesPlaceholderConfigurer Bean を作成する必要があります。

PropertySourcesPlaceholderConfigurer を使用してクラス名を置き換えることができます。これは、実行時に特定の実装クラスを選択する必要がある場合に役立つことがあります。次の例は、その方法を示しています。

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
	<property name="locations">
		<value>classpath:com/something/strategy.properties</value>
	</property>
	<property name="properties">
		<value>custom.strategy.class=com.something.DefaultStrategy</value>
	</property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

実行時にクラスを有効なクラスに解決できない場合、Bean の解決は、作成されようとしているときに失敗します。これは、lazy-init 以外の Bean の ApplicationContext の preInstantiateSingletons() フェーズ中です。

サンプル: PropertyOverrideConfigurer

別の Bean ファクトリポストプロセッサーである PropertyOverrideConfigurer は PropertySourcesPlaceholderConfigurer に似ていますが、後者とは異なり、元の定義には Bean プロパティのデフォルト値を設定することも、値をまったく設定しないこともできます。オーバーライドする Properties ファイルに特定の Bean プロパティのエントリがない場合、デフォルトのコンテキスト定義が使用されます。

Bean 定義はオーバーライドされることを認識していないため、オーバーライド構成が使用されていることは XML 定義ファイルからすぐにはわかりません。同じ Bean プロパティに異なる値を定義する複数の PropertyOverrideConfigurer インスタンスの場合、オーバーライドメカニズムにより、最後のインスタンスが優先されます。

プロパティファイルの構成行の形式は次のとおりです。

beanName.property=value

次のリストは、フォーマットの例を示しています。

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

このサンプルファイルは、driverClassName および url プロパティを持つ dataSource という Bean を含むコンテナー定義で使用できます。

オーバーライドされる最終プロパティを除くパスのすべてのコンポーネントがすでに null でない(おそらくコンストラクターによって初期化される)限り、複合プロパティ名もサポートされます。次の例では、tom Bean の fred プロパティの bob プロパティの sammy プロパティがスカラー値 123 に設定されます。

tom.fred.bob.sammy=123
指定されたオーバーライド値は常にリテラル値です。それらは Bean 参照に変換されません。この規則は、XML Bean 定義の元の値が Bean 参照を指定している場合にも適用されます。

Spring 2.5 で導入された context 名前空間を使用すると、次の例に示すように、専用の構成要素でプロパティのオーバーライドを構成できます。

<context:property-override location="classpath:override.properties"/>

FactoryBean を使用したインスタンス化ロジックのカスタマイズ

自身がファクトリであるオブジェクトに対して org.springframework.beans.factory.FactoryBean インターフェースを実装できます。

FactoryBean インターフェースは、Spring IoC コンテナーのインスタンス化ロジックへのプラグインのポイントです。(潜在的に)冗長な量の XML ではなく Java でより適切に表現される複雑な初期化コードがある場合、独自の FactoryBean を作成し、そのクラス内に複雑な初期化を記述してから、カスタム FactoryBean をコンテナーにプラグインできます。

FactoryBean<T> インターフェースには 3 つの方法があります。

  • T getObject(): このファクトリが作成するオブジェクトのインスタンスを返します。このファクトリがシングルトンを返すかプロトタイプを返すかに応じて、インスタンスを共有できます。

  • boolean isSingleton(): この FactoryBean がシングルトンを返す場合は true を返し、それ以外の場合は false を返します。このメソッドのデフォルトの実装は true を返します。

  • Class<?> getObjectType(): 型が事前にわからない場合、getObject() メソッドまたは null によって返されたオブジェクト型を返します。

FactoryBean の概念とインターフェースは、Spring Framework 内のさまざまな場所で使用されています。FactoryBean インターフェースの 50 以上の実装には、Spring 自体が付属しています。

コンテナーが生成する Bean ではなく実際の FactoryBean インスタンス自体をコンテナーに要求する必要がある場合は、ApplicationContext の getBean() メソッドを呼び出すときに、Bean の id の前にアンパサンド記号(&)を付けます。myBean の id を持つ特定の FactoryBean の場合、コンテナーで getBean("myBean") を呼び出すと、FactoryBean の積が返されますが、getBean("&myBean") を呼び出すと、FactoryBean インスタンス自体が返されます。