リファレンスドキュメントのこのパートは、Spring Framework に不可欠なすべてのテクノロジーを網羅しています。

これらの中で最も重要なのは、Spring Framework の Inversion of Control(IoC: 制御の反転)コンテナーです。Spring Framework の IoC コンテナーを徹底的に扱った後、Spring のアスペクト指向プログラミング(AOP)テクノロジーを包括的な説明が続きます。Spring Framework には独自の AOP フレームワークがあり、概念的に理解しやすく、Java エンタープライズプログラミングにおける AOP 要件の 80% のスイートスポットにうまく対処します。

Spring の AspectJ との統合(現在、機能面で最も豊富で、Java エンタープライズ空間で最も成熟した AOP 実装)も提供されています。

1. IoC コンテナー

この章では、Spring の制御の反転(IoC)コンテナーについて説明します。

1.1. Spring IoC コンテナーと Bean の概要

この章では、制御の反転(IoC)原則の Spring Framework 実装について説明します。IoC は、依存性注入(DI)とも呼ばれます。これは、コンストラクター引数、ファクトリメソッドへの引数、ファクトリメソッドが構築または返された後にオブジェクトインスタンスに設定されるプロパティを通じてのみ、オブジェクトが依存関係(つまり、操作する他のオブジェクト)を定義するプロセスです。コンテナーは、Bean を作成するときにそれらの依存関係を注入します。このプロセスは、基本的に、クラスの直接構築または Service Locator パターンなどのメカニズムを使用して、Bean 自身がその依存関係のインスタンス化や場所を制御することの逆(そのため、Inversion of Control という名前)です。

org.springframework.beans および org.springframework.context パッケージは、Spring Framework の IoC コンテナーの基盤です。BeanFactory (Javadoc) インターフェースは、あらゆる型のオブジェクトを管理できる高度な構成メカニズムを提供します。ApplicationContext (Javadoc) は BeanFactory のサブインターフェースです。以下を追加します。

  • Spring の AOP 機能との簡単な統合

  • メッセージリソースの処理 (国際化で使用するため)

  • イベント公開

  • Web アプリケーションで使用する WebApplicationContext などのアプリケーション層固有のコンテキスト。

つまり、BeanFactory は構成フレームワークと基本機能を提供し、ApplicationContext はエンタープライズ固有の機能をさらに追加します。ApplicationContext は BeanFactory の完全なスーパーセットであり、この章で Spring の IoC コンテナーの説明でのみ使用されます。ApplicationContext, の代わりに BeanFactory を使用する方法の詳細については、BeanFactory を参照してください。

Spring では、アプリケーションのバックボーンを形成し、Spring IoC コンテナーによって管理されるオブジェクトを Bean と呼びます。Bean は、Spring IoC コンテナーによってインスタンス化、アセンブル、管理されるオブジェクトです。それ以外の場合、Bean はアプリケーションの多くのオブジェクトの 1 つにすぎません。Bean とそれらの間の依存関係は、コンテナーが使用する構成メタデータに反映されます。

1.2. コンテナーの概要

org.springframework.context.ApplicationContext インターフェースは Spring IoC コンテナーを表し、Bean のインスタンス化、構成、組み立てを担当します。コンテナーは、構成メタデータを読み取ることにより、どのオブジェクトをインスタンス化、構成、アセンブルするかに関する指示を取得します。構成メタデータは、XML、Java アノテーション、Java コードで表されます。アプリケーションを構成するオブジェクトと、それらのオブジェクト間の豊富な相互依存関係を表現できます。

ApplicationContext インターフェースのいくつかの実装が Spring で提供されます。スタンドアロンアプリケーションでは、ClassPathXmlApplicationContext (Javadoc) または FileSystemXmlApplicationContext (Javadoc) のインスタンスを作成するのが一般的です。XML は構成メタデータを定義するための従来の形式でしたが、これらの追加のメタデータ形式のサポートを宣言的に有効にするために少量の XML 構成を提供することにより、メタデータ形式として Java アノテーションまたはコードを使用するようにコンテナーに指示できます。

ほとんどのアプリケーションシナリオでは、Spring IoC コンテナーの 1 つ以上のインスタンスをインスタンス化するために明示的なユーザーコードは必要ありません。例: Web アプリケーションのシナリオでは、通常、アプリケーションの web.xml ファイル内のボイラープレート Web 記述子 XML の単純な 8 行(またはそれ以上)で十分です(Web アプリケーション用の便利な ApplicationContext インスタンス化を参照)。Pleiades All in One (JDK, STS, Lombok 付属) または Eclipse 用 Spring Tools (英語) (Eclipse を使用した開発環境)を使用している場合、数回のマウスクリックまたはキーストロークでこの定型的な構成を簡単に作成できます。

次の図は、Spring の機能の概要を示しています。アプリケーションクラスは構成メタデータと組み合わされ、ApplicationContext が作成および初期化された後、完全に構成された実行可能なシステムまたはアプリケーションができます。

container magic
図 1: Spring IoC コンテナー

1.2.1. 構成メタデータ

前の図に示すように、Spring IoC コンテナーは、構成メタデータの形式を使用します。この構成メタデータは、アプリケーション開発者として、Spring コンテナーにアプリケーション内のオブジェクトのインスタンス化、構成、アセンブルを指示する方法を表します。

構成メタデータは従来、シンプルで直感的な XML 形式で提供されます。これは、この章のほとんどで、Spring IoC コンテナーの主要な概念と機能を伝えるために使用されます。

XML ベースのメタデータは、構成メタデータの唯一の許可された形式ではありません。Spring IoC コンテナー自体は、この構成メタデータが実際に書き込まれる形式から完全に切り離されています。最近では、多くの開発者が Spring アプリケーションに Java ベースの構成を選択しています。

Spring コンテナーで他の形式のメタデータを使用する方法については、以下を参照してください。

Spring 構成は、コンテナーが管理する必要のある少なくとも 1 つ、通常は複数の Bean 定義で構成されます。XML ベースの構成メタデータは、これらの Bean を最上位の <beans/> 要素内の <bean/> 要素として構成します。Java 構成は通常、@Configuration クラス内で @Bean アノテーション付きメソッドを使用します。

これらの Bean 定義は、アプリケーションを構成する実際のオブジェクトに対応しています。通常、サービスレイヤーオブジェクト、データアクセスオブジェクト(DAO)、Struts Action インスタンスなどのプレゼンテーションオブジェクト、Hibernate SessionFactories などのインフラストラクチャオブジェクト、JMS Queues などを定義します。通常、ドメインオブジェクトを作成およびロードするのは通常 DAO とビジネスロジックの責任であるため、コンテナー内で詳細なドメインオブジェクトを構成することはありません。ただし、Spring と AspectJ の統合を使用して、IoC コンテナーの制御外で作成されたオブジェクトを構成できます。AspectJ を使用して Spring を使用してドメインオブジェクトを依存性注入するを参照してください。

次の例は、XML ベースの構成メタデータの基本構造を示しています。

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

    <bean id="..." class="..."> (1) (2)
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>
1id 属性は、個々の Bean 定義を識別する文字列です。
2class 属性は、Bean の型を定義し、完全修飾クラス名を使用します。

id 属性の値は、共同作業オブジェクトを指します。この例では、共同作業オブジェクトを参照するための XML は示されていません。詳細については、依存関係を参照してください。

1.2.2. コンテナーのインスタンス化

ApplicationContext コンストラクターに提供されるロケーションパスは、コンテナーがローカルファイルシステム、Java CLASSPATH などのさまざまな外部リソースから構成メタデータをロードできるようにするリソース文字列です。

Java
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
Kotlin
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")

Spring の IoC コンテナーについて学習した後、Spring の Resource 抽象化(リソースで説明)について詳しく知りたい場合があります。これは、URI 構文で定義された場所から InputStream を読み取るための便利なメカニズムを提供します。特に、アプリケーションコンテキストとリソースパスに従って、Resource パスはアプリケーションコンテキストの構築に使用されます。

次の例は、サービスレイヤーオブジェクト (services.xml) 構成ファイルを示しています。

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

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

次の例は、データアクセスオブジェクト daos.xml ファイルを示しています。

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

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

前の例では、サービスレイヤーは PetStoreServiceImpl クラスと、型 JpaAccountDao および JpaItemDao の 2 つのデータアクセスオブジェクト(JPA オブジェクトリレーショナルマッピング標準に基づく)で構成されています。property name エレメントは JavaBean プロパティの名前を参照し、ref エレメントは別の Bean 定義の名前を参照します。id 要素と ref 要素の間のこのリンケージは、コラボレーションするオブジェクト間の依存関係を表します。オブジェクトの依存関係の構成の詳細については、依存関係を参照してください。

XML ベースの構成メタデータの作成

Bean 定義が複数の XML ファイルにまたがっていると便利です。多くの場合、個々の XML 構成ファイルは、アーキテクチャ内の論理層またはモジュールを表します。

アプリケーションコンテキストコンストラクターを使用して、これらすべての XML フラグメントから Bean 定義をロードできます。前のセクションで示したように、このコンストラクターは複数の Resource の場所を受け取ります。あるいは、<import/> 要素を 1 つ以上使用して、別のファイルから Bean 定義をロードします。次の例は、その方法を示しています。

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

前の例では、外部 Bean 定義は、services.xmlmessageSource.xmlthemeSource.xml の 3 つのファイルからロードされます。すべてのロケーションパスは、インポートを実行する定義ファイルに関連しているため、services.xml はインポートを実行するファイルと同じディレクトリまたはクラスパスの場所にある必要があり、messageSource.xml および themeSource.xml はインポートファイルの場所の resources の場所にある必要があります。ご覧のとおり、先頭のスラッシュは無視されます。ただし、これらのパスは相対的なものであるため、スラッシュをまったく使用しない方が適切です。Spring スキーマによれば、インポートされるファイルの内容は、最上位の <beans/> 要素を含め、有効な XML Bean 定義である必要があります。

相対パス "../" を使用して親ディレクトリのファイルを参照することは可能ですが、推奨されません。これを行うと、現在のアプリケーションの外部にあるファイルに依存関係が作成されます。特に、この参照は classpath: URL(たとえば classpath:../services.xml)には推奨されません。この場合、ランタイム解決プロセスは「最も近い」クラスパスルートを選択し、その親ディレクトリを調べます。クラスパス構成の変更により、別の誤ったディレクトリが選択される場合があります。

相対パスの代わりに、たとえば file:C:/config/services.xml または classpath:/config/services.xml のような完全修飾リソースの場所を常に使用できます。ただし、アプリケーションの構成を特定の絶対ロケーションに結合していることに注意してください。一般に、このような絶対的な場所に対しては、たとえば、実行時に JVM システムプロパティに対して解決される "${ … }" プレースホルダーを介した間接性を維持することが望ましいです。

ネームスペース自体がインポートディレクティブ機能を提供します。context および util 名前空間など、Spring が提供する XML 名前空間の選択では、プレーンな Bean 定義を超えるさらなる構成機能を利用できます。

Groovy Bean 定義 DSL

外部化された設定メタデータのさらなる例として、Grails フレームワークで知られているように、Bean 定義は Spring の Groovy Bean Definition DSL でも表現できます。通常、このような設定は、次の例に示す構造を持つ ".groovy" ファイルに存在します。

beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}

この構成スタイルは、XML Bean 定義とほぼ同等であり、Spring の XML 構成名前空間もサポートしています。また、importBeans ディレクティブを介して XML Bean 定義ファイルをインポートすることもできます。

1.2.3. コンテナーの使用

ApplicationContext は、さまざまな Bean とその依存関係のレジストリを維持することができる高度なファクトリのインターフェースです。メソッド T getBean(String name, Class<T> requiredType) を使用すると、Bean のインスタンスを取得できます。

ApplicationContext では、次の例に示すように、Bean 定義を読み取ってそれらにアクセスできます。

Java
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();
Kotlin
import org.springframework.beans.factory.getBean

// create and configure beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")

// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")

// use configured instance
var userList = service.getUsernameList()

Groovy 構成では、ブートストラップは非常に似ています。Groovy 対応の異なるコンテキスト実装クラスがあります(ただし、XML Bean 定義も理解します)。次の例は、Groovy 構成を示しています。

Java
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
Kotlin
val context = GenericGroovyApplicationContext("services.groovy", "daos.groovy")

最も柔軟なバリアントは、次の例に示すように、リーダーデリゲートと組み合わせた GenericApplicationContext です。たとえば、XML ファイルの XmlBeanDefinitionReader です。

Java
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
Kotlin
val context = GenericApplicationContext()
XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml")
context.refresh()

次の例に示すように、Groovy ファイルに GroovyBeanDefinitionReader を使用することもできます。

Java
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
Kotlin
val context = GenericApplicationContext()
GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy")
context.refresh()

同じリーダーデリゲートを同じ ApplicationContext で組み合わせて、さまざまな構成ソースから Bean 定義を読み取ることができます。

その後、getBean を使用して、Bean のインスタンスを取得できます。ApplicationContext インターフェースには、Bean を取得するためのその他のメソッドがいくつかありますが、理想的には、アプリケーションコードで Bean を使用しないでください。実際、アプリケーションコードには getBean() メソッドをまったく呼び出さないようにし、Spring API にまったく依存しないようにします。例: Spring の Web フレームワークとの統合は、コントローラーや JSF 管理の Bean などのさまざまな Web フレームワークコンポーネントへの依存性注入を提供し、メタデータ(オートワイヤーアノテーションなど)を通じて特定の Bean への依存性を宣言できます。

1.3. Bean の概要

Spring IoC コンテナーは、1 つ以上の Bean を管理します。これらの Bean は、コンテナーに提供する構成メタデータを使用して作成されます(たとえば、XML <bean/> 定義の形式で)。

コンテナー自体の中では、これらの Bean 定義は BeanDefinition オブジェクトとして表され、(他の情報とともに)次のメタデータを含んでいます:

  • パッケージ修飾クラス名: 通常、定義されている Bean の実際の実装クラス。

  • Bean 動作構成要素。Bean がコンテナー内でどのように動作するかを指定します(スコープ、ライフサイクルコールバックなど)。

  • Bean が機能するために必要な他の Bean への参照。これらの参照は、コラボレーターまたは依存関係とも呼ばれます。

  • 新しく作成されたオブジェクトに設定する他の構成設定。たとえば、プールのサイズ制限や、接続プールを管理する Bean で使用する接続数など。

このメタデータは、各 Bean 定義を構成する一連のプロパティに変換されます。次の表で、これらのプロパティについて説明します。

表 1: Bean 定義
プロパティ 説明…

クラス

Bean のインスタンス化

名前

Bean の命名

スコープ

Bean スコープ

コンストラクター引数

依存性注入

プロパティ

依存性注入

オートワイヤーモード

オートワイヤーのコラボレーター

遅延初期化モード

遅延初期化された Bean

初期化メソッド

初期化コールバック

破棄メソッド

破棄コールバック

特定の Bean を作成する方法に関する情報を含む Bean 定義に加えて、ApplicationContext 実装では、コンテナーの外部で(ユーザーによって)作成される既存のオブジェクトの登録も許可します。これは、BeanFactory DefaultListableBeanFactory 実装を返す getBeanFactory() メソッドを介して ApplicationContext の BeanFactory にアクセスすることにより行われます。DefaultListableBeanFactory は、registerSingleton(..) および registerBeanDefinition(..) メソッドを介してこの登録をサポートします。ただし、一般的なアプリケーションは、通常の Bean 定義メタデータを介して定義された Bean のみで機能します。

Bean のメタデータと手動で提供されたシングルトンインスタンスは、コンテナーがオートワイヤーやその他のイントロスペクションの段階で適切に判断できるように、できるだけ早く登録する必要があります。既存のメタデータおよび既存のシングルトンインスタンスの上書きはある程度サポートされていますが、実行時の新規 Bean の登録 (ファクトリへのライブアクセスと同時に) は公式にはサポートされておらず、同時アクセス例外、Bean コンテナーでの矛盾した状態、その両方につながる可能性があります。

1.3.1. Bean の命名

すべての Bean には 1 つ以上の識別子があります。これらの識別子は、Bean をホストするコンテナー内で一意である必要があります。Bean には通常 1 つの識別子しかありません。ただし、複数必要な場合は、余分なものをエイリアスと見なすことができます。

XML ベースの構成メタデータでは、id 属性、name 属性、またはその両方を使用して、Bean 識別子を指定します。id 属性を使用すると、正確に 1 つの ID を指定できます。通常、これらの名前は英数字 ('myBean'、'someService' など) ですが、特殊文字も含めることができます。Bean に他のエイリアスを導入する場合は、name 属性で、コンマ (,)、セミコロン (;)、または空白で区切って指定することもできます。歴史的な注意点として、Spring 3.1 より前のバージョンでは、id 属性は xsd:ID 型として定義され、使用可能な文字が制限されていました。3.1 以降では、xsd:string 型として定義されています。Bean id の一意性は、XML パーサーでは強制されなくなりましたが、コンテナーによって引き続き強制されることに注意してください。

Bean に name または id を指定する必要はありません。name または id を明示的に指定しない場合、コンテナーはその Bean に一意の名前を生成します。ただし、ref 要素またはサービスロケータースタイルの検索を使用して、その Bean を名前で参照する場合は、名前を指定する必要があります。名前を指定しない理由は、内部 Beanオートワイヤリングコラボレーターの使用に関連しています。

Bean の命名規則

規則は、Bean の命名時にインスタンスフィールド名に標準の Java 規則を使用することです。つまり、Bean 名は小文字で始まり、そこからキャメルケースになります。そのような名前の例には、accountManageraccountServiceuserDaologinController などがあります。

Bean に一貫して名前を付けると、構成が読みやすくなり、理解しやすくなります。また、Spring AOP を使用すると、名前で関連付けられた一連の Bean にアドバイスを適用するときに非常に役立ちます。

クラスパスでコンポーネントをスキャンすると、Spring は前述のルールに従って、名前のないコンポーネントの Bean 名を生成します。基本的には、単純なクラス名を取得し、その最初の文字を小文字に変換します。ただし、複数の文字があり、最初の文字と 2 番目の文字の両方が大文字である(異常な)特殊なケースでは、元の大文字と小文字が保持されます。これらは、java.beans.Introspector.decapitalize (Spring がここで使用する)で定義されているものと同じルールです。
Bean 定義外の Bean のエイリアス

Bean 定義自体では、id 属性で指定された最大 1 つの名前と name 属性の他の任意の数の組み合わせを使用して、Bean に複数の名前を指定できます。これらの名前は、同じ Bean と同等のエイリアスにすることができ、アプリケーション内の各コンポーネントがそのコンポーネント自体に固有の Bean 名を使用して共通の依存関係を参照できるようにするなど、いくつかの状況で役立ちます。

ただし、Bean が実際に定義されているすべてのエイリアスを指定することは必ずしも適切ではありません。他の場所で定義されている Bean のエイリアスを導入することが望ましい場合があります。これは、構成が各サブシステム間で分割され、各サブシステムが独自のオブジェクト定義のセットを持つ大規模なシステムの場合に一般的です。XML ベースの構成メタデータでは、<alias/> 要素を使用してこれを実現できます。次の例は、その方法を示しています。

<alias name="fromName" alias="toName"/>

この場合、fromName という名前の Bean(同じコンテナー内)は、この別名定義の使用後、toName と呼ばれることもあります。

例: サブシステム A の構成メタデータは、subsystemA-dataSource という名前の DataSource を参照する場合があります。サブシステム B の構成メタデータは、subsystemB-dataSource という名前の DataSource を参照する場合があります。これら両方のサブシステムを使用するメインアプリケーションを作成する場合、メインアプリケーションは myApp-dataSource という名前で DataSource を参照します。3 つの名前すべてが同じオブジェクトを参照するようにするには、次のエイリアス定義を構成メタデータに追加できます。

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

これで、各コンポーネントとメインアプリケーションは、一意で他の定義と競合しないことが保証された名前 (実質的に名前空間を作成) を使用して dataSource を参照できますが、参照先は同じ Bean になります。

Java 構成

Java 構成を使用する場合、@Bean アノテーションを使用してエイリアスを提供できます。詳細については、@Bean アノテーションの使用を参照してください。

1.3.2. Bean のインスタンス化

Bean 定義は、本質的に 1 つ以上のオブジェクトを作成するためのレシピです。コンテナーは、要求されると名前付き Bean のレシピを調べ、その Bean 定義によってカプセル化された構成メタデータを使用して、実際のオブジェクトを作成(または取得)します。

XML ベースの構成メタデータを使用する場合、<bean/> 要素の class 属性でインスタンス化されるオブジェクトの型(またはクラス)を指定します。通常、この class 属性(内部的には BeanDefinition インスタンスの Class プロパティ)は必須です。(例外については、インスタンスファクトリメソッドを使用したインスタンス化および Bean 定義の継承を参照してください) Class プロパティは、次の 2 つの方法のいずれかで使用できます。

  • 通常、new 演算子を使用した Java コードと多少同等の、コンストラクターをリフレクティブに呼び出すことにより、コンテナー自体が直接 Bean を作成する場合に構築される Bean クラスを指定します。

  • オブジェクトを作成するために呼び出される static ファクトリメソッドを含む実際のクラスを指定するには、あまり一般的ではないが、コンテナーがクラスで static ファクトリメソッドを呼び出して Bean を作成します。static ファクトリメソッドの呼び出しから返されるオブジェクト型は、同じクラスまたは完全に別のクラスです。

ネストされたクラス名

ネストされたクラスの Bean 定義を構成する場合は、ネストされたクラスのバイナリ名またはソース名のいずれかを使用できます。

例: com.example パッケージに SomeThing というクラスがあり、この SomeThing クラスに OtherThing という static ネストクラスがある場合、ドル記号($)またはドット(.)で区切ることができます。Bean 定義の class 属性の値は、com.example.SomeThing$OtherThing または com.example.SomeThing.OtherThing になります。

コンストラクターによるインスタンス化

コンストラクターアプローチによって Bean を作成すると、すべての通常クラスが Spring で使用でき、互換性があります。つまり、開発中のクラスは、特定のインターフェースを実装したり、特定の方法でコーディングしたりする必要はありません。Bean クラスを指定するだけで十分です。ただし、その特定の Bean に使用する IoC の型によっては、デフォルト(空の)コンストラクターが必要になる場合があります。

Spring IoC コンテナーは、管理したいほぼすべてのクラスを管理できます。真の JavaBeans の管理に限定されません。ほとんどの Spring ユーザーは、デフォルト(引数なし)コンストラクターと、コンテナー内のプロパティをモデルにした適切な setter および getter のみを備えた実際の JavaBeans を好みます。コンテナーには、よりエキゾチックな非 Bean スタイルのクラスを含めることもできます。たとえば、JavaBean 仕様に絶対に準拠していないレガシー接続プールを使用する必要がある場合、Spring もそれを管理できます。

XML ベースの構成メタデータを使用すると、Bean クラスを次のように指定できます。

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

コンストラクターに引数を提供し(必要な場合)、オブジェクトの構築後にオブジェクトインスタンスプロパティを設定するメカニズムの詳細については、依存関係の注入を参照してください。

静的ファクトリメソッドを使用したインスタンス化

静的ファクトリメソッドを使用して作成する Bean を定義する場合、class 属性を使用して static ファクトリメソッドを含むクラスを指定し、factory-method という名前の属性を使用してファクトリメソッド自体の名前を指定します。このメソッドを(後述のオプションの引数を使用して)呼び出し、ライブオブジェクトを返すことができるはずです。ライブオブジェクトは、その後、コンストラクターを介して作成されたかのように扱われます。そのような Bean 定義の 1 つの用途は、レガシーコードで static ファクトリを呼び出すことです。

次の Bean 定義は、ファクトリメソッドを呼び出して Bean を作成することを指定しています。定義では、返されるオブジェクトの型(クラス)は指定せず、ファクトリメソッドを含むクラスのみを指定します。この例では、createInstance() メソッドは静的メソッドである必要があります。次の例は、ファクトリメソッドを指定する方法を示しています。

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

次の例は、前述の Bean 定義で機能するクラスを示しています。

Java
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}
Kotlin
class ClientService private constructor() {
    companion object {
        private val clientService = ClientService()
        fun createInstance() = clientService
    }
}

ファクトリメソッドに(オプションの)引数を提供し、オブジェクトがファクトリから返された後にオブジェクトインスタンスプロパティを設定するメカニズムの詳細については、依存関係と構成の詳細を参照してください。

インスタンスファクトリメソッドを使用したインスタンス化

静的ファクトリメソッドによるインスタンス化と同様に、インスタンスファクトリメソッドによるインスタンス化では、コンテナーから既存の Bean の非静的メソッドを呼び出して、新しい Bean を作成します。このメカニズムを使用するには、class 属性を空のままにし、factory-bean 属性で、オブジェクトを作成するために呼び出されるインスタンスメソッドを含む、現在の (または親または祖先) コンテナー内の Bean の名前を指定します。ファクトリメソッド自体の名前を factory-method 属性で設定します。次の例は、そのような Bean を構成する方法を示しています。

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

次の例は、対応するクラスを示しています。

Java
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}
Kotlin
class DefaultServiceLocator {
    companion object {
        private val clientService = ClientServiceImpl()
    }
    fun createClientServiceInstance(): ClientService {
        return clientService
    }
}

次の例に示すように、1 つのファクトリクラスは複数のファクトリメソッドも保持できます。

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

次の例は、対応するクラスを示しています。

Java
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}
Kotlin
class DefaultServiceLocator {
    companion object {
        private val clientService = ClientServiceImpl()
        private val accountService = AccountServiceImpl()
    }

    fun createClientServiceInstance(): ClientService {
        return clientService
    }

    fun createAccountServiceInstance(): AccountService {
        return accountService
    }
}

このアプローチは、ファクトリ Bean 自体を依存性注入(DI)によって管理および構成できることを示しています。依存関係と構成の詳細を参照してください。

Spring ドキュメントでは、「ファクトリ Bean」とは、Spring コンテナー内で構成され、インスタンスまたは静的ファクトリメソッドを通じてオブジェクトを作成する Bean を指します。対照的に、FactoryBean (大文字に注意) は Spring 固有の FactoryBean 実装クラスを指します。
Bean の実行時型の決定

特定の Bean の実行時型を決定することは簡単ではありません。Bean メタデータ定義で指定されたクラスは、単なる初期クラス参照であり、宣言されたファクトリメソッドと組み合わされるか、FactoryBean クラスであり、Bean の実行時型が異なるか、インスタンスの場合はまったく設定されない可能性があります。レベルのファクトリメソッド(代わりに、指定された factory-bean 名を介して解決されます)。さらに、AOP プロキシは、ターゲット Bean の実際の型(実装されたインターフェースのみ)の限定的な公開で、インターフェースベースのプロキシで Bean インスタンスをラップする場合があります。

特定の Bean の実際の実行時型を調べるには、指定された Bean 名の BeanFactory.getType 呼び出しをお勧めします。これは、上記のすべてのケースを考慮に入れ、BeanFactory.getBean 呼び出しが同じ Bean 名に対して返すオブジェクトの型を返します。

1.4. 依存関係

典型的なエンタープライズアプリケーションは、単一のオブジェクト(または Spring 用語では Bean)で構成されていません。最も単純なアプリケーションでさえ、エンドユーザーが一貫したアプリケーションと見なすものを提示するために連携するいくつかのオブジェクトを持っています。この次のセクションでは、スタンドアロンの多数の Bean 定義の定義から、ゴールを達成するためにオブジェクトが協力する完全に実現されたアプリケーションに至るまでの方法について説明します。

1.4.1. 依存性注入

依存性注入(DI)は、コンストラクターの引数、ファクトリメソッドへの引数、オブジェクトインスタンスの構築後に設定されるプロパティを通じてのみ、オブジェクトが依存関係(つまり、動作する他のオブジェクト)を定義するプロセスです。ファクトリメソッドから返されます。コンテナーは、Bean を作成するときにそれらの依存関係を注入します。このプロセスは、基本的に、クラスの直接構築または Service Locator パターンを使用して、Bean 自体のインスタンス化または依存関係の位置を制御する Bean 自体の逆(つまり、Inversion of Control)です。

DI の原則によりコードは簡潔になり、オブジェクトに依存関係が提供されると、デカップリングがより効果的になります。オブジェクトは依存関係を検索せず、依存関係の場所またはクラスを知りません。その結果、特に依存関係がインターフェースまたは抽象基本クラスにある場合、クラスのテストが容易になり、ユニットテストでスタブまたはモックの実装を使用できるようになります。

DI は、コンストラクターベースの依存性注入setter ベースの依存性注入の 2 つの主要なバリアントに存在します。

コンストラクターベースの依存性注入

コンストラクターベースの DI は、それぞれが依存関係を表すいくつかの引数を使用してコンストラクターを呼び出すコンテナーによって実現されます。特定の引数を使用して static ファクトリメソッドを呼び出して Bean を構築することはほぼ同等であり、この説明ではコンストラクターと static ファクトリメソッドの引数を同様に扱います。次の例は、コンストラクターの注入でのみ依存関係を注入できるクラスを示しています。

Java
public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private final MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}
Kotlin
// a constructor so that the Spring container can inject a MovieFinder
class SimpleMovieLister(private val movieFinder: MovieFinder) {
    // business logic that actually uses the injected MovieFinder is omitted...
}

このクラスには特別なことは何もないことに注意してください。これは、コンテナー固有のインターフェース、基本クラス、アノテーションに依存しない POJO です。

コンストラクター引数解決

コンストラクターの引数解決の一致は、引数の型を使用して発生します。Bean 定義のコンストラクター引数に潜在的なあいまいさが存在しない場合、コンストラクター引数が Bean 定義で定義される順序は、Bean がインスタンス化されるときにそれらの引数が適切なコンストラクターに提供される順序です。次のクラスを検討してください。

Java
package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}
Kotlin
package x.y

class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)

ThingTwo クラスと ThingThree クラスが継承によって関連付けられていないと仮定すると、潜在的なあいまいさは存在しません。次の構成は正常に機能し、<constructor-arg/> 要素でコンストラクター引数のインデックスまたは型を明示的に指定する必要はありません。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

別の Bean が参照される場合、型は既知であり、一致が発生する可能性があります(前の例の場合のように)。<value>true</value> などの単純な型が使用される場合、Spring は値の型を判別できないため、ヘルプなしでは型ごとに一致できません。次のクラスを検討してください。

Java
package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    private final String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
Kotlin
package examples

class ExampleBean(
    private val years: Int, // Number of years to calculate the Ultimate Answer
    private val ultimateAnswer: String// The Answer to Life, the Universe, and Everything
)
コンストラクター引数型の一致

前のシナリオでは、次の例に示すように、type 属性を使用してコンストラクター引数の型を明示的に指定すると、コンテナーは単純型との型照合を使用できます。

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
コンストラクター引数インデックス

次の例に示すように、index 属性を使用して、コンストラクター引数のインデックスを明示的に指定できます。

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

複数の単純な値のあいまいさを解決することに加えて、インデックスを指定すると、コンストラクターに同じ型の 2 つの引数がある場合のあいまいさを解決できます。

インデックスは 0 ベースです。
コンストラクター引数名

次の例に示すように、コンストラクターパラメーター名を使用して値を明確にすることもできます。

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

この機能をそのまま使用するには、Spring がコンストラクターからパラメーター名を検索できるように、デバッグフラグを有効にしてコードをコンパイルする必要があることに注意してください。デバッグフラグを使用してコードをコンパイルできない場合、またはコンパイルしたくない場合は、@ConstructorProperties (標準 Javadoc) JDK アノテーションを使用して、コンストラクター引数に明示的に名前を付けることができます。サンプルクラスは次のようになります。

Java
package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
Kotlin
package examples

class ExampleBean
@ConstructorProperties("years", "ultimateAnswer")
constructor(val years: Int, val ultimateAnswer: String)
setter ベースの依存性注入

setter ベースの DI は、引数なしのコンストラクターまたは引数なしの static ファクトリメソッドを呼び出して Bean をインスタンス化した後に、コンテナーが setter メソッドを Bean で呼び出すことによって実現されます。

次の例は、純粋な setter インジェクションを使用することによってのみ依存関係をインジェクトできるクラスを示しています。このクラスは従来の Java です。これは、コンテナー固有のインターフェース、基本クラス、アノテーションに依存しない POJO です。

Java
public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}
Kotlin
class SimpleMovieLister {

    // a late-initialized property so that the Spring container can inject a MovieFinder
    lateinit var movieFinder: MovieFinder

    // business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext は、管理する Bean のコンストラクターベースおよび setter ベースの DI をサポートします。また、コンストラクターアプローチによっていくつかの依存関係がすでに注入された後の setter ベースの DI もサポートします。依存関係は BeanDefinition の形式で構成します。これを PropertyEditor インスタンスと組み合わせて使用して、プロパティをある形式から別の形式に変換します。ただし、ほとんどの Spring ユーザーは、これらのクラスを直接(つまり、プログラムで)操作するのではなく、XML bean 定義、アノテーション付きコンポーネント(つまり、@Component@Controller などでアノテーションが付けられたクラス)、Java ベースの @Bean メソッドを操作します。@Configuration クラス。次に、これらのソースは内部で BeanDefinition のインスタンスに変換され、Spring IoC コンテナーインスタンス全体をロードするために使用されます。

コンストラクター vs setter、どちらの DI を選ぶ?

コンストラクターベースの DI と setter ベースの DI を混在させることができるため、必須の依存関係にはコンストラクターを使用し、オプションの依存関係には setter メソッドまたは構成メソッドを使用することをお勧めします。setter メソッドで @Required アノテーションを使用すると、プロパティを必須の依存関係にすることができます。ただし、引数のプログラムによる検証を伴うコンストラクターインジェクションが望ましいです。

Spring チームは通常、アプリケーションコンポーネントを不変オブジェクトとして実装し、必要な依存関係が null でないことを保証できるため、コンストラクターインジェクションを推奨しています。さらに、コンストラクターが注入したコンポーネントは常に、完全に初期化された状態でクライアント(呼び出し)コードに返されます。副次的な注意事項として、コンストラクター引数が多数あることはコードの悪臭であり、クラスの責任が多すぎる可能性があることを意味し、関心事の適切な分離に対処するためにリファクタリングする必要があります。

Setter インジェクションは、主に、クラス内で適切なデフォルト値を割り当てることができるオプションの依存関係にのみ使用する必要があります。それ以外の場合、コードが依存関係を使用するすべての場所で非 null チェックを実行する必要があります。setter インジェクションの利点の 1 つは、setter メソッドが、そのクラスのオブジェクトを後で再構成または再インジェクションしやすくすることです。JMX MBean による管理は、setter インジェクションの魅力的なユースケースです。

特定のクラスに最も意味のある DI スタイルを使用します。場合によっては、ソースがないサードパーティクラスを処理するときに、選択が行われます。例: サードパーティのクラスが setter メソッドを公開しない場合、コンストラクターインジェクションが DI の唯一の利用可能な形式である可能性があります。

依存関係解決プロセス

コンテナーは、次のように Bean 依存関係の解決を実行します。

  • ApplicationContext は、すべての Bean を記述する構成メタデータで作成および初期化されます。構成メタデータは、XML、Java コード、アノテーションによって指定できます。

  • 各 Bean の依存関係は、プロパティ、コンストラクター引数、静的ファクトリメソッドの引数の形式で表されます(通常のコンストラクターの代わりにそれを使用する場合)。これらの依存関係は、Bean が実際に作成されるときに Bean に提供されます。

  • 各プロパティまたはコンストラクターの引数は、設定する値の実際の定義、またはコンテナー内の別の Bean への参照です。

  • 値である各プロパティまたはコンストラクター引数は、指定された形式からそのプロパティまたはコンストラクター引数の実際の型に変換されます。デフォルトでは、Spring は、ストリング形式で提供された値を、intlongStringboolean などのすべての組み込み型に変換できます。

Spring コンテナーは、コンテナーの作成時に各 Bean の構成を検証します。ただし、Bean が実際に作成されるまで、Bean プロパティ自体は設定されません。シングルトンスコープで事前インスタンス化(デフォルト)に設定された Bean は、コンテナーの作成時に作成されます。スコープは Bean スコープで定義されています。それ以外の場合、Bean はリクエストされたときにのみ作成されます。Bean を作成すると、Bean の依存関係とその依存関係の依存関係(など)が作成および割り当てられるため、Bean のグラフが作成される可能性があります。これらの依存関係間の解決の不一致は、遅れて、つまり、影響を受ける Bean を最初に作成したときに表示されることに注意してください。

循環依存関係

主にコンストラクターインジェクションを使用する場合、解決できない循環依存シナリオを作成することができます。

次に例を示します: クラス A は、コンストラクターインジェクションを通じてクラス B のインスタンスを必要とし、クラス B は、コンストラクターインジェクションを通じてクラス A のインスタンスを必要とします。クラス A および B の Bean を相互に注入するように構成すると、Spring IoC コンテナーは実行時にこの循環参照を検出し、BeanCurrentlyInCreationException をスローします。

考えられる解決策の 1 つは、一部のクラスのソースコードを編集して、コンストラクターではなく setter で構成することです。または、コンストラクターインジェクションを避け、setter 注入のみを使用します。つまり、推奨されていませんが、setter インジェクションで循環依存関係を構成できます。

典型的な場合(循環依存関係なし)とは異なり、Bean A と Bean B の間の循環依存関係により、完全に初期化される前に、一方の Bean が他方に強制的に注入されます(従来の鶏と卵のシナリオ)。

一般的に Spring が正しいことをすることを信頼することができます。コンテナーのロード時に、存在しない Bean への参照や循環依存関係などの構成の課題を検出します。Spring は、Bean が実際に作成されたときに、プロパティを設定し、依存関係をできるだけ遅く解決します。つまり、正しくロードされた Spring コンテナーは、オブジェクトまたはその依存関係の 1 つを作成する際に問題が発生した場合に、オブジェクトをリクエストしたときに後で例外を生成できます。たとえば、Bean は、欠落または無効の結果として例外をスローします。プロパティ。いくつかの構成の課題のこの潜在的に遅延した可視性が、ApplicationContext 実装がデフォルトでシングルトン Bean を事前インスタンス化する理由です。これらの Bean を実際に必要になる前に作成するための事前の時間とメモリを犠牲にして、ApplicationContext の作成時に、後でではなく、構成の課題を発見します。シングルトン Bean が先行して事前インスタンス化されるのではなく、遅延して初期化されるように、このデフォルトの動作をオーバーライドすることもできます。

循環依存関係が存在しない場合、1 つ以上の連携 Bean が依存 Bean に注入されるときに、各連携 Bean は依存 Bean に注入される前に完全に構成されます。これは、Bean A が Bean B に依存している場合、Spring IoC コンテナーは、Bean A で setter メソッドを呼び出す前に Bean B を完全に構成することを意味します。つまり、Bean はインスタンス化されます (事前にインスタンス化されたシングルトンでない場合)。)、その依存関係が設定され、関連するライフサイクルメソッド ( 構成された init メソッドInitializingBean コールバックメソッドなど) が呼び出されます。

依存性注入の例

次の例では、setter ベースの DI に XML ベースの構成メタデータを使用しています。Spring XML 構成ファイルのごく一部は、次のようにいくつかの Bean 定義を指定しています。

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

次の例は、対応する ExampleBean クラスを示しています。

Java
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}
Kotlin
class ExampleBean {
    lateinit var beanOne: AnotherBean
    lateinit var beanTwo: YetAnotherBean
    var i: Int = 0
}

上記の例では、setter は XML ファイルで指定されたプロパティと一致するように宣言されています。次の例では、コンストラクターベースの DI を使用しています。

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

次の例は、対応する ExampleBean クラスを示しています。

Java
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}
Kotlin
class ExampleBean(
        private val beanOne: AnotherBean,
        private val beanTwo: YetAnotherBean,
        private val i: Int)

Bean 定義で指定されたコンストラクター引数は、ExampleBean のコンストラクターへの引数として使用されます。

ここで、コンストラクターを使用する代わりに、Spring が static ファクトリメソッドを呼び出してオブジェクトのインスタンスを返すように指示されている、この例のバリアントを考えます。

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

次の例は、対応する ExampleBean クラスを示しています。

Java
public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}
Kotlin
class ExampleBean private constructor() {
    companion object {
        // a static factory method; the arguments to this method can be
        // considered the dependencies of the bean that is returned,
        // regardless of how those arguments are actually used.
        fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean {
            val eb = ExampleBean (...)
            // some other operations...
            return eb
        }
    }
}

static ファクトリメソッドへの引数は、コンストラクターが実際に使用された場合とまったく同じように、<constructor-arg/> 要素によって提供されます。ファクトリメソッドによって返されるクラスの型は、static ファクトリメソッドを含むクラスと同じ型である必要はありません(ただし、この例ではそうです)。インスタンス(非静的)ファクトリメソッドは(class 属性の代わりに factory-bean 属性を使用することを除いて)基本的に同じ方法で使用できるため、ここではそれらの詳細については説明しません。

1.4.2. 依存関係と構成の詳細

前のセクションで説明したように、Bean プロパティとコンストラクター引数は、他のマネージド Bean (コラボレーター) への参照として、またはインラインで定義された値として定義できます。Spring の XML ベースの構成メタデータは、この目的のために、<property/> 要素および <constructor-arg/> 要素内のサブ要素型をサポートします。

ストレート値 (プリミティブ、文字列など)

<property/> 要素の value 属性は、人間が読める文字列表現としてプロパティまたはコンストラクター引数を指定します。Spring の変換サービスは、これらの値を String からプロパティまたは引数の実際の型に変換するために使用されます。次の例は、設定されるさまざまな値を示しています。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="misterkaoli"/>
</bean>

次の例では、さらに簡潔な XML 構成に p-namespace を使用しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="misterkaoli"/>

</beans>

上記の XML はより簡潔です。ただし、Bean 定義を作成するときにプロパティの自動補完をサポートする IDE(Pleiades All in One (JDK, STS, Lombok 付属) または Eclipse 用 Spring Tools (英語) IntelliJ IDEA (英語) など)を使用しない限り、設計時ではなく実行時に型ミスが検出されます。このような IDE の支援を強くお勧めします。

次のように、java.util.Properties インスタンスを構成することもできます。

<bean id="mappings"
    class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Spring コンテナーは、JavaBeans PropertyEditor メカニズムを使用して、<value/> エレメント内のテキストを java.util.Properties インスタンスに変換します。これは便利なショートカットであり、Spring チームが value 属性スタイルよりもネストされた <value/> 要素の使用を好む数少ない場所の 1 つです。

idref 要素

idref 要素は、コンテナー内の別の Bean の id (文字列値 - 参照ではない)を <constructor-arg/> または <property/> 要素に渡すための単なるエラー防止方法です。次の例は、その使用方法を示しています。

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean"/>
    </property>
</bean>

上記の Bean 定義スニペットは、次のスニペットと(実行時に)まったく同じです。

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean"/>
</bean>

idref タグを使用すると、参照された名前付き Bean が実際に存在するかどうかを デプロイ時にコンテナーが検証できるため、最初の形式が 2 番目の形式よりも推奨されます。2 番目のバリエーションでは、client Bean の targetName プロパティに渡される値の検証は実行されません。型ミスは、client Bean が実際にインスタンス化されるときにのみ発見されます (おそらく致命的な結果を伴います)。client Bean がプロトタイプ Bean である場合、この型ミスとその結果として生じる例外は、コンテナーがデプロイされてからかなり経ってから初めて発見される可能性があります。

idref エレメントの local 属性は、通常の bean 参照を超える値を提供しないため、4.0 Bean XSD ではサポートされなくなりました。4.0 スキーマにアップグレードするときに、既存の idref local 参照を idref bean に変更します。

<idref/> 要素が価値をもたらす一般的な場所(少なくとも Spring 2.0 より前のバージョン)は、ProxyFactoryBean Bean 定義の AOP インターセプターの構成にあります。インターセプター名を指定するときに <idref/> 要素を使用すると、インターセプター ID のスペルミスを防ぐことができます。

他の Bean への参照 (コラボレーター)

ref 要素は、<constructor-arg/> または <property/> 定義要素内の最後の要素です。ここでは、Bean の指定されたプロパティの値を、コンテナーによって管理される別の Bean(コラボレーター)への参照に設定します。参照される Bean は、プロパティが設定される Bean の依存関係であり、プロパティが設定される前に必要に応じて初期化されます。(コラボレーターがシングルトン Bean である場合、コンテナーによってすでに初期化されている可能性があります)すべての参照は、最終的には別のオブジェクトへの参照です。スコープと検証は、bean 属性または parent 属性を介して他のオブジェクトの ID または名前を指定するかどうかによって異なります。

<ref/> タグの bean 属性を介してターゲット Bean を指定するのが最も一般的な形式であり、同じ XML ファイル内にあるかどうかに関係なく、同じコンテナーまたは親コンテナー内の Bean への参照を作成できます。bean 属性の値は、ターゲット Bean の id 属性と同じでも、ターゲット Bean の name 属性の値の 1 つと同じでもかまいません。次の例は、ref 要素の使用方法を示しています。

<ref bean="someBean"/>

parent 属性を介してターゲット Bean を指定すると、現在のコンテナーの親コンテナーにある Bean への参照が作成されます。parent 属性の値は、ターゲット Bean の id 属性またはターゲット Bean の name 属性の値のいずれかと同じである場合があります。ターゲット Bean は、現在のコンテナーの親コンテナーに存在する必要があります。この Bean 参照バリアントは、主にコンテナーの階層があり、親 Bean と同じ名前のプロキシで親コンテナー内の既存の Bean をラップする場合に使用する必要があります。次のリストのペアは、parent 属性の使用方法を示しています。

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>
ref エレメントの local 属性は、通常の bean 参照を超える値を提供しないため、4.0 Bean XSD ではサポートされなくなりました。4.0 スキーマにアップグレードするときに、既存の ref local 参照を ref bean に変更します。
インナー bean

<property/> または <constructor-arg/> 要素内の <bean/> 要素は、次の例に示すように、内部 Bean を定義します。

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

内部 Bean 定義には、定義済みの ID または名前は必要ありません。指定されている場合、コンテナーはそのような値を識別子として使用しません。コンテナーは、作成時に scope フラグも無視します。これは、内部 Bean は常に匿名であり、常に外部 Bean で作成されるためです。インナー Bean に独立してアクセスしたり、内包する Bean 以外のコラボレーション Bean に注入したりすることはできません。

コーナーケースとして、たとえばシングルトン Bean に含まれるリクエストスコープの内部 Bean の場合、カスタムスコープから破棄コールバックを受け取ることができます。内側の Bean インスタンスの作成は、含まれる Bean に関連付けられていますが、破棄コールバックにより、リクエストスコープのライフサイクルに参加できます。これは一般的なシナリオではありません。通常、内部 Bean は、含まれる Bean のスコープを単に共有します。

コレクション

<list/><set/><map/><props/> 要素は、Java Collection 型 ListSetMapProperties のプロパティと引数をそれぞれ設定します。次の例は、それらの使用方法を示しています。

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">[email protected] (英語)  </prop>
            <prop key="support">[email protected] (英語)  </prop>
            <prop key="development">[email protected] (英語)  </prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

マップキーまたは値の値、または設定値は、次の要素のいずれかです。

bean | ref | idref | list | set | map | props | value | null
コレクションのマージ

Spring コンテナーは、コレクションのマージもサポートしています。アプリケーション開発者は、親 <list/><map/><set/> または <props/> 要素を定義し、子 <list/><map/><set/> または <props/> 要素が親コレクションから値を継承およびオーバーライドするようにできます。つまり、子コレクションの値は、親コレクションと子コレクションの要素をマージした結果であり、子コレクションの要素は親コレクションで指定された値をオーバーライドします。

マージに関するこのセクションでは、親子 Bean のメカニズムについて説明します。親 Bean と子 Bean 定義を持つリーダー未知 は、続行する前に関連するセクションを読むことをお勧めします。

次の例は、コレクションのマージを示しています。

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">[email protected] (英語)  </prop>
                <prop key="support">[email protected] (英語)  </prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">[email protected] (英語)  </prop>
                <prop key="support">[email protected] (英語)  </prop>
            </props>
        </property>
    </bean>
<beans>

child Bean 定義の adminEmails プロパティの <props/> 要素で merge=true 属性が使用されていることに注意してください。child Bean がコンテナーによって解決およびインスタンス化されると、結果のインスタンスには、子の adminEmails コレクションを親の adminEmails コレクションとマージした結果を含む adminEmails Properties コレクションが含まれます。次のリストは結果を示しています。

子 Properties コレクションの値セットは親 <props/> からすべてのプロパティ要素を継承し、support 値の子の値は親コレクションの値をオーバーライドします。

このマージ動作は、<list/><map/><set/> コレクション型と同様に適用されます。<list/> エレメントの特定のケースでは、List コレクション・型に関連付けられたセマンティクス(つまり、ordered 値のコレクションの概念)が維持されます。親の値は、子リストのすべての値の前にあります。MapSetProperties コレクション型の場合、順序はありません。コンテナーが内部で使用する関連する MapSetProperties 実装型の基礎となるコレクション・型には、順序付けのセマンティクスは有効ではありません。

コレクションのマージの制限

異なるコレクション型(Map と List など)をマージすることはできません。そうしようとすると、適切な Exception がスローされます。merge 属性は、下位の継承された子定義で指定する必要があります。親コレクション定義で merge 属性を指定することは冗長であり、目的のマージにはなりません。

強く型付けされたコレクション

Java 5 でのジェネリクス型の導入により、強く型付けされたコレクションを使用できます。つまり、String 要素のみを含むことができるように Collection 型を宣言することができます。Spring を使用して、厳密に型指定された Collection を Bean に依存性注入すると、Spring の型変換サポートを利用して、厳密に型指定された Collection インスタンスの要素を適切な型に変換してから、Collection 次の Java クラスと Bean 定義は、その方法を示しています。

Java
public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
Kotlin
class SomeClass {
    lateinit var accounts: Map<String, Float>
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

something Bean の accounts プロパティがインジェクション用に準備されると、強く型付けされた Map<String, Float> の要素型に関するジェネリクス情報がリフレクションによって利用可能になります。Spring の型変換インフラストラクチャは、さまざまな値要素を Float 型であると認識し、文字列値(9.99, 2.753.99)は実際の Float 型に変換されます。

NULL および空の文字列値

Spring は、プロパティなどの空の引数を空の Strings として扱います。次の XML ベースの構成メタデータスニペットは、email プロパティを空の String 値("")に設定します。

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

上記の例は、次の Java コードと同等です。

Java
exampleBean.setEmail("");
Kotlin
exampleBean.email = ""

<null/> 要素は null 値を処理します。次のリストに例を示します。

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上記の構成は、次の Java コードと同等です。

Java
exampleBean.setEmail(null);
Kotlin
exampleBean.email = null
p-namespace を使用した XML ショートカット

p- 名前空間では、ネストされた <property/> 要素の代わりに bean 要素の属性を使用して、プロパティ値をコラボレーションする Bean、またはその両方を記述することができます。

Spring は、XML スキーマ定義に基づく名前空間を持つ拡張可能な構成形式をサポートしています。この章で説明する beans 構成フォーマットは、XML スキーマドキュメントで定義されています。ただし、p-namespace は XSD ファイルでは定義されておらず、Spring のコアにのみ存在します。

次の例は、同じ結果に解決される 2 つの XML スニペット(1 つ目は標準 XML 形式を使用し、2 つ目は p-namespace を使用)を示しています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="[email protected] (英語)  "/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="[email protected] (英語)  "/>
</beans>

この例は、Bean 定義の email と呼ばれる p 名前空間の属性を示しています。これは、Spring にプロパティ宣言を含めるように指示します。前述したように、p-namespace にはスキーマ定義がないため、属性の名前をプロパティ名に設定できます。

次の例には、さらに 2 つの Bean 定義が含まれており、両方とも別の Bean への参照を持っています。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

この例には、p-namespace を使用したプロパティ値だけでなく、プロパティ参照を宣言するための特別な形式も含まれています。最初の Bean 定義では <property name="spouse" ref="jane"/> を使用して Bean john から Bean jane への参照を作成しますが、2 番目の Bean 定義では p:spouse-ref="jane" を属性として使用してまったく同じことを行います。この場合、spouse はプロパティ名ですが、-ref 部分は、これがストレート値ではなく、別の Bean への参照であることを示しています。

p-namespace は、標準の XML 形式ほど柔軟ではありません。例: プロパティ参照を宣言する形式は、Ref で終わるプロパティと衝突しますが、標準の XML 形式は衝突しません。3 つのアプローチすべてを同時に使用する XML ドキュメントを作成しないように、アプローチを慎重に選択し、チームメンバーに伝えることをお勧めします。
c-namespace を使用した XML ショートカット

p-namespace を使用した XML ショートカットと同様に、Spring 3.1 で導入された c-namespace では、ネストされた constructor-arg 要素ではなく、コンストラクター引数を構成するためのインライン属性を使用できます。

次の例では、c: 名前空間を使用して、コンストラクターベースの依存性注入からと同じことを行います。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="[email protected] (英語)  "/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="[email protected] (英語)  "/>

</beans>

c: 名前空間は、名前でコンストラクター引数を設定するために、p: の名前空間(Bean 参照の末尾の -ref)と同じ規則を使用します。同様に、XSD スキーマ(Spring コア内に存在する)で定義されていない場合でも、XML ファイルで宣言する必要があります。

コンストラクターの引数名が使用できないまれな場合(通常、デバッグ情報なしでバイトコードがコンパイルされた場合)、次のように引数インデックスへのフォールバックを使用できます。

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="[email protected] (英語)  "/>
XML 文法により、XML 属性名は数字で始めることができないため(インデックスの表記法では先頭の _ の存在が必要です(一部の IDE では許可されていますが)。対応するインデックス表記は <constructor-arg> 要素にも使用できますが、通常は宣言の単純な順序で十分なので一般的には使用されません。

実際には、コンストラクター解決メカニズムは引数のマッチングにおいて非常に効率的です。本当に必要な場合を除き、構成全体で名前表記を使用することをお勧めします。

複合プロパティ名

最終プロパティ名を除くパスのすべてのコンポーネントが null でない限り、Bean プロパティを設定するときに複合またはネストされたプロパティ名を使用できます。以下の Bean 定義を考慮してください。

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

something Bean には fred プロパティがあり、bob プロパティには sammy プロパティがあり、その最終 sammy プロパティには 123 の値が設定されています。これが機能するためには、Bean の作成後に something の fred プロパティと fred の bob プロパティが null であってはなりません。そうでない場合、NullPointerException がスローされます。

1.4.3. depends-on を使用する

Bean が別の Bean の依存関係である場合、通常は、ある Bean が別の Bean のプロパティとして設定されていることを意味します。通常、XML ベースの構成メタデータの <ref/> 要素でこれを実現します。ただし、Bean 間の依存関係が直接的でない場合があります。例は、データベースドライバーの登録など、クラス内の静的初期化子をトリガーする必要がある場合です。depends-on 属性は、この要素を使用する Bean が初期化される前に、1 つ以上の Bean を明示的に初期化することを明示的に許可できます。次の例では、depends-on 属性を使用して、単一の Bean への依存関係を表現しています。

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

複数の Bean への依存関係を表現するには、depends-on 属性の値として Bean 名のリストを指定します(コンマ、空白、セミコロンは有効な区切り文字です)。

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
depends-on 属性では、初期化時の依存関連と、シングルトン Bean の場合のみ、対応する破棄時の依存関連の両方を指定できます。特定の Bean との depends-on 関連を定義する依存 Bean は、特定の Bean 自体が破棄される前に、最初に破棄されます。depends-on はシャットダウン順序も制御できます。

1.4.4. 遅延初期化された Bean

デフォルトでは、ApplicationContext 実装は、初期化プロセスの一環としてすべてのシングルトン Bean を積極的に作成して構成します。一般に、構成や周囲の環境のエラーは数時間、場合によっては数日後ではなく、すぐに発見されるため、この事前インスタンス化が望ましいです。この動作が望ましくない場合は、Bean 定義を遅延初期化としてマークすることで、シングルトン Bean の事前インスタンス化を防ぐことができます。遅延初期化 Bean は、起動時ではなく最初にリクエストされたときに Bean インスタンスを作成するように IoC コンテナーに指示します。

XML では、次の例に示すように、この動作は <bean/> 要素の lazy-init 属性によって制御されます。

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

上記の構成が ApplicationContext によって使用される場合、ApplicationContext の開始時に lazy Bean は事前にインスタンス化されませんが、not.lazy Bean は事前にインスタンス化されます。

ただし、遅延初期化された Bean が遅延初期化されていないシングルトン Bean の依存関係である場合、ApplicationContext はシングルトンの依存関係を満たす必要があるため、起動時に遅延初期化 Bean を作成します。レイジー初期化された Bean は、レイジー初期化されていない他の場所のシングルトン Bean に注入されます。

次の例に示すように、<beans/> 要素の default-lazy-init 属性を使用して、コンテナーレベルで遅延初期化を制御することもできます。

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

1.4.5. オートワイヤーのコラボレーター

Spring コンテナーは、コラボレーションする Bean 間の関連をオートワイヤーできます。ApplicationContext の内容をインスペクションすることにより、Spring に Bean のコラボレーター(他の Bean)を自動的に解決させることができます。オートワイヤーには次の利点があります。

  • オートワイヤーにより、プロパティまたはコンストラクター引数を指定する必要性を大幅に減らすことができます。( この章の他の場所で説明する Bean テンプレートなどの他のメカニズムも、この点で有益です。)

  • オートワイヤーは、オブジェクトの進化に合わせて構成を更新できます。例: クラスに依存関係を追加する必要がある場合、構成を変更する必要なく、その依存関係を自動的に満たすことができます。自動ベース接続は、コードベースがより安定したときに明示的な接続に切り替えるオプションを無効にすることなく、開発中に特に役立ちます。

XML ベースの構成メタデータ(依存性注入を参照)を使用する場合、<bean/> エレメントの autowire 属性を使用して、Bean 定義のオートワイヤーモードを指定できます。オートワイヤー機能には 4 つのモードがあります。Bean ごとにオートワイヤーを指定するため、オートワイヤーするものを選択できます。次の表に、4 つのオートワイヤーモードを示します。

表 2: オートワイヤーモード
モード 説明

no

(デフォルト)オートワイヤーなし。Bean 参照は、ref 要素によって定義する必要があります。コラボレーターを明示的に指定すると、制御と明確さが向上するため、大きいデプロイの場合、デフォルト設定を変更することはお勧めしません。ある程度、システムの構造をドキュメント化します。

byName

プロパティ名によるオートワイヤー。Spring は、オートワイヤーが必要なプロパティと同じ名前の Bean を探します。例: Bean 定義が名前によるオートワイヤーに設定され、master プロパティが含まれている(つまり、setMaster(..) メソッドがある)場合、Spring は master という名前の Bean 定義を探し、それを使用してプロパティを設定します。

byType

コンテナーにプロパティ型の Bean が 1 つだけ存在する場合、プロパティを自動接続します。複数存在する場合、致命的な例外がスローされます。これは、その Bean に対して byType オートワイヤーを使用できないことを示しています。一致する Bean がない場合、何も起こりません(プロパティは設定されません)。

constructor

byType に似ていますが、コンストラクター引数に適用されます。コンテナー内にコンストラクター引数型の Bean が 1 つしかない場合、致命的なエラーが発生します。

byType または constructor オートワイヤーモードでは、配列と型付きコレクションを接続できます。このような場合、依存関係を満たすために、予想される型に一致するコンテナー内のすべてのオートワイヤー候補が提供されます。予想されるキー型が String の場合、強く型付けされた Map インスタンスをオートワイヤーできます。オートワイヤーされた Map インスタンスの値は、予想される型に一致するすべての Bean インスタンスで構成され、Map インスタンスのキーには対応する Bean 名が含まれています。

オートワイヤーの制限と欠点

オートワイヤーは、プロジェクト全体で一貫して使用される場合に最適に機能します。オートワイヤーが一般的に使用されない場合、開発者が 1 つまたは 2 つの Bean 定義のみを接続するためにそれを使用することは混乱を招く可能性があります。

オートワイヤーの制限と欠点を考慮してください。

  • property および constructor-arg 設定の明示的な依存関係は、常にオートワイヤーをオーバーライドします。プリミティブ、StringsClasses などの単純なプロパティ(およびそのような単純なプロパティの配列)をオートワイヤーすることはできません。この制限は仕様によるものです。

  • オートワイヤーは、明示的な接続ほど正確ではありません。ただし、前の表で記述されていたように、Spring は、予期しない結果が生じる可能性のあるあいまいな場合に推測を避けるように注意しています。Spring 管理対象オブジェクト間の関連は、明示的にドキュメント化されなくなりました。

  • Spring コンテナーからドキュメントを生成するツールでは、接続情報を利用できない場合があります。

  • コンテナー内の複数の Bean 定義は、setter メソッドまたはオートワイヤーされるコンストラクター引数で指定された型と一致する場合があります。配列、コレクション、Map インスタンスの場合、これは必ずしも問題ではありません。ただし、単一の値を期待する依存関係の場合、このあいまいさは勝手に解決されません。一意の Bean 定義が利用できない場合、例外がスローされます。

後者のシナリオでは、いくつかのオプションがあります。

  • 明示的な接続を優先してオートワイヤーを放棄します。

  • 次のセクションで説明するように、Bean 定義の autowire-candidate 属性を false に設定して、Bean 定義のオートワイヤーを回避します。

  • <bean/> 要素の primary 属性を true に設定することにより、単一の Bean 定義を 1 次候補として指定します。

  • アノテーションベースのコンテナー構成に従って、アノテーションベースの構成で利用可能な、よりきめ細かい制御を実装します。

オートワイヤーから Bean を除外する

Bean ごとに、Bean をオートワイヤーから除外できます。Spring の XML 形式で、<bean/> 要素の autowire-candidate 属性を false に設定します。コンテナーは、その特定の Bean 定義をオートワイヤーインフラストラクチャー(@Autowired などのアノテーションスタイル構成を含む)で使用できないようにします。

autowire-candidate 属性は、型ベースのオートワイヤーのみに影響するように設計されています。指定された Bean がオートワイヤー候補としてマークされていない場合でも、名前による明示的な参照には影響しません。結果として、名前によるオートワイヤーは、名前が一致する場合、Bean を注入します。

Bean 名に対するパターンマッチングに基づいて、オートワイヤーの候補を制限することもできます。最上位の <beans/> 要素は、default-autowire-candidates 属性内で 1 つ以上のパターンを受け入れます。例: オートワイヤー候補のステータスを、名前が Repository で終わる Bean に制限するには、値 *Repository を指定します。複数のパターンを提供するには、コンマ区切りリストで定義します。Bean 定義の autowire-candidate 属性の明示的な値 true または false が常に優先されます。このような Bean の場合、パターンマッチングルールは適用されません。

これらの手法は、オートワイヤーによって他の Bean に注入したくない Bean に役立ちます。除外された Bean 自体をオートワイヤーを使用して構成できないという意味ではありません。むしろ、Bean 自体は他の Bean のオートワイヤーの候補ではありません。

1.4.6. メソッドインジェクション

ほとんどのアプリケーションシナリオでは、コンテナー内のほとんどの Bean はシングルトンです。シングルトン Bean が別のシングルトン Bean と連携する必要がある場合、または非シングルトン Bean が別の非シングルトン Bean と連携する必要がある場合、通常は、一方の Bean をもう一方の Bean のプロパティとして定義することで依存関係を処理します。Bean のライフサイクルが異なる場合、問題が発生します。シングルトン Bean A が、おそらく A のメソッド呼び出しごとに、非シングルトン (プロトタイプ) Bean B を使用する必要があるとします。コンテナーはシングルトン Bean A を 1 回だけ作成するため、プロパティを設定する機会は 1 回だけです。コンテナーは、Bean A に Bean B の新しいインスタンスが必要になるたびに提供することはできません。

解決策は、制御の逆転を回避することです。ApplicationContextAware インターフェースを実装し、Bean A が必要とするたびに (通常は新しい) Bean B インスタンスを要求するコンテナーへの getBean("B") 呼び出しを行うことで、Bean A にコンテナーを認識させることができます。次の例は、このアプローチを示しています。

Java
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
Kotlin
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple

// Spring-API imports
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware

class CommandManager : ApplicationContextAware {

    private lateinit var applicationContext: ApplicationContext

    fun process(commandState: Map<*, *>): Any {
        // grab a new instance of the appropriate Command
        val command = createCommand()
        // set the state on the (hopefully brand new) Command instance
        command.state = commandState
        return command.execute()
    }

    // notice the Spring API dependency!
    protected fun createCommand() =
            applicationContext.getBean("command", Command::class.java)

    override fun setApplicationContext(applicationContext: ApplicationContext) {
        this.applicationContext = applicationContext
    }
}

ビジネスコードは Spring Framework を認識し、結合しているため、上記は望ましくありません。Spring IoC コンテナーのやや高度な機能であるメソッドインジェクションを使用すると、このユースケースをきれいに処理できます。

このブログエントリ (英語) でメソッドインジェクションの動機について詳しく読むことができます。

ルックアップメソッドインジェクション

ルックアップメソッドインジェクションは、コンテナー管理 Bean のメソッドをオーバーライドし、コンテナー内の別の名前付き Bean のルックアップ結果を返すコンテナーの機能です。前のセクションで説明したシナリオのように、ルックアップには通常、プロトタイプ Bean が含まれます。Spring Framework は、CGLIB ライブラリからのバイトコード生成を使用してこのメソッドインジェクションを実装し、メソッドをオーバーライドするサブクラスを動的に生成します。

  • この動的なサブクラス化が機能するためには、Spring Bean コンテナーサブクラスが final にすることはできず、オーバーライドするメソッドも final にすることはできません。

  • abstract メソッドを持つクラスを単体テストするには、クラスを自分でサブクラス化し、abstract メソッドのスタブ実装を提供する必要があります。

  • 具象メソッドは、コンポーネントのスキャンにも必要です。これには、具象クラスを取得する必要があります。

  • さらに重要な制限は、ルックアップメソッドがファクトリメソッドでは機能せず、特に構成クラスの @Bean メソッドでは機能しないことです。その理由は、この場合、コンテナーがインスタンスの作成を担当しないため、実行時に生成されるサブクラスをその場で作成できないからです。

前のコードスニペットの CommandManager クラスの場合、Spring コンテナーは createCommand() メソッドの実装を動的にオーバーライドします。再加工された例が示すように、CommandManager クラスには Spring 依存関係はありません。

Java
package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}
Kotlin
package fiona.apple

// no more Spring imports!

abstract class CommandManager {

    fun process(commandState: Any): Any {
        // grab a new instance of the appropriate Command interface
        val command = createCommand()
        // set the state on the (hopefully brand new) Command instance
        command.state = commandState
        return command.execute()
    }

    // okay... but where is the implementation of this method?
    protected abstract fun createCommand(): Command
}

注入されるメソッド(この場合は CommandManager)を含むクライアントクラスでは、注入されるメソッドには次の形式の署名が必要です。

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

メソッドが abstract の場合、動的に生成されたサブクラスがメソッドを実装します。それ以外の場合、動的に生成されたサブクラスは、元のクラスで定義された具象メソッドをオーバーライドします。次の例を考えてみましょう。

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

commandManager として識別される Bean は、myCommand Bean の新しいインスタンスが必要になるたびに、独自の createCommand() メソッドを呼び出します。実際に必要な場合は、myCommand Bean をプロトタイプとしてデプロイするように注意する必要があります。シングルトンの場合は、毎回 myCommand Bean の同じインスタンスが返されます。

または、次の例に示すように、アノテーションベースのコンポーネントモデル内で、@Lookup アノテーションを使用してルックアップメソッドを宣言できます。

Java
public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}
Kotlin
abstract class CommandManager {

    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }

    @Lookup("myCommand")
    protected abstract fun createCommand(): Command
}

または、より慣用的に、ターゲット Bean がルックアップメソッドの宣言された戻り型に対して解決されることに依存できます。

Java
public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}
Kotlin
abstract class CommandManager {

    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }

    @Lookup
    protected abstract fun createCommand(): Command
}

通常、抽象クラスがデフォルトで無視される Spring のコンポーネントスキャンルールと互換性を持たせるために、具体的なスタブ実装でこのようなアノテーション付きルックアップメソッドを宣言する必要があります。この制限は、明示的に登録または明示的にインポートされた Bean クラスには適用されません。

スコープの異なるターゲット Bean にアクセスする別の方法は、ObjectFactoryProvider インジェクションポイントです。依存関係としてのスコープ Bean を参照してください。

ServiceLocatorFactoryBean (org.springframework.beans.factory.config パッケージ内)が役立つこともあります。

任意のメソッドの置換

ルックアップメソッドインジェクションよりも有用性の低いメソッドインジェクションは、マネージド Bean の任意のメソッドを別のメソッド実装に置き換える機能です。この機能が実際に必要になるまで、このセクションの残りを安全にスキップできます。

XML ベースの構成メタデータを使用すると、replaced-method 要素を使用して、デプロイされた Bean の既存のメソッド実装を別のメソッド実装に置き換えることができます。computeValue というメソッドをオーバーライドする次のクラスを検討してください。

Java
public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}
Kotlin
class MyValueCalculator {

    fun computeValue(input: String): String {
        // some real code...
    }

    // some other methods...
}

次の例に示すように、org.springframework.beans.factory.support.MethodReplacer インターフェースを実装するクラスは、新しいメソッド定義を提供します。

Java
/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}
Kotlin
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
class ReplacementComputeValue : MethodReplacer {

    override fun reimplement(obj: Any, method: Method, args: Array<out Any>): Any {
        // get the input value, work with it, and return a computed result
        val input = args[0] as String;
        ...
        return ...;
    }
}

元のクラスをデプロイしてメソッドのオーバーライドを指定する Bean 定義は、次の例のようになります。

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

<replaced-method/> 要素内で 1 つ以上の <arg-type/> 要素を使用して、オーバーライドされるメソッドのメソッドシグネチャーを示すことができます。引数の署名は、メソッドがオーバーロードされ、クラス内に複数のバリアントが存在する場合にのみ必要です。便宜上、引数の型文字列は完全修飾型名の部分文字列である場合があります。例: 以下はすべて java.lang.String に一致します:

java.lang.String
String
Str

多くの場合、引数の数はそれぞれの可能な選択肢を区別するのに十分なので、引数型に一致する最短の文字列のみを入力できるようにすることで、このショートカットは多くの入力を節約できます。

1.5. Bean スコープ

Bean 定義を作成するとき、その Bean 定義によって定義されたクラスの実際のインスタンスを作成するためのレシピを作成します。Bean 定義がレシピであるという考え方は重要です。これは、クラスと同様に、単一のレシピから多くのオブジェクトインスタンスを作成できることを意味するためです。

特定の Bean 定義から作成されたオブジェクトにプラグインされるさまざまな依存関係と構成値を制御できるだけでなく、特定の Bean 定義から作成されたオブジェクトのスコープも制御できます。このアプローチは強力で柔軟です。なぜなら、Java クラスレベルでオブジェクトのスコープをベイク処理する代わりに、構成を通じて作成するオブジェクトのスコープを選択できるからです。Bean は、いくつかのスコープのいずれかにデプロイされるように定義できます。Spring Framework は 6 つのスコープをサポートしますが、そのうち 4 つは Web 対応の ApplicationContext を使用する場合にのみ使用可能です。カスタムスコープを作成することもできます。

次の表に、サポートされているスコープを示します。

表 3: Bean スコープ
スコープ 説明

singleton

(デフォルト)Spring IoC コンテナーごとに、単一の Bean 定義を単一のオブジェクトインスタンスにスコープします。

prototype

単一の Bean 定義を任意の数のオブジェクトインスタンスにスコープします。

request

単一の Bean 定義を単一の HTTP リクエストのライフサイクルにスコープします。つまり、各 HTTP リクエストには、単一の Bean 定義の背後から作成された Bean の独自のインスタンスがあります。Web 対応 Spring ApplicationContext のコンテキストでのみ有効です。

session

単一の Bean 定義を HTTP Session のライフサイクルにスコープします。Web 対応 Spring ApplicationContext のコンテキストでのみ有効です。

application

単一の Bean 定義を ServletContext のライフサイクルにスコープします。Web 対応 Spring ApplicationContext のコンテキストでのみ有効です。

websocket

単一の Bean 定義を WebSocket のライフサイクルにスコープします。Web 対応 Spring ApplicationContext のコンテキストでのみ有効です。

Spring 3.0 以降、スレッドスコープは使用可能ですが、デフォルトでは登録されていません。詳細については、SimpleThreadScope (Javadoc) のドキュメントを参照してください。このカスタムスコープまたは他のカスタムスコープを登録する方法については、カスタムスコープの使用を参照してください。

1.5.1. シングルトンスコープ

シングルトン Bean の 1 つの共有インスタンスのみが管理され、その Bean 定義に一致する 1 つ以上の ID を持つ Bean のすべてのリクエストにより、その 1 つの特定の Bean インスタンスが Spring コンテナーによって返されます。

別の言い方をすれば、Bean 定義を定義し、シングルトンとしてスコープされている場合、Spring IoC コンテナーは、その Bean 定義によって定義されたオブジェクトのインスタンスを 1 つだけ作成します。この単一のインスタンスは、そのようなシングルトン Bean のキャッシュに格納され、その名前付き Bean に対する以降のすべてのリクエストと参照は、キャッシュされたオブジェクトを返します。次の図は、シングルトンスコープの仕組みを示しています。

singleton

Spring のシングルトン Bean の概念は、Gang of Four(GoF)パターンブックで定義されているシングルトンパターンとは異なります。GoF シングルトンは、ClassLoader ごとに特定のクラスのインスタンスが 1 つだけ作成されるように、オブジェクトのスコープをハードコードします。Spring シングルトンの範囲は、コンテナーごとおよび Bean ごとと最もよく説明されています。つまり、単一の Spring コンテナー内の特定のクラスに対して 1 つの Bean を定義すると、Spring コンテナーはその Bean 定義によって定義されたクラスのインスタンスを 1 つだけ作成します。シングルトンスコープは、Spring のデフォルトスコープです。Bean を XML のシングルトンとして定義するには、次の例に示すように Bean を定義できます。

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

1.5.2. プロトタイプスコープ

Bean デプロイの非シングルトンプロトタイプスコープでは、その特定の Bean のリクエストが行われるたびに、新しい Bean インスタンスが作成されます。つまり、Bean が別の Bean に注入されるか、コンテナーで getBean() メソッド呼び出しを介してリクエストされます。原則として、すべてのステートフル Bean にはプロトタイプスコープを使用し、ステートレス Bean にはシングルトンスコープを使用する必要があります。

次の図は、Spring プロトタイプスコープを示しています。

prototype

(典型的な DAO は会話状態を保持しないため、データアクセスオブジェクト(DAO)は通常、プロトタイプとして構成されません。シングルトンダイアグラムのコアを再利用する方が簡単でした)

次の例では、Bean を XML のプロトタイプとして定義しています。

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

他のスコープとは異なり、Spring はプロトタイプ Bean の完全なライフサイクルを管理しません。コンテナーは、プロトタイプオブジェクトをインスタンス化し、構成し、その他の方法で組み立ててクライアントに渡しますが、そのプロトタイプインスタンスのそれ以上の記録はありません。初期化ライフサイクルコールバックメソッドはスコープに関係なくすべてのオブジェクトで呼び出されますが、プロトタイプの場合、構成された破棄ライフサイクルコールバックは呼び出されません。クライアントコードは、プロトタイプスコープのオブジェクトをクリーンアップし、プロトタイプ Bean が保持している高負荷なリソースを解放する必要があります。Spring コンテナーがプロトタイプスコープの Bean によって保持されているリソースを解放できるようにするには、クリーンアップする必要がある Bean への参照を保持するカスタム Bean ポストプロセッサーを使用してみてください。

いくつかの点で、プロトタイプスコープの Bean に関する Spring コンテナーのロールは、Java new オペレーターに代わるものです。その時点以降のすべてのライフサイクル管理は、クライアントが処理する必要があります。(Spring コンテナー内の Bean のライフサイクルの詳細については、ライフサイクルコールバックを参照してください。)

1.5.3. プロトタイプ Bean 依存関係を持つシングルトン Bean

プロトタイプ Bean に依存するシングルトンスコープ Bean を使用する場合、インスタンス化時に依存関係が解決されることに注意してください。プロトタイプスコープの Bean をシングルトンスコープの Bean に依存性注入すると、新しいプロトタイプ Bean がインスタンス化され、シングルトン Bean に依存性注入されます。プロトタイプインスタンスは、シングルトンスコープの Bean に提供される唯一のインスタンスです。

ただし、シングルトンスコープの Bean が、プロトタイプスコープの Bean の新しいインスタンスを実行時に繰り返し取得するとします。プロトタイプスコープの Bean をシングルトン Bean に依存性注入することはできません。その注入は、Spring コンテナーがシングルトン Bean をインスタンス化し、依存性を解決および注入するときに 1 回しか発生しないためです。プロトタイプ Bean の新しいインスタンスが実行時に複数回必要な場合は、メソッドインジェクションを参照してください

1.5.4. リクエスト、セッション、アプリケーション、WebSocket スコープ

requestsessionapplicationwebsocket スコープは、Web 対応の Spring ApplicationContext 実装(XmlWebApplicationContext など)を使用する場合にのみ使用できます。これらのスコープを ClassPathXmlApplicationContext などの通常の Spring IoC コンテナーで使用すると、不明な Bean スコープについて文句を言う IllegalStateException がスローされます。

Web の初期設定

requestsessionapplicationwebsocket レベルでの Bean のスコープ(Web スコープの Bean)をサポートするには、Bean を定義する前にいくつかのマイナーな初期構成が必要です。(この初期セットアップは、標準スコープ singleton および prototype には必要ありません。)

この初期設定を達成する方法は、特定のサーブレット環境によって異なります。

Spring Web MVC 内の Spring DispatcherServlet によって処理されるリクエスト内で、スコープ付き Bean にアクセスする場合、特別な設定は必要ありません。DispatcherServlet は、関連するすべての状態をすでに公開しています。

Spring の DispatcherServlet の外部でリクエストが処理される Servlet 2.5 Web コンテナーを使用する場合(たとえば、JSF または Struts を使用する場合)、org.springframework.web.context.request.RequestContextListener ServletRequestListener を登録する必要があります。Servlet 3.0+ の場合、これは WebApplicationInitializer インターフェースを使用してプログラムで実行できます。または、古いコンテナーの場合は、次の宣言を Web アプリケーションの web.xml ファイルに追加します。

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

または、リスナーの設定に課題がある場合は、Spring の RequestContextFilter の使用を検討してください。フィルターマッピングは、周囲の Web アプリケーションの構成に依存するため、必要に応じて変更する必要があります。次のリストは、Web アプリケーションのフィルター部分を示しています。

<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

DispatcherServletRequestContextListenerRequestContextFilter はすべてまったく同じことを行います。つまり、HTTP リクエストオブジェクトを、そのリクエストを処理している Thread にバインドします。これにより、リクエストスコープおよびセッションスコープの Bean がチェーン呼び出しのさらに下で使用可能になります。

リクエストスコープ

Bean 定義の次の XML 構成を検討してください。

<bean id="loginAction" class="com.something.LoginAction" scope="request"/>

Spring コンテナーは、すべての HTTP リクエストの loginAction Bean 定義を使用して、LoginAction Bean の新しいインスタンスを作成します。つまり、loginAction Bean は、HTTP リクエストレベルでスコープされます。同じ loginAction Bean 定義から作成された他のインスタンスはこれらの状態の変化を認識しないため、作成されたインスタンスの内部状態を必要なだけ変更できます。これらは個々のリクエストに固有のものです。リクエストの処理が完了すると、リクエストのスコープにある Bean は破棄されます。

アノテーション駆動型コンポーネントまたは Java 構成を使用する場合、@RequestScope アノテーションを使用して、コンポーネントを request スコープに割り当てることができます。次の例は、その方法を示しています。

Java
@RequestScope
@Component
public class LoginAction {
    // ...
}
Kotlin
@RequestScope
@Component
class LoginAction {
    // ...
}
セッションスコープ

Bean 定義の次の XML 構成を検討してください。

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

Spring コンテナーは、単一の HTTP Session の存続期間に userPreferences Bean 定義を使用して、UserPreferences Bean の新しいインスタンスを作成します。言い換えると、userPreferences Bean は HTTP Session レベルで効果的にスコープされます。リクエストスコープ Bean と同様に、同じ userPreferences Bean 定義から作成されたインスタンスも使用している他の HTTP Session インスタンスはこれらの状態の変化を認識しないため、作成されるインスタンスの内部状態を必要なだけ変更できます。なぜなら、それらは個々の HTTP Session に特有です。HTTP Session が最終的に破棄されると、その特定の HTTP Session にスコープされた Bean も破棄されます。

アノテーション駆動型コンポーネントまたは Java 構成を使用する場合、@SessionScope アノテーションを使用して、コンポーネントを session スコープに割り当てることができます。

Java
@SessionScope
@Component
public class UserPreferences {
    // ...
}
Kotlin
@SessionScope
@Component
class UserPreferences {
    // ...
}
アプリケーションスコープ

Bean 定義の次の XML 構成を検討してください。

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

Spring コンテナーは、Web アプリケーション全体に対して appPreferences Bean 定義を 1 回使用して、AppPreferences Bean の新しいインスタンスを作成します。つまり、appPreferences Bean は ServletContext レベルでスコープされ、通常の ServletContext 属性として保存されます。これは Spring シングルトン Bean に多少似ていますが、2 つの重要な点で異なります: Spring "ApplicationContext" (特定の Web アプリケーションには複数ある場合があります)ごとではなく、ServletContext ごとにシングルトンであり、実際に公開されているため、ServletContext 属性として表示されます。

アノテーション駆動型コンポーネントまたは Java 構成を使用する場合、@ApplicationScope アノテーションを使用して、コンポーネントを application スコープに割り当てることができます。次の例は、その方法を示しています。

Java
@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
Kotlin
@ApplicationScope
@Component
class AppPreferences {
    // ...
}
依存関係としてのスコープ Bean

Spring IoC コンテナーは、オブジェクト(Bean)のインスタンス化だけでなく、コラボレーター(または依存関係)の接続も管理します。(たとえば)HTTP リクエストスコープの Bean を、より寿命の長いスコープの別の Bean に注入する場合、スコープ付き Bean の代わりに AOP プロキシを注入することを選択できます。つまり、スコープオブジェクトと同じパブリックインターフェースを公開するプロキシオブジェクトを挿入する必要がありますが、関連するスコープ(HTTP リクエストなど)から実際のターゲットオブジェクトを取得し、メソッド呼び出しを実際のオブジェクトに委譲することもできます。

singleton をスコープとする Bean 間で <aop:scoped-proxy/> を使用することもできます。その場合、参照はシリアライズ可能な中間プロキシを通過するため、デシリアライズ時にターゲットシングルトン Bean を再取得できます。

<aop:scoped-proxy/> をスコープ prototype の Bean に対して宣言すると、共有プロキシでのすべてのメソッド呼び出しは、呼び出しが転送される新しいターゲットインスタンスの作成につながります。

また、スコーププロキシは、より短いスコープから Bean にライフサイクルセーフな方法でアクセスする唯一の方法ではありません。また、インジェクションポイント (つまり、コンストラクターまたは setter 引数、あるいは自動フィールドです) を ObjectFactory<MyTargetBean> として宣言することで、getObject() 呼び出しが必要になるたびに、インスタンスを保持したり個別に格納したりすることなく、現在のインスタンスをオンデマンドで取得できるようにすることもできます。

拡張バリアントとして、ObjectProvider<MyTargetBean> を宣言できます。これは、getIfAvailable や getIfUnique など、いくつかの追加のアクセスバリアントを提供します。

これの JSR-330 バリアントは Provider と呼ばれ、Provider<MyTargetBean> 宣言と、検索の試行ごとに対応する get() 呼び出しで使用されます。JSR-330 全体の詳細については、こちらを参照してください。

次の例の構成は 1 行のみですが、その背後にある「理由」と「方法」を理解することが重要です。

<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/> (1)
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>
1 プロキシを定義する行。

このようなプロキシを作成するには、子 <aop:scoped-proxy/> 要素をスコープ付き Bean 定義に挿入します(作成するプロキシの型の選択および XML スキーマベースの構成を参照)。requestsession、カスタムスコープレベルでスコープされた Bean の定義に <aop:scoped-proxy/> 要素が必要なのはなぜですか? 次のシングルトン Bean 定義を検討し、前述のスコープに対して定義する必要があるものと対比してください(次の userPreferences Bean 定義は、現状では不完全であることに注意してください)。

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

上記の例では、シングルトン Bean(userManager)に HTTP Session -scoped Bean(userPreferences)への参照が挿入されています。ここでの顕著な点は、userManager Bean はシングルトンであるということです。コンテナーごとに 1 回だけインスタンス化され、その依存関係(この場合は 1 つのみ、userPreferences Bean)も 1 回だけ注入されます。これは、userManager Bean がまったく同じ userPreferences オブジェクト(つまり、最初に注入されたオブジェクト)でのみ動作することを意味します。

これは、寿命の短いスコープ Bean を寿命の長いスコープ Bean に注入するときの動作ではありません(たとえば、Bean を依存関係としてシングルトン Bean にコラボレーションする HTTP Session -scoped を注入します)。むしろ、単一の userManager オブジェクトが必要であり、HTTP Session の存続期間中、HTTP Session に固有の userPreferences オブジェクトが必要です。コンテナーは、UserPreferences クラス(理想的には UserPreferences インスタンスであるオブジェクト)とまったく同じパブリックインターフェースを公開するオブジェクトを作成します。オブジェクトは、スコープメカニズム(HTTP リクエスト、Session など)から実際の UserPreferences オブジェクトをフェッチできます。コンテナーは、このプロキシオブジェクトを userManager Bean に注入します。これは、この UserPreferences 参照がプロキシであることを認識していません。この例では、UserManager インスタンスが依存関係が注入された UserPreferences オブジェクトのメソッドを呼び出すとき、実際にはプロキシのメソッドを呼び出しています。次に、プロキシは(この場合)HTTP Session から実際の UserPreferences オブジェクトをフェッチし、取得した実際の UserPreferences オブジェクトにメソッド呼び出しを委譲します。

次の例に示すように、request- Bean および session-scoped Bean をコラボレーションオブジェクトに注入する場合は、次の(正しい完全な)構成が必要です。

<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
作成するプロキシの型の選択

デフォルトでは、Spring コンテナーが <aop:scoped-proxy/> 要素でマークアップされた Bean のプロキシを作成すると、CGLIB ベースのクラスプロキシが作成されます。

CGLIB プロキシは、public メソッド呼び出しのみをインターセプトします! そのようなプロキシで非 public メソッドを呼び出さないでください。これらは、実際のスコープターゲットオブジェクトに委譲されません。

または、<aop:scoped-proxy/> 要素の proxy-target-class 属性の値に false を指定することにより、Spring コンテナーを設定して、そのようなスコープ Bean の標準 JDK インターフェースベースのプロキシを作成できます。JDK インターフェースベースのプロキシを使用すると、そのようなプロキシに影響を与えるためにアプリケーションクラスパスに追加のライブラリを必要としないことを意味します。ただし、スコープ付き Bean のクラスは少なくとも 1 つのインターフェースを実装する必要があり、スコープ付き Bean が挿入されるすべてのコラボレーターは、そのインターフェースの 1 つを介して Bean を参照する必要があります。次の例は、インターフェースに基づいたプロキシを示しています。

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.stuff.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

クラスベースまたはインターフェースベースのプロキシの選択の詳細については、プロキシメカニズムを参照してください。

1.5.5. カスタムスコープ

Bean スコーピングメカニズムは拡張可能です。独自のスコープを定義することも、既存のスコープを再定義することもできますが、後者は悪い習慣と見なされ、組み込みの singleton および prototype スコープをオーバーライドすることはできません。

カスタムスコープの作成

カスタムスコープを Spring コンテナーに統合するには、このセクションで説明する org.springframework.beans.factory.config.Scope インターフェースを実装する必要があります。独自のスコープを実装する方法のアイデアについては、Spring Framework 自体と Scope javadoc で提供される Scope 実装を参照してください。これにより、実装する必要があるメソッドが詳細に説明されます。

Scope インターフェースには、スコープからオブジェクトを取得し、スコープから削除し、破棄するための 4 つのメソッドがあります。

たとえば、セッションスコープの実装は、セッションスコープの Bean を返します(存在しない場合、メソッドは、Bean の新しいインスタンスを、将来の参照のためにセッションにバインドした後に返します)。次のメソッドは、基になるスコープからオブジェクトを返します。

Java
Object get(String name, ObjectFactory<?> objectFactory)
Kotlin
fun get(name: String, objectFactory: ObjectFactory<*>): Any

たとえば、セッションスコープの実装は、基になるセッションからセッションスコープの Bean を削除します。オブジェクトが返されますが、指定された名前のオブジェクトが見つからない場合は null を返すことができます。次のメソッドは、基になるスコープからオブジェクトを削除します。

Java
Object remove(String name)
Kotlin
fun remove(name: String): Any

次のメソッドは、スコープが破棄されたとき、またはスコープ内の指定されたオブジェクトが破棄されたときにスコープが呼び出すコールバックを登録します。

Java
void registerDestructionCallback(String name, Runnable destructionCallback)
Kotlin
fun registerDestructionCallback(name: String, destructionCallback: Runnable)

破棄コールバックの詳細については、javadoc または Spring スコープの実装を参照してください。

次のメソッドは、基になるスコープの会話識別子を取得します。

Java
String getConversationId()
Kotlin
fun getConversationId(): String

この識別子はスコープごとに異なります。セッションスコープの実装の場合、この識別子はセッション識別子にすることができます。

カスタムスコープの使用

1 つ以上のカスタム Scope 実装を作成してテストした後、Spring コンテナーに新しいスコープを認識させる必要があります。以下の方法は、新しい Scope を Spring コンテナーに登録する中心的な方法です。

Java
void registerScope(String scopeName, Scope scope);
Kotlin
fun registerScope(scopeName: String, scope: Scope)

このメソッドは、Spring に同梱されているほとんどの具体的な ApplicationContext 実装の BeanFactory プロパティを介して利用可能な ConfigurableBeanFactory インターフェースで宣言されています。

registerScope(..) メソッドの最初の引数は、スコープに関連付けられた一意の名前です。Spring コンテナー自体のこのような名前の例は、singleton および prototype です。registerScope(..) メソッドの 2 番目の引数は、登録して使用するカスタム Scope 実装の実際のインスタンスです。

カスタム Scope 実装を作成し、次の例に示すように登録するとします。

次の例では、Spring に含まれていますが、デフォルトでは登録されていない SimpleThreadScope を使用します。手順は、独自のカスタム Scope 実装でも同じです。
Java
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
Kotlin
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)

その後、次のように、カスタム Scope のスコープ規則に準拠する Bean 定義を作成できます。

<bean id="..." class="..." scope="thread">

カスタム Scope 実装を使用すると、スコープのプログラムによる登録に限定されません。次の例に示すように、CustomScopeConfigurer クラスを使用して、Scope の登録を宣言的に行うこともできます。

<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>
FactoryBean 実装の <bean> 宣言内に <aop:scoped-proxy/> を配置すると、スコープが設定されるのはファクトリ Bean 自体であり、getObject() から返されるオブジェクトではありません。

1.6. Bean の性質のカスタマイズ

Spring Framework は、Bean の性質をカスタマイズするために使用できる多くのインターフェースを提供します。このセクションでは、次のようにグループ化します。

1.6.1. ライフサイクルコールバック

Bean ライフサイクルのコンテナーの管理とやり取りするために、Spring InitializingBean および DisposableBean インターフェースを実装できます。コンテナーは、前者の場合は afterPropertiesSet() を、後者の場合は destroy() を呼び出して、Bean の初期化および破棄時に Bean が特定のアクションを実行できるようにします。

JSR-250 @PostConstruct および @PreDestroy アノテーションは、一般に、最新の Spring アプリケーションでライフサイクルコールバックを受信するためのベストプラクティスと見なされています。これらのアノテーションを使用すると、Bean は Spring 固有のインターフェースに結合されません。詳細については、@PostConstruct および @PreDestroy の使用を参照してください。

JSR-250 アノテーションを使用したくないが、それでもカップリングを除去したい場合は、init-method および destroy-method Bean 定義メタデータを検討してください。

内部的に、Spring Framework は BeanPostProcessor 実装を使用して、適切なメソッドを見つけて呼び出すことができるコールバックインターフェースを処理します。カスタム機能またはその他のライフサイクル動作が必要な場合、Spring はデフォルトでは提供していませんが、BeanPostProcessor を自分で実装できます。詳細については、コンテナー拡張ポイントを参照してください。

初期化および破棄のコールバックに加えて、Spring 管理オブジェクトは Lifecycle インターフェースを実装することもできます。これにより、これらのオブジェクトは、コンテナー自体のライフサイクルによって駆動される起動およびシャットダウンプロセスに参加できます。

このセクションでは、ライフサイクルコールバックインターフェースについて説明します。

初期化コールバック

org.springframework.beans.factory.InitializingBean インターフェースにより、Bean は、コンテナーが Bean で必要なすべてのプロパティを設定した後に初期化作業を実行できます。InitializingBean インターフェースは単一のメソッドを指定します:

void afterPropertiesSet() throws Exception;

InitializingBean インターフェースはコードを Spring に不必要に結合するため、使用しないことをお勧めします。または、@PostConstruct アノテーションを使用するか、POJO 初期化メソッドを指定することをお勧めします。XML ベースの構成メタデータの場合、init-method 属性を使用して、引数なしの void 署名を持つメソッドの名前を指定できます。Java 構成では、@Bean の initMethod 属性を使用できます。ライフサイクルコールバックの受信を参照してください。次の例を考えてみましょう。

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
Java
public class ExampleBean {

    public void init() {
        // do some initialization work
    }
}
Kotlin
class ExampleBean {

    fun init() {
        // do some initialization work
    }
}

上記の例は、次の例(2 つのリストで構成されています)とほぼ同じ効果があります。

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
Java
public class AnotherExampleBean implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        // do some initialization work
    }
}
Kotlin
class AnotherExampleBean : InitializingBean {

    override fun afterPropertiesSet() {
        // do some initialization work
    }
}

ただし、前述の 2 つの例の最初の例では、コードを Spring に結合していません。

破棄コールバック

org.springframework.beans.factory.DisposableBean インターフェースを実装すると、Bean を含むコンテナーが破棄されたときに Bean がコールバックを取得できます。DisposableBean インターフェースは単一のメソッドを指定します:

void destroy() throws Exception;

DisposableBean コールバックインターフェースは、コードを Spring に不必要に結合するため、使用しないことをお勧めします。または、@PreDestroy アノテーションを使用するか、Bean 定義でサポートされている一般的なメソッドを指定することをお勧めします。XML ベースの構成メタデータを使用すると、<bean/> の destroy-method 属性を使用できます。Java 構成では、@Bean の destroyMethod 属性を使用できます。ライフサイクルコールバックの受信を参照してください。次の定義を考慮してください。

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
Java
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}
Kotlin
class ExampleBean {

    fun cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

上記の定義は、次の定義とほぼ同じ効果があります。

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
Java
public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
Kotlin
class AnotherExampleBean : DisposableBean {

    override fun destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

ただし、前述の 2 つの定義の最初のものは、コードを Spring に結合しません。

<bean> 要素の destroy-method 属性に特別な (inferred) 値を割り当てることができます。これにより、特定の Bean クラスでパブリック close または shutdown メソッドを自動的に検出するように Spring に指示します。(したがって、java.lang.AutoCloseable または java.io.Closeable を実装するクラスはすべて一致します) <beans> 要素の default-destroy-method 属性にこの特別な (inferred) 値を設定して、この動作を Bean のセット全体に適用することもできます(デフォルトの初期化および破棄メソッドを参照)。これは Java 構成のデフォルトの動作であることに注意してください。
デフォルトの初期化および破棄メソッド

初期化を記述し、Spring 固有の InitializingBean および DisposableBean コールバックインターフェースを使用しないメソッドコールバックを破棄する場合、通常は init()initialize()dispose() などの名前のメソッドを記述します。理想的には、このようなライフサイクルコールバックメソッドの名前はプロジェクト全体で標準化され、すべての開発者が同じメソッド名を使用して一貫性を確保できるようにします。

すべての Bean の名前付き初期化を「検索」し、コールバックメソッド名を破棄するように Spring コンテナーを構成できます。これは、アプリケーション開発者が、各 Bean 定義で init-method="init" 属性を構成することなく、アプリケーションクラスを作成し、init() と呼ばれる初期化コールバックを使用できることを意味します。Spring IoC コンテナーは、Bean の作成時に ( 前述の標準ライフサイクルコールバック契約に従って) そのメソッドを呼び出します。この機能は、初期化および破棄メソッドのコールバックに対して一貫した命名規則も適用します。

初期化コールバックメソッドの名前が init() で、破棄コールバックメソッドの名前が destroy() であるとします。クラスは、次の例のクラスに似ています。

Java
public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}
Kotlin
class DefaultBlogService : BlogService {

    private var blogDao: BlogDao? = null

    // this is (unsurprisingly) the initialization callback method
    fun init() {
        if (blogDao == null) {
            throw IllegalStateException("The [blogDao] property must be set.")
        }
    }
}

次に、そのクラスを次のような Bean で使用できます。

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

最上位の <beans/> 要素属性に default-init-method 属性が存在すると、Spring IoC コンテナーは、Bean クラスの init と呼ばれるメソッドを初期化メソッドコールバックとして認識します。Bean が作成およびアセンブルされるときに、Bean クラスにそのようなメソッドがある場合、適切なタイミングで呼び出されます。

最上位の <beans/> 要素で default-destroy-method 属性を使用することで、同様に(つまり XML で)destroy メソッドコールバックを構成できます。

既存の Bean クラスには、慣例とは異なる名前のコールバックメソッドがすでに存在する場合、<bean/> 自体の init-method および destroy-method 属性を使用してメソッド名を(XML で)指定することにより、デフォルトをオーバーライドできます。

Spring コンテナーは、Bean にすべての依存関係が提供された直後に、構成された初期化コールバックが呼び出されることを保証します。初期化コールバックは生の Bean 参照で呼び出されます。これは、AOP インターセプターなどがまだ Bean に適用されていないことを意味します。ターゲット Bean が最初に完全に作成され、次にインターセプターチェーンを備えた AOP プロキシ(たとえば)が適用されます。ターゲット Bean とプロキシが別々に定義されている場合、コードはプロキシをバイパスして生のターゲット Bean と対話することさえできます。インターセプターを init メソッドに適用することは一貫性がありません。これを行うと、ターゲット Bean のライフサイクルがそのプロキシまたはインターセプターに結合され、コードが生のターゲット Bean と直接対話するときに奇妙なセマンティクスが残るためです。

ライフサイクルメカニズムの組み合わせ

Spring 2.5 以降、Bean ライフサイクルの動作を制御するための 3 つのオプションがあります。

1 つの Bean に対して複数のライフサイクルメカニズムが構成されており、各メカニズムが異なるメソッド名で構成されている場合、構成された各メソッドは、この注記の後にリストされている順序で実行されます。ただし、これらのライフサイクルメカニズムの複数に対して同じメソッド名が構成されている場合 (たとえば、初期化メソッドの init() )、前のセクションで説明したように、そのメソッドは 1 回実行されます。

同じ Bean に対して、異なる初期化方法で構成された複数のライフサイクルメカニズムは、次のように呼び出されます。

  1. @PostConstruct アノテーションが付けられたメソッド

  2.  InitializingBean コールバックインターフェースによって定義された afterPropertiesSet() 

  3. カスタム構成の init() メソッド

Destroy メソッドは同じ順序で呼び出されます:

  1. @PreDestroy アノテーションが付けられたメソッド

  2.  DisposableBean コールバックインターフェースによって定義された destroy() 

  3. カスタム構成の destroy() メソッド

起動とシャットダウンのコールバック

Lifecycle インターフェースは、独自のライフサイクル要件を持つオブジェクト(バックグラウンドプロセスの開始や停止など)に不可欠なメソッドを定義します。

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

Spring で管理されるオブジェクトは、Lifecycle インターフェースを実装できます。次に、ApplicationContext 自体が(たとえば、実行時の停止 / 再起動シナリオのために)開始および停止シグナルを受信すると、それらの呼び出しをそのコンテキスト内で定義されたすべての Lifecycle 実装にカスケードします。これを行うには、次のリストに示す LifecycleProcessor に委譲します。

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

LifecycleProcessor 自体が Lifecycle インターフェースの拡張であることに注意してください。また、リフレッシュおよび閉じられるコンテキストに反応するための 2 つの他のメソッドを追加します。

通常の org.springframework.context.Lifecycle インターフェースは、明示的な開始および停止通知の単純な契約であり、コンテキストのリフレッシュ時の自動起動を意味しないことに注意してください。特定の Bean の自動起動(起動フェーズを含む)をきめ細かく制御するには、代わりに org.springframework.context.SmartLifecycle の実装を検討してください。

また、停止通知が破棄される前に送信されるとは限りません。通常のシャットダウンでは、すべての Lifecycle Bean が最初に停止通知を受信してから、一般的な破棄コールバックが伝達されます。ただし、コンテキストの有効期間中のホットリフレッシュ時、またはリフレッシュの試行が停止したときは、destroy メソッドのみが呼び出されます。

起動とシャットダウンの呼び出しの順序は重要です。2 つのオブジェクト間に「依存」関連が存在する場合、依存側は依存関連の後に開始し、依存関連の前に停止します。ただし、直接的な依存関連が不明な場合があります。特定の型のオブジェクトは、別の型のオブジェクトよりも先に開始する必要があることを知っているかもしれません。そのような場合、SmartLifecycle インターフェースは別のオプション、つまりスーパーインターフェース Phased で定義されている getPhase() メソッドを定義します。次のリストは、Phased インターフェースの定義を示しています。

public interface Phased {

    int getPhase();
}

次のリストは、SmartLifecycle インターフェースの定義を示しています。

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

開始時に、最も低いフェーズのオブジェクトが最初に開始されます。停止するときは、逆の順序に従います。SmartLifecycle を実装し、getPhase() メソッドが Integer.MIN_VALUE を返すオブジェクトは、最初に開始し、最後に停止するオブジェクトになります。スペクトルのもう一方の端では、Integer.MAX_VALUE の位相値は、オブジェクトが最後に開始され、最初に停止されることを示します(実行されている他のプロセスに依存するため)。位相値を検討する場合、SmartLifecycle を実装しない「通常の」 Lifecycle オブジェクトのデフォルトの位相が 0 であることを知ることも重要です。負の位相値は、オブジェクトがそれらの標準コンポーネントの前に開始する(およびその後に停止する)ことを示します。正の位相値の場合、逆のことが言えます。

SmartLifecycle によって定義された停止メソッドは、コールバックを受け入れます。実装は、その実装のシャットダウンプロセスが完了した後に、そのコールバックの run() メソッドを呼び出す必要があります。LifecycleProcessor インターフェースのデフォルト実装である DefaultLifecycleProcessor は、各フェーズ内のオブジェクトのグループのタイムアウト値まで待機してコールバックを呼び出すため、必要に応じて非同期シャットダウンが可能になります。デフォルトのフェーズごとのタイムアウトは 30 秒です。コンテキスト内で lifecycleProcessor という名前の Bean を定義することにより、デフォルトのライフサイクルプロセッサーインスタンスをオーバーライドできます。タイムアウトのみを変更する場合は、次を定義するだけで十分です。

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

前に記述されていたように、LifecycleProcessor インターフェースは、コンテキストのリフレッシュとクローズのコールバックメソッドも定義します。後者は、stop() が明示的に呼び出されたかのようにシャットダウンプロセスを駆動しますが、コンテキストが閉じるときに発生します。一方、"refresh" コールバックは、SmartLifecycle Bean の別の機能を有効にします。コンテキストがリフレッシュされると(すべてのオブジェクトがインスタンス化および初期化された後)、そのコールバックが呼び出されます。その時点で、デフォルトのライフサイクルプロセッサーは、各 SmartLifecycle オブジェクトの isAutoStartup() メソッドによって返されるブール値をチェックします。true の場合、そのオブジェクトはコンテキストまたは独自の start() メソッドの明示的な呼び出しを待つのではなく、その時点で開始されます(コンテキストのリフレッシュとは異なり、コンテキスト開始は標準コンテキスト実装では自動的に行われません)。phase 値と「依存」関連により、前述のように起動順序が決まります。

非 Web アプリケーションで Spring IoC コンテナーを正常にシャットダウンする

このセクションは、非 Web アプリケーションにのみ適用されます。Spring の Web ベースの ApplicationContext 実装には、関連する Web アプリケーションのシャットダウン時に Spring IoC コンテナーを正常にシャットダウンするためのコードがすでに用意されています。

Spring の IoC コンテナーを非 Web アプリケーション環境(たとえば、リッチクライアントデスクトップ環境)で使用する場合、シャットダウンフックを JVM に登録します。これにより、正常なシャットダウンが保証され、シングルトン Bean の関連する destroy メソッドが呼び出され、すべてのリソースが解放されます。これらの破棄コールバックを正しく構成および実装する必要があります。

シャットダウンフックを登録するには、次の例に示すように、ConfigurableApplicationContext インターフェースで宣言されている registerShutdownHook() メソッドを呼び出します。

Java
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...
    }
}
Kotlin
import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    val ctx = ClassPathXmlApplicationContext("beans.xml")

    // add a shutdown hook for the above context...
    ctx.registerShutdownHook()

    // app runs here...

    // main method exits, hook is called prior to the app shutting down...
}

1.6.2. ApplicationContextAware および BeanNameAware

ApplicationContext が org.springframework.context.ApplicationContextAware インターフェースを実装するオブジェクトインスタンスを作成すると、インスタンスにはその ApplicationContext への参照が提供されます。次のリストは、ApplicationContextAware インターフェースの定義を示しています。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

Bean は、ApplicationContext インターフェースを介して、またはこのインターフェースの既知のサブクラス(追加機能を公開する ConfigurableApplicationContext など)への参照をキャストすることにより、作成した ApplicationContext をプログラムで操作できます。1 つの用途は、他の Bean のプログラムによる取得です。この機能が役立つ場合があります。ただし、コードを Spring に結合し、コラボレーターがプロパティとして Bean に提供される Inversion of Control スタイルには従わないため、通常は回避する必要があります。ApplicationContext の他のメソッドは、ファイルリソースへのアクセス、アプリケーションイベントの公開、MessageSource へのアクセスを提供します。これらの追加機能については、ApplicationContext の追加機能で説明しています。

オートワイヤーは、ApplicationContext への参照を取得する別の代替手段です。従来の  constructor および byType オートワイヤーモード(オートワイヤーのコラボレーターで説明)は、それぞれコンストラクター引数または setter メソッドパラメーターに対して型 ApplicationContext の依存関係を提供できます。フィールドや複数のパラメーターメソッドをオートワイヤーする機能など、柔軟性を高めるには、アノテーションベースのオートワイヤー機能を使用します。使用すると、ApplicationContext は、問題のフィールド、コンストラクター、メソッドが @Autowired アノテーションを保持している場合、ApplicationContext 型を予期するフィールド、コンストラクター引数、メソッドパラメーターに自動接続されます。詳細については、@Autowired の使用を参照してください。

ApplicationContext が org.springframework.beans.factory.BeanNameAware インターフェースを実装するクラスを作成すると、そのクラスには、関連するオブジェクト定義で定義された名前への参照が提供されます。次のリストは、BeanNameAware インターフェースの定義を示しています。

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

コールバックは、通常の Bean プロパティの設定後、InitializingBeanafterPropertiesSet、カスタム init メソッドなどの初期化コールバックの前に呼び出されます。

1.6.3. その他の Aware インターフェース

ApplicationContextAware および BeanNameAware (前述)に加えて、Spring は、Bean が特定のインフラストラクチャ依存性を必要とすることをコンテナーに示すことができる、広範囲の Aware コールバックインターフェースを提供します。一般的なルールとして、名前は依存関係の型を示します。次の表に、最も重要な Aware インターフェースを要約します。

表 4: 認識インターフェース
名前 注入された依存関係 説明…

ApplicationContextAware

ApplicationContext の宣言。

ApplicationContextAware および BeanNameAware

ApplicationEventPublisherAware

包含 ApplicationContext のイベント発行者。

ApplicationContext の追加機能

BeanClassLoaderAware

Bean クラスをロードするために使用されるクラスローダー。

Bean のインスタンス化

BeanFactoryAware

BeanFactory の宣言。

ApplicationContextAware および BeanNameAware

BeanNameAware

宣言する Bean の名前。

ApplicationContextAware および BeanNameAware

LoadTimeWeaverAware

ロード時にクラス定義を処理するためのウィーバーを定義しました。

Spring Framework における AspectJ を使用したロードタイムウィービング

MessageSourceAware

メッセージを解決するための構成された戦略(パラメーター化と国際化のサポート付き)。

ApplicationContext の追加機能

NotificationPublisherAware

Spring JMX 通知パブリッシャー。

Notifications

ResourceLoaderAware

リソースへの低レベルアクセス用に構成されたローダー。

Resources

ServletConfigAware

コンテナーが実行される現在の ServletConfig。Web 対応 Spring ApplicationContext でのみ有効です。

Spring MVC

ServletContextAware

コンテナーが実行される現在の ServletContext。Web 対応 Spring ApplicationContext でのみ有効です。

Spring MVC

これらのインターフェースを使用すると、コードが Spring API に結び付けられ、Inversion of Control スタイルに従っていません。その結果、コンテナーへのプログラムによるアクセスを必要とするインフラストラクチャ Bean にお勧めします。

1.7. Bean 定義の継承

Bean 定義には、コンストラクター引数、プロパティ値、初期化メソッド、静的ファクトリメソッド名などのコンテナー固有の情報など、多くの構成情報を含めることができます。子 Bean 定義は、親定義から構成データを継承します。子定義は、必要に応じて一部の値をオーバーライドしたり、他の値を追加したりできます。親と子の Bean 定義を使用すると、入力を大幅に節約できます。事実上、これはテンプレートの形式です。

ApplicationContext インターフェースをプログラムで操作する場合、子 Bean 定義は ChildBeanDefinition クラスで表されます。ほとんどのユーザーは、このレベルではそれらを操作しません。代わりに、ClassPathXmlApplicationContext などのクラスで Bean 定義を宣言的に構成します。XML ベースの構成メタデータを使用する場合、parent 属性を使用して子 Bean 定義を指定し、この属性の値として親 Bean を指定できます。次の例は、その方法を示しています。

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  (1)
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>
1parent 属性に注意してください。

子 Bean 定義は、指定されていない場合は親定義の Bean クラスを使用しますが、オーバーライドすることもできます。後者の場合、子 Bean クラスは親と互換性がなければなりません(つまり、親のプロパティ値を受け入れる必要があります)。

子 Bean 定義は、新しい値を追加するオプションを使用して、親からスコープ、コンストラクター引数値、プロパティ値、メソッドオーバーライドを継承します。指定したスコープ、初期化メソッド、破棄メソッド、static ファクトリメソッド設定は、対応する親設定をオーバーライドします。

残りの設定は常に子定義から取得されます: 依存、オートワイヤーモード、依存関係チェック、シングルトン、遅延初期化。

前の例では、abstract 属性を使用して、親 Bean 定義を抽象として明示的にマークしています。親定義でクラスが指定されていない場合、次の例に示すように、親 Bean 定義を abstract として明示的にマークする必要があります。

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

親 Bean は不完全であるため、単独でインスタンス化することはできません。また、明示的に abstract としてマークされています。定義が abstract の場合、子定義の親定義として機能する純粋なテンプレート Bean 定義としてのみ使用できます。別の Bean の ref プロパティとして参照するか、親 Bean ID で明示的な getBean() 呼び出しを行うことにより、そのような abstract 親 Bean を単独で使用しようとすると、エラーが返されます。同様に、コンテナーの内部 preInstantiateSingletons() メソッドは、抽象として定義されている Bean 定義を無視します。

ApplicationContext は、デフォルトですべてのシングルトンを事前にインスタンス化します。(少なくともシングルトン Bean の場合)テンプレートとしてのみ使用する(親)Bean 定義があり、この定義がクラスを指定する場合、abstract 属性を true に設定する必要があります。そうでない場合、アプリケーションコンテキストは、実際に abstract Bean を事前にインスタンス化(試行)します。

1.8. コンテナー拡張ポイント

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

1.8.1. 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
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;
    }
}
Kotlin
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
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);
    }

}
Kotlin
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 メソッド、任意の構成メソッドがオートワイヤーされます。

1.8.2. 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" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <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 プロパティに対してチェックします。

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

このサンプルファイルは、driver および 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"/>

1.8.3. 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 インスタンス自体が返されます。

1.9. アノテーションベースのコンテナー構成

Spring を構成するためのアノテーションは XML よりも優れていますか?

アノテーションベースの構成の導入により、このアプローチが XML よりも「優れている」かどうかという疑問が生じました。短い答えは「それは依存します」です。長い答えは、各アプローチには長所と短所があり、通常、どの戦略がより適しているかを決定するのは開発者次第です。それらが定義される方法のために、アノテーションは宣言で多くのコンテキストを提供し、より短くより簡潔な構成につながります。ただし、XML は、ソースコードに触れたり、再コンパイルしたりすることなく、コンポーネントの接続に優れています。ソースに近い接続を好む開発者もいれば、アノテーション付きクラスはもはや POJO ではなく、さらに構成が分散化され制御が難しくなると主張する開発者もいます。

どちらを選択しても、Spring は両方のスタイルに対応し、さらには両方を組み合わせることもできます。JavaConfig オプションによって、Spring はターゲットコンポーネントのソースコードに手を触れることなく、非侵襲的な方法でアノテーションを使用できること、ツールに関しては、すべての構成スタイルが Pleiades All in One (JDK, STS, Lombok 付属) または Eclipse 用 Spring Tools (英語) によってサポートされます。

XML セットアップの代替手段は、山括弧宣言の代わりにバイトコードメタデータに依存してコンポーネントを結び付けるアノテーションベースの構成によって提供されます。XML を使用して Bean ワイヤリングを記述する代わりに、開発者は、関連するクラス、メソッド、フィールド宣言のアノテーションを使用して、構成をコンポーネントクラス自体に移動します。サンプル: AutowiredAnnotationBeanPostProcessor で記述されていたように、アノテーションと組み合わせて BeanPostProcessor を使用することは、Spring IoC コンテナーを継承する一般的な手段です。例: Spring 2.0 では、@Required アノテーションを使用して必須プロパティを強制する可能性が導入されました。Spring 2.5 では、同じ一般的なアプローチに従って Spring の依存関係の注入を実行できるようになりました。基本的に、@Autowired アノテーションは、オートワイヤーのコラボレーターで説明されているものと同じ機能を提供しますが、よりきめ細かい制御と幅広い適用性を備えています。Spring 2.5 では、@PostConstruct や @PreDestroy などの JSR-250 アノテーションのサポートも追加されました。Spring 3.0 では、@Inject や @Named などの javax.inject パッケージに含まれる JSR-330 (Java の依存性注入) アノテーションのサポートが追加されました。これらのアノテーションの詳細については、関連するセクションを参照してください。

アノテーション注入は、XML 注入の前に実行されます。XML 構成は、両方のアプローチで接続されたプロパティのアノテーションをオーバーライドします。

通常どおり、ポストプロセッサーを個別の Bean 定義として登録できますが、XML ベースの Spring 構成に次のタグを含めることで暗黙的に登録することもできます(context 名前空間が含まれていることに注意してください)。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

<context:annotation-config/> 要素は、次のポストプロセッサーを暗黙的に登録します。

<context:annotation-config/> は、定義されているのと同じアプリケーションコンテキストで Bean のアノテーションのみを検索します。つまり、<context:annotation-config/> を DispatcherServlet の WebApplicationContext に配置すると、コントローラーでは @Autowired Bean のみがチェックされ、サービスはチェックされません。詳細については、DispatcherServlet を参照してください。

1.9.1. @Required

@Required アノテーションは、次の例のように、Bean プロパティ setter メソッドに適用されます。

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
Kotlin
    class SimpleMovieLister {

    @Required
    lateinit var movieFinder: MovieFinder

    // ...
}

このアノテーションは、影響を受ける Bean プロパティが、構成時に、Bean 定義の明示的なプロパティ値またはオートワイヤーを通じて取り込まれる必要があることを示します。影響を受ける Bean プロパティが設定されていない場合、コンテナーは例外をスローします。これにより、後の NullPointerException インスタンスなどを回避して、積極的かつ明示的な障害を許容します。アサーションを Bean クラス自体に(たとえば、init メソッドに)入れることをお勧めします。そうすることで、コンテナーの外部でクラスを使用する場合でも、これらの必要な参照と値が強制されます。

@Required アノテーションのサポートを有効にするには、RequiredAnnotationBeanPostProcessor (Javadoc) を Bean として登録する必要があります。

@Required アノテーションと RequiredAnnotationBeanPostProcessor は、Spring Framework 5.1 で正式に非推奨になり、必要な設定にコンストラクターインジェクションを使用するようになりました(または InitializingBean.afterPropertiesSet() のカスタム実装または Bean プロパティ setter メソッドと一緒にカスタム @PostConstruct メソッド)。

1.9.2. @Autowired を使用する

このセクションに含まれる例では、JSR 330 の @Inject アノテーションを Spring の @Autowired アノテーションの代わりに使用できます。詳細はこちらを参照してください。

次の例に示すように、@Autowired アノテーションをコンストラクターに適用できます。

Java
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
Kotlin
class MovieRecommender @Autowired constructor(
    private val customerPreferenceDao: CustomerPreferenceDao)

Spring Framework 4.3 以降、ターゲット Bean が最初にコンストラクターを 1 つだけ定義している場合、そのようなコンストラクターに対する @Autowired アノテーションは必要なくなりました。ただし、複数のコンストラクターが使用可能で、プライマリ / デフォルトコンストラクターがない場合は、どれを使用するかをコンテナーに指示するために、少なくとも 1 つのコンストラクターに @Autowired アノテーションを付ける必要があります。詳細については、コンストラクターの解決に関する説明を参照してください。

次の例に示すように、@Autowired アノテーションを従来の setter メソッドに適用することもできます。

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
Kotlin
class SimpleMovieLister {

    @Autowired
    lateinit var movieFinder: MovieFinder

    // ...

}

次の例に示すように、任意の名前と複数の引数を持つメソッドにアノテーションを適用することもできます。

Java
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
Kotlin
class MovieRecommender {

    private lateinit var movieCatalog: MovieCatalog

    private lateinit var customerPreferenceDao: CustomerPreferenceDao

    @Autowired
    fun prepare(movieCatalog: MovieCatalog,
                customerPreferenceDao: CustomerPreferenceDao) {
        this.movieCatalog = movieCatalog
        this.customerPreferenceDao = customerPreferenceDao
    }

    // ...
}

次の例に示すように、@Autowired をフィールドにも適用し、コンストラクターと組み合わせることもできます。

Java
public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
Kotlin
class MovieRecommender @Autowired constructor(
    private val customerPreferenceDao: CustomerPreferenceDao) {

    @Autowired
    private lateinit var movieCatalog: MovieCatalog

    // ...
}

ターゲットコンポーネント(たとえば、MovieCatalog または CustomerPreferenceDao)が、@Autowired アノテーション付きインジェクションポイントに使用する型によって一貫して宣言されていることを確認してください。そうしないと、実行時に「型の一致が見つかりません」というエラーが原因でインジェクションが失敗する可能性があります。

クラスパススキャンを介して検出された XML 定義の Bean またはコンポーネントクラスの場合、コンテナーは通常、事前に具象型を認識します。ただし、@Bean ファクトリメソッドの場合、宣言された戻り値の型が十分に表現力があることを確認する必要があります。複数のインターフェースを実装するコンポーネント、または実装型によって潜在的に参照されるコンポーネントの場合、ファクトリメソッドで最も具体的な戻り値型を宣言することを検討してください(少なくとも Bean を参照するインジェクションポイントで必要とされる特定の)。

次の例に示すように、ApplicationContext から特定の型のすべての Bean を提供するように Spring に指示して、@Autowired アノテーションをその型の配列を想定するフィールドまたはメソッドに追加することもできます。

Java
public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    private lateinit var movieCatalogs: Array<MovieCatalog>

    // ...
}

次の例に示すように、型付きコレクションにも同じことが当てはまります。

Java
public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    lateinit var movieCatalogs: Set<MovieCatalog>

    // ...
}

配列またはリスト内の項目を特定の順序でソートする場合、ターゲット Bean は org.springframework.core.Ordered インターフェースを実装するか、@Order または標準 @Priority アノテーションを使用できます。それ以外の場合、それらの順序は、コンテナー内の対応するターゲット Bean 定義の登録順序に従います。

@Order アノテーションは、ターゲットクラスレベルおよび @Bean メソッドで、個々の Bean 定義に対して宣言できます(同じ Bean クラスを使用する複数の定義の場合)。@Order 値は、インジェクションポイントの優先順位に影響を与える可能性がありますが、依存関連と @DependsOn 宣言によって決定される直交の懸念であるシングルトンの起動順序には影響しないことに注意してください。

標準の javax.annotation.Priority アノテーションは、メソッドで宣言できないため、@Bean レベルでは使用できないことに注意してください。そのセマンティクスは、各型の単一 Bean で @Primary と組み合わせて @Order 値を介してモデル化できます。

予想されるキー型が String である限り、型された Map インスタンスでさえオートワイヤーできます。次の例に示すように、マップ値には予想される型のすべての Bean が含まれ、キーには対応する Bean 名が含まれます。

Java
public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    lateinit var movieCatalogs: Map<String, MovieCatalog>

    // ...
}

デフォルトでは、特定のインジェクションポイントに一致する候補 Bean がない場合、オートワイヤーは失敗します。宣言された配列、コレクション、マップの場合、少なくとも 1 つの一致する要素が期待されます。

デフォルトの動作では、アノテーション付きのメソッドとフィールドを必要な依存関係を示すものとして扱います。次の例に示すように、この動作を変更して、フレームワークが不必要なものとしてマークすることで不満足なインジェクションポイントをスキップできるようにします(つまり、@Autowired の required 属性を false に設定することにより)。

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
Kotlin
class SimpleMovieLister {

    @Autowired(required = false)
    var movieFinder: MovieFinder? = null

    // ...
}

依存関係(または、複数の引数の場合はその依存関係の 1 つ)が利用できない場合、非必須メソッドはまったく呼び出されません。このような場合、必須ではないフィールドはまったく入力されず、デフォルト値がそのまま残ります。

@Autowired の required 属性は、複数のコンストラクターを処理する可能性のある Spring のコンストラクター解決アルゴリズムのために、注入されたコンストラクターとファクトリメソッドの引数は特別な場合があります。コンストラクターとファクトリメソッドの引数はデフォルトで効果的に必要ですが、単一のコンストラクターシナリオでは、一致する Bean が利用できない場合に空のインスタンスに解決する複数要素のインジェクションポイント(配列、コレクション、マップ)などのいくつかの特別なルールがありますこれにより、すべての依存関係を一意の複数引数コンストラクターで宣言できる共通の実装パターンが可能になります。たとえば、@Autowired アノテーションなしで単一の public コンストラクターとして宣言できます。

特定の Bean クラスの 1 つのコンストラクターのみが、required 属性を true に設定して @Autowired を宣言できます。これは、Spring Bean として使用される場合にオートワイヤーするコンストラクターを示します。その結果、required 属性がデフォルト値の true のままである場合、@Autowired でアノテーションを付けられるコンストラクターは 1 つだけです。複数のコンストラクターがアノテーションを宣言する場合、すべてオートワイヤーの候補と見なされるために required=false を宣言する必要があります(XML の autowire=constructor に類似)。Spring コンテナー内の Bean を一致させることで満たすことができる依存関係の数が最も多いコンストラクターが選択されます。どの候補も満たすことができない場合は、プライマリ / デフォルトコンストラクター(存在する場合)が使用されます。同様に、クラスが複数のコンストラクターを宣言しているが、それらのいずれにも @Autowired アノテーションが付いていない場合、プライマリ / デフォルトコンストラクター(存在する場合)が使用されます。クラスが最初に単一のコンストラクターのみを宣言する場合、アノテーションが付けられていなくても、常に使用されます。アノテーション付きコンストラクターはパブリックである必要はないことに注意してください。

@Autowired の required 属性は、setter メソッドの非推奨の @Required アノテーションよりも推奨されます。required 属性を false に設定すると、このプロパティはオートワイヤーには不要であり、プロパティをオートワイヤーできない場合は無視されます。一方、@Required は、コンテナーでサポートされている任意の手段によって設定されるプロパティを強制し、値が定義されていない場合、対応する例外が発生するという点でより強力です。

または、次の例に示すように、Java 8 の java.util.Optional を使用して、特定の依存関係の不要な性質を表現できます。

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

Spring Framework 5.0 以降、@Nullable アノテーション(任意のパッケージ内の任意の種類 — たとえば、JSR-305 の javax.annotation.Nullable)を使用するか、Kotlin 組み込みの null セーフティサポートを利用することもできます。

Java
public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}
Kotlin
class SimpleMovieLister {

    @Autowired
    var movieFinder: MovieFinder? = null

    // ...
}

よく知られている解決可能な依存関係であるインターフェースに @Autowired を使用することもできます: BeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisherMessageSource。これらのインターフェースと、ConfigurableApplicationContext や ResourcePatternResolver などの拡張インターフェースは、特別な設定を必要とせずに自動的に解決されます。次の例では、ApplicationContext オブジェクトをオートワイヤーします。

Java
public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    lateinit var context: ApplicationContext

    // ...
}

@Autowired@Inject@Value@Resource アノテーションは、Spring BeanPostProcessor 実装によって処理されます。つまり、これらのアノテーションを独自の BeanPostProcessor または BeanFactoryPostProcessor 型(存在する場合)内に適用することはできません。これらの型は、XML または Spring @Bean メソッドを使用して明示的に「接続」する必要があります。

1.9.3. @Primary によるアノテーションベースのオートワイヤーの微調整

型によるオートワイヤーは複数の候補につながる可能性があるため、多くの場合、選択プロセスをより詳細に制御する必要があります。これを実現する 1 つの方法は、Spring の @Primary アノテーションを使用することです。@Primary は、複数の Bean が単一値の依存関係にオートワイヤーされる候補である場合、特定の Bean を優先する必要があることを示します。候補の中に 1 つのプライマリ Bean が存在する場合、オートワイヤーされた値になります。

firstMovieCatalog をプライマリ MovieCatalog として定義する次の構成を検討してください。

Java
@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}
Kotlin
@Configuration
class MovieConfiguration {

    @Bean
    @Primary
    fun firstMovieCatalog(): MovieCatalog { ... }

    @Bean
    fun secondMovieCatalog(): MovieCatalog { ... }

    // ...
}

上記の構成では、次の MovieRecommender が firstMovieCatalog と自動接続されます。

Java
public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    private lateinit var movieCatalog: MovieCatalog

    // ...
}

対応する Bean 定義は次のとおりです。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

1.9.4. 修飾子を使用したアノテーションベースのオートワイヤーの微調整

@Primary は、1 つの 1 次候補を決定できる場合に、複数のインスタンスで型ごとのオートワイヤーを使用する効果的な方法です。選択プロセスをさらに制御する必要がある場合は、Spring の @Qualifier アノテーションを使用できます。修飾子の値を特定の引数に関連付けて、特定の Bean が各引数に選択されるように型一致のセットを絞り込みます。最も単純なケースでは、次の例に示すように、これはわかりやすい説明的な値になります。

Java
public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private lateinit var movieCatalog: MovieCatalog

    // ...
}

次の例に示すように、個々のコンストラクター引数またはメソッドパラメーターに @Qualifier アノテーションを指定することもできます。

Java
public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}
Kotlin
class MovieRecommender {

    private lateinit var movieCatalog: MovieCatalog

    private lateinit var customerPreferenceDao: CustomerPreferenceDao

    @Autowired
    fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
                customerPreferenceDao: CustomerPreferenceDao) {
        this.movieCatalog = movieCatalog
        this.customerPreferenceDao = customerPreferenceDao
    }

    // ...
}

次の例は、対応する Bean 定義を示しています。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> (1)

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> (2)

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
1main 修飾子値を持つ Bean は、同じ値で修飾されたコンストラクター引数に関連付けられます。
2action 修飾子値を持つ Bean は、同じ値で修飾されたコンストラクター引数に関連付けられます。

フォールバック一致の場合、Bean 名はデフォルトの修飾子値と見なされます。ネストされた修飾子要素の代わりに main の id を使用して Bean を定義すると、同じ一致結果が得られます。ただし、この規則を使用して特定の Bean を名前で参照することはできますが、@Autowired は基本的に、オプションのセマンティック修飾子を使用した型駆動型注入に関するものです。これは、Bean 名のフォールバックがある場合でも、修飾子の値は、型一致のセット内で常に狭義のセマンティクスを持つことを意味します。それらは、一意の Bean id への参照を意味的に表現しません。適切な修飾子の値は main または EMEA または persistent で、Bean id から独立した特定のコンポーネントの特性を表します。これは、前述の例のような匿名 Bean 定義の場合に自動生成される場合があります。

前述のように、修飾子は型付きコレクションにも適用されます。たとえば、Set<MovieCatalog> に適用されます。この場合、宣言された修飾子に従って、一致するすべての Bean がコレクションとして注入されます。これは、修飾子が一意である必要がないことを意味します。むしろ、それらはフィルタリング条件を構成します。例: 同じ修飾子値「アクション」を持つ複数の MovieCatalog Bean を定義できます。これらはすべて、@Qualifier("action") アノテーションが付けられた Set<MovieCatalog> に注入されます。

型一致候補内のターゲット Bean 名に対して修飾子の値を選択できるようにするには、インジェクションポイントで @Qualifier アノテーションを必要としません。他の解決インジケータ(修飾子やプライマリマーカーなど)がない場合、一意でない依存関係の状況では、Spring はインジェクションポイント名(つまり、フィールド名またはパラメーター名)をターゲット Bean 名と照合し、選択します。同じ名前の候補(ある場合)。

ただし、アノテーション駆動型の注入を名前で表現する場合は、型一致候補の中から Bean 名で選択できる場合でも、主に @Autowired を使用しないでください。代わりに、JSR-250 @Resource アノテーションを使用します。これは、一意の名前で特定のターゲットコンポーネントを識別するためにセマンティックに定義されており、宣言された型はマッチングプロセスとは無関係です。@Autowired にはかなり異なるセマンティクスがあります: 型によって候補 Bean を選択した後、指定された String 修飾子の値は、それらの型選択された候補内でのみ考慮されます(たとえば、同じ修飾子ラベルでマークされた Bean に対して account 修飾子を照合します)。

それ自体がコレクション Map または配列型として定義されている Bean の場合、@Resource は特定のコレクションまたは配列 Bean を一意の名前で参照する優れたソリューションです。つまり、4.3、コレクションでは、@Bean の戻り値の型の署名またはコレクションの継承階層に要素型情報が保持されている限り、Spring の @Autowired 型マッチングアルゴリズムを介して Map および配列型をマッチングできます。この場合、前の段落で概説したように、修飾子の値を使用して、同じ型のコレクションから選択できます。

4.3 以降、@Autowired は注入の自己参照(つまり、現在注入されている Bean への参照)も考慮します。自己注入はフォールバックであることに注意してください。他のコンポーネントへの定期的な依存関係は常に優先されます。その意味で、自己参照は通常の候補者選考には参加しないため、特に初心者になることはありません。それどころか、それらは常に最低の優先順位になります。実際には、自己参照は最後の手段としてのみ使用する必要があります(たとえば、Bean のトランザクションプロキシを介して同じインスタンスで他のメソッドを呼び出す場合など)。このようなシナリオでは、影響を受けるメソッドを別のデリゲート Bean に除外することを検討してください。または、@Resource を使用することもできます。これにより、一意の名前で現在の Bean にプロキシを戻すことができます。

同じ構成クラスの @Bean メソッドから結果を注入しようとすることも、事実上自己参照シナリオです。構成クラスのオートワイヤーフィールドとは対照的に、実際に必要なメソッドシグネチャーでそのような参照を遅延解決するか、影響を受ける @Bean メソッドを static として宣言し、含む構成クラスインスタンスとそのライフサイクルから切り離します。それ以外の場合、そのような Bean はフォールバックフェーズでのみ考慮され、他の構成クラスの一致する Bean が代わりにプライマリ候補として選択されます(利用可能な場合)。

@Autowired は、フィールド、コンストラクター、複数引数メソッドに適用され、パラメーターレベルで修飾子のアノテーションを絞り込むことができます。対照的に、@Resource は、単一の引数を持つフィールドおよび Bean プロパティ setter メソッドに対してのみサポートされます。結果として、注入ターゲットがコンストラクターまたは複数引数メソッドである場合、修飾子を使用する必要があります。

独自のカスタム修飾子アノテーションを作成できます。これを行うには、次の例に示すように、アノテーションを定義し、定義内で @Qualifier アノテーションを提供します。

Java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}
Kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)

次に、次の例に示すように、オートワイヤーされたフィールドとパラメーターにカスタム修飾子を提供できます。

Java
public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    @Genre("Action")
    private lateinit var actionCatalog: MovieCatalog

    private lateinit var comedyCatalog: MovieCatalog

    @Autowired
    fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
        this.comedyCatalog = comedyCatalog
    }

    // ...
}

次に、候補 Bean 定義の情報を提供できます。<qualifier/> タグを <bean/> タグのサブエレメントとして追加してから、type および value を指定して、カスタム修飾子アノテーションに一致させることができます。型は、アノテーションの完全修飾クラス名と照合されます。または、名前の競合のリスクが存在しない場合の便宜として、短いクラス名を使用できます。次の例は、両方のアプローチを示しています。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

クラスパススキャンと管理対象コンポーネントでは、XML で修飾子メタデータを提供する代わりのアノテーションベースの代替手段を見ることができます。具体的には、アノテーション付きの修飾子メタデータの提供を参照してください。

場合によっては、値なしでアノテーションを使用するだけで十分な場合があります。これは、アノテーションがより一般的な目的に役立ち、いくつかの異なる型の依存関係に適用できる場合に役立ちます。例: インターネットに接続できないときに検索できるオフラインカタログを提供できます。最初に、次の例に示すように、簡単なアノテーションを定義します。

Java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}
Kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline

次に、次の例に示すように、オートワイヤーするフィールドまたはプロパティにアノテーションを追加します。

Java
public class MovieRecommender {

    @Autowired
    @Offline (1)
    private MovieCatalog offlineCatalog;

    // ...
}
1 この行は、@Offline アノテーションを追加します。
Kotlin
class MovieRecommender {

    @Autowired
    @Offline (1)
    private lateinit var offlineCatalog: MovieCatalog

    // ...
}
1 この行は、@Offline アノテーションを追加します。

これで、次の例に示すように、Bean 定義には修飾子 type のみが必要になります。

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> (1)
    <!-- inject any dependencies required by this bean -->
</bean>
1 この要素は修飾子を指定します。

単純な value 属性に加えて、またはその代わりに、名前付き属性を受け入れるカスタム修飾子アノテーションを定義することもできます。オートワイヤーされるフィールドまたはパラメーターに複数の属性値が指定されている場合、Bean 定義は、オートワイヤーの候補と見なされるすべての属性値と一致する必要があります。例として、次のアノテーション定義を検討してください。

Java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();
}
Kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)

この場合、Format は列挙型で、次のように定義されます。

Java
public enum Format {
    VHS, DVD, BLURAY
}
Kotlin
enum class Format {
    VHS, DVD, BLURAY
}

オートワイヤーされるフィールドには、カスタム修飾子でアノテーションが付けられ、次の例に示すように、両方の属性 genre および format の値が含まれます。

Java
public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}
Kotlin
class MovieRecommender {

    @Autowired
    @MovieQualifier(format = Format.VHS, genre = "Action")
    private lateinit var actionVhsCatalog: MovieCatalog

    @Autowired
    @MovieQualifier(format = Format.VHS, genre = "Comedy")
    private lateinit var comedyVhsCatalog: MovieCatalog

    @Autowired
    @MovieQualifier(format = Format.DVD, genre = "Action")
    private lateinit var actionDvdCatalog: MovieCatalog

    @Autowired
    @MovieQualifier(format = Format.BLURAY, genre = "Comedy")
    private lateinit var comedyBluRayCatalog: MovieCatalog

    // ...
}

最後に、Bean 定義には一致する修飾子の値が含まれている必要があります。この例は、<qualifier/> 要素の代わりに Bean メタ属性を使用できることも示しています。可能な場合、<qualifier/> 要素とその属性が優先されますが、次の例の最後の 2 つの Bean 定義のように、そのような修飾子が存在しない場合、オートワイヤーメカニズムは <meta/> タグ内で提供される値にフォールバックします。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>

1.9.5. オートワイヤー修飾子としてジェネリクスを使用する

@Qualifier アノテーションに加えて、Java ジェネリクス型を暗黙的な修飾形式として使用できます。例: 次の構成があるとします。

Java
@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}
Kotlin
@Configuration
class MyConfiguration {

    @Bean
    fun stringStore() = StringStore()

    @Bean
    fun integerStore() = IntegerStore()
}

前述の Bean が汎用インターフェース(つまり、Store<String> および Store<Integer>)を実装していると仮定すると、次の例に示すように、@Autowire で Store インターフェースを使用でき、汎用は修飾子として使用されます。

Java
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
Kotlin
@Autowired
private lateinit var s1: Store<String> // <String> qualifier, injects the stringStore bean

@Autowired
private lateinit var s2: Store<Integer> // <Integer> qualifier, injects the integerStore bean

リスト、Map インスタンス、配列をオートワイヤーする場合にも、一般的な修飾子が適用されます。次の例は、一般的な List をオートワイヤーします。

Java
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
Kotlin
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private lateinit var s: List<Store<Integer>>

1.9.6. CustomAutowireConfigurer を使用する

CustomAutowireConfigurer (Javadoc) は、Spring の @Qualifier アノテーションが付けられていない場合でも、独自のカスタム修飾子アノテーション型を登録できる BeanFactoryPostProcessor です。次の例は、CustomAutowireConfigurer の使用方法を示しています。

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver は、次の方法でオートワイヤー候補を決定します。

  • 各 Bean 定義の autowire-candidate 値

  • <beans/> 要素で利用可能な default-autowire-candidates パターン

  • @Qualifier アノテーションと CustomAutowireConfigurer に登録されたカスタムアノテーションの存在

複数の Bean がオートワイヤー候補として適格である場合、「プライマリ」の決定は次のとおりです。候補の中の 1 つの Bean 定義に primary 属性が true に設定されている場合、それが選択されます。

1.9.7. @Resource による注入

Spring は、フィールドまたは Bean プロパティ setter メソッドで JSR-250 @Resource アノテーション(javax.annotation.Resource)を使用した注入もサポートしています。これは、Java EE の一般的なパターンです。たとえば、JSF 管理の Bean および JAX-WS エンドポイントです。Spring は、Spring 管理オブジェクトに対してもこのパターンをサポートしています。

@Resource は名前属性を取ります。デフォルトでは、Spring はその値を、挿入される Bean 名として解釈します。つまり、次の例に示すように、名前によるセマンティクスに従います。

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") (1)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
1 この行は @Resource を注入します。
Kotlin
class SimpleMovieLister {

    @Resource(name="myMovieFinder") (1)
    private lateinit var movieFinder:MovieFinder
}
1 この行は @Resource を注入します。

名前が明示的に指定されていない場合、デフォルト名はフィールド名または setter メソッドから派生します。フィールドの場合、フィールド名を取ります。setter メソッドの場合、Bean プロパティ名を取ります。次の例では、movieFinder という名前の Bean を setter メソッドに挿入します。

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
Kotlin
class SimpleMovieLister {

    @Resource
    private lateinit var movieFinder: MovieFinder

}
アノテーションで提供される名前は、CommonAnnotationBeanPostProcessor が認識している ApplicationContext によって Bean 名として解決されます。Spring の SimpleJndiBeanFactory (Javadoc) を明示的に構成すると、JNDI を介して名前を解決できます。ただし、デフォルトの動作に依存し、Spring の JNDI ルックアップ機能を使用して間接性のレベルを維持することをお勧めします。

明示的な名前が指定されていない @Resource の使用の排他的なケースでは、@Autowired と同様に、@Resource は、特定の名前の Bean ではなくプライマリ型の一致を検出し、よく知られている解決可能な依存関係である BeanFactoryApplicationContextResourceLoaderApplicationEventPublisherMessageSource インターフェースを解決します。

次の例では、customerPreferenceDao フィールドは最初に "customerPreferenceDao" という名前の Bean を検索し、次に型 CustomerPreferenceDao のプライマリ型 マッチにフォールバックします。

Java
public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; (1)

    public MovieRecommender() {
    }

    // ...
}
1context フィールドは、既知の解決可能な依存関係型 ApplicationContext に基づいて挿入されます。
Kotlin
class MovieRecommender {

    @Resource
    private lateinit var customerPreferenceDao: CustomerPreferenceDao


    @Resource
    private lateinit var context: ApplicationContext (1)

    // ...
}
1context フィールドは、既知の解決可能な依存関係型 ApplicationContext に基づいて挿入されます。

1.9.8. @Value を使用する

@Value は通常、外部化されたプロパティを注入するために使用されます。

Java
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name}") String catalog) {
        this.catalog = catalog;
    }
}
Kotlin
@Component
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)

次の構成で:

Java
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
Kotlin
@Configuration
@PropertySource("classpath:application.properties")
class AppConfig

そして、次の application.properties ファイル:

catalog.name=MovieCatalog

その場合、catalog パラメーターとフィールドは MovieCatalog 値と等しくなります。

デフォルトの寛容な埋め込み値リゾルバーは、Spring によって提供されます。プロパティ値を解決しようとしますが、解決できない場合は、プロパティ名(${catalog.name} など)が値として挿入されます。存在しない値を厳密に制御したい場合は、次の例に示すように、PropertySourcesPlaceholderConfigurer Bean を宣言する必要があります。

Java
@Configuration
public class AppConfig {

     @Bean
     public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
           return new PropertySourcesPlaceholderConfigurer();
     }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}
JavaConfig を使用して PropertySourcesPlaceholderConfigurer を構成する場合、@Bean メソッドは static でなければなりません。

上記の構成を使用すると、${} プレースホルダーを解決できなかった場合に、Spring の初期化が失敗することが保証されます。setPlaceholderPrefixsetPlaceholderSuffixsetValueSeparator などのメソッドを使用してプレースホルダーをカスタマイズすることもできます。

Spring Boot は、デフォルトで、application.properties および application.yml ファイルからプロパティを取得する PropertySourcesPlaceholderConfigurer Bean を構成します。

Spring が提供する組み込みコンバーターのサポートにより、単純な型変換(たとえば、Integer または int へ)を自動的に処理できます。複数のコンマ区切り値は、特別な労力をかけることなく、自動的に文字列配列に変換できます。

次のようにデフォルト値を提供することが可能です。

Java
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}
Kotlin
@Component
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)

Spring BeanPostProcessor は、バックグラウンドで ConversionService を使用して、@Value の文字列値をターゲット型に変換するプロセスを処理します。独自のカスタム型の変換サポートを提供する場合は、次の例に示すように、独自の ConversionService Bean インスタンスを提供できます。

Java
@Configuration
public class AppConfig {

    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun conversionService(): ConversionService {
            return DefaultFormattingConversionService().apply {
            addConverter(MyCustomConverter())
        }
    }
}

@Value に SpEL 式が含まれる場合、次の例に示すように、値は実行時に動的に計算されます。

Java
@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}
Kotlin
@Component
class MovieRecommender(
    @Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String)

SpEL は、より複雑なデータ構造の使用も可能にします。

Java
@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}
Kotlin
@Component
class MovieRecommender(
    @Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map<String, Int>)

1.9.9. @PostConstruct および @PreDestroy の使用

CommonAnnotationBeanPostProcessor は、@Resource アノテーションだけでなく、JSR-250 ライフサイクルアノテーション javax.annotation.PostConstruct および javax.annotation.PreDestroy も認識します。Spring 2.5 で導入されたこれらのアノテーションのサポートは、初期化コールバックと破棄コールバックで説明されているライフサイクルコールバックメカニズムの代替手段を提供します。CommonAnnotationBeanPostProcessor が Spring ApplicationContext 内に登録されている場合、これらのアノテーションの 1 つを運ぶメソッドは、対応する Spring ライフサイクルインターフェースメソッドまたは明示的に宣言されたコールバックメソッドと同じライフサイクルのポイントで呼び出されます。次の例では、キャッシュは初期化時に事前入力され、破棄時にクリアされます。

Java
public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}
Kotlin
class CachingMovieLister {

    @PostConstruct
    fun populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    fun clearMovieCache() {
        // clears the movie cache upon destruction...
    }
}

さまざまなライフサイクルメカニズムを組み合わせた効果の詳細については、ライフサイクルメカニズムの組み合わせを参照してください。

@Resource と同様に、@PostConstruct および @PreDestroy アノテーション型は JDK 6 〜 8 の標準 Java ライブラリの一部でしたが、javax.annotation パッケージ全体は JDK 9 のコア Java モジュールから分離され、最終的に JDK 11 で削除されました。javax.annotation-api アーティファクトは、Maven Central を介して取得する必要があります。他のライブラリと同様に、アプリケーションのクラスパスに追加するだけです。

1.10. クラスパススキャンと管理対象コンポーネント

この章のほとんどの例では、XML を使用して、Spring コンテナー内の各 BeanDefinition を生成する構成メタデータを指定します。前のセクション(アノテーションベースのコンテナー構成)は、ソースレベルのアノテーションを使用して多くの構成メタデータを提供する方法を示しています。ただし、これらの例でも、「ベース」の Bean 定義は XML ファイルで明示的に定義されていますが、アノテーションは依存性注入のみを駆動します。このセクションでは、クラスパスをスキャンして候補コンポーネントを暗黙的に検出するオプションについて説明します。候補コンポーネントは、フィルター条件に一致するクラスであり、対応する Bean 定義がコンテナーに登録されています。これにより、Bean 登録を実行するために XML を使用する必要がなくなります。代わりに、アノテーション(たとえば、@Component)、AspectJ 型式、独自のカスタムフィルター条件を使用して、コンテナーに登録された Bean 定義を持つクラスを選択できます。

Spring 3.0 以降、Spring JavaConfig プロジェクトによって提供される多くの機能は、コア Spring Framework の一部です。これにより、従来の XML ファイルを使用するのではなく、Java を使用して Bean を定義できます。これらの新機能の使用方法の例については、@Configuration@Bean@Import@DependsOn アノテーションを参照してください。

1.10.1. @Component およびその他のステレオタイプアノテーション

@Repository アノテーションは、リポジトリ(データアクセスオブジェクトまたは DAO とも呼ばれる)のロールまたはステレオタイプを満たすクラスのマーカーです。このマーカーの用途には、例外変換に従って、例外の自動変換があります。

Spring は、さらにステレオタイプアノテーションを提供します: @Component@Service@Controller@Component は、Spring が管理するコンポーネントの一般的なステレオタイプです。@Repository@Service@Controller は、より具体的なユースケース(それぞれ、永続性、サービス、プレゼンテーション層)向けの @Component の特殊化です。コンポーネントクラスに @Component でアノテーションを付けることができますが、代わりに @Repository@Service@Controller でアノテーションを付けることにより、クラスはツールによる処理やアスペクトへの関連付けにより適しています。例: これらのステレオタイプアノテーションは、ポイントカットの理想的なターゲットになります。@Repository@Service@Controller は、Spring Framework の将来のリリースで追加のセマンティクスを実行することもできます。サービスレイヤーに @Component または @Service のどちらを使用するかを選択する場合は、@Service の方が明らかに優れた選択肢です。同様に、前述のように、@Repository は、永続層での自動例外変換のマーカーとしてすでにサポートされています。

1.10.2. メタアノテーションと合成アノテーションの使用

Spring が提供するアノテーションの多くは、独自のコードでメタアノテーションとして使用できます。メタアノテーションは、別のアノテーションに適用できるアノテーションです。例: 前出の @Service アノテーションは、次の例に示すように、@Component でメタアノテーションが付けられています。

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {

    // ...
}
1Component により、@Service は @Component と同じ方法で処理されます。
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {

    // ...
}
1Component により、@Service は @Component と同じ方法で処理されます。

メタアノテーションを組み合わせて「合成アノテーション」を作成することもできます。例: Spring MVC からの @RestController アノテーションは、@Controller と @ResponseBody で構成されます。

さらに、構成されたアノテーションは、オプションでメタアノテーションから属性を再宣言してカスタマイズを許可できます。これは、メタアノテーションの属性のサブセットのみを公開する場合に特に役立ちます。例: Spring の @SessionScope アノテーションは、スコープ名を session にハードコードしますが、proxyMode のカスタマイズは引き続き可能です。次のリストは、SessionScope アノテーションの定義を示しています。

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {

    /**
     * Alias for {@link Scope#proxyMode}.
     * <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;

}
Kotlin
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
        @get:AliasFor(annotation = Scope::class)
        val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)

その後、proxyMode を次のように宣言せずに @SessionScope を使用できます。

Java
@Service
@SessionScope
public class SessionScopedService {
    // ...
}
Kotlin
@Service
@SessionScope
class SessionScopedService {
    // ...
}

次の例に示すように、proxyMode の値をオーバーライドすることもできます。

Java
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}
Kotlin
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
    // ...
}

詳細については、Spring アノテーションプログラミングモデル [GitHub] (英語) wiki ページを参照してください。

1.10.3. クラスの自動検出と Bean 定義の登録

Spring は、ステレオタイプ化されたクラスを自動的に検出し、対応する BeanDefinition インスタンスを ApplicationContext に登録できます。例: 次の 2 つのクラスは、このような自動検出の対象です。

Java
@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
Kotlin
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
Java
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}
Kotlin
@Repository
class JpaMovieFinder : MovieFinder {
    // implementation elided for clarity
}

これらのクラスを自動検出して対応する Bean を登録するには、@ComponentScan を @Configuration クラスに追加する必要があります。basePackages 属性は 2 つのクラスの共通の親パッケージです。(または、各クラスの親パッケージを含むコンマ区切り、セミコロン区切り、スペース区切りのリストを指定できます。)

Java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig  {
    // ...
}
簡潔にするために、前の例ではアノテーションの value 属性(つまり @ComponentScan("org.example"))を使用できます。

次の代替方法では XML を使用します。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>
<context:component-scan> を使用すると、<context:annotation-config> の機能が暗黙的に有効になります。<context:component-scan> を使用する場合、通常 <context:annotation-config> 要素を含める必要はありません。

クラスパスパッケージをスキャンするには、対応するディレクトリエントリがクラスパスに存在する必要があります。Ant を使用して JAR をビルドする場合は、JAR タスクのファイルのみのスイッチをアクティブにしないでください。また、一部の環境では、セキュリティポリシーに基づいてクラスパスディレクトリが公開されない場合があります。たとえば、JDK 1.7.0_45 以降のスタンドアロンアプリ(マニフェストで 'Trusted-Library' の設定が必要です。https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources (英語) を参照してください)。

JDK 9 のモジュールパス(Jigsaw)では、Spring のクラスパススキャンは通常期待どおりに機能します。ただし、コンポーネントクラスが module-info 記述子でエクスポートされていることを確認してください。Spring がクラスの非パブリックメンバーを呼び出すことが予想される場合は、それらが「開かれている」ことを確認してください(つまり、module-info 記述子で exports 宣言の代わりに opens 宣言を使用する)。

さらに、コンポーネントスキャン要素を使用すると、AutowiredAnnotationBeanPostProcessor と CommonAnnotationBeanPostProcessor の両方が暗黙的に含まれます。つまり、2 つのコンポーネントは自動的に検出され、相互に接続されます。すべて XML で提供される Bean 構成メタデータはありません。

false の値を持つ annotation-config 属性を含めることにより、AutowiredAnnotationBeanPostProcessor および CommonAnnotationBeanPostProcessor の登録を無効にできます。

1.10.4. フィルターを使用してスキャンをカスタマイズする

デフォルトでは、@Component@Repository@Service@Controller@Configuration でアノテーションが付けられたクラス、または @Component でアノテーションが付けられたカスタムアノテーションのみが検出された候補コンポーネントです。ただし、カスタムフィルターを適用することにより、この動作を変更および拡張できます。@ComponentScan アノテーションの includeFilters または excludeFilters 属性として(または XML 構成の <context:component-scan> 要素の <context:include-filter /> または <context:exclude-filter /> 子要素として)追加します。各フィルター要素には、type および expression 属性が必要です。次の表で、フィルタリングオプションについて説明します。

表 5: 型のフィルター
フィルタータイプ 式の例 説明

アノテーション (default)

org.example.SomeAnnotation

ターゲットコンポーネントの型レベルで存在またはメタ表示するアノテーション。

assignable

org.example.SomeClass

ターゲットコンポーネントが割り当てられる(拡張または実装する)クラス(またはインターフェース)。

aspectj

org.example..*Service+

ターゲットコンポーネントによって照合される AspectJ 型式。

regex

org\.example\.Default.*

ターゲットコンポーネントのクラス名と一致する正規表現。

custom

org.example.MyTypeFilter

org.springframework.core.type.TypeFilter インターフェースのカスタム実装。

次の例は、すべての @Repository アノテーションを無視し、代わりに「スタブ」リポジトリを使用する構成を示しています。

Java
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
        excludeFilters = [Filter(Repository::class)])
class AppConfig {
    // ...
}

次のリストは、同等の XML を示しています。

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>
アノテーションに useDefaultFilters=false を設定するか、<component-scan/> 要素の属性として use-default-filters="false" を指定することにより、デフォルトのフィルターを無効にすることもできます。これにより、@Component@Repository@Service@Controller@RestController または @Configuration でアノテーションが付けられたクラスまたはメタアノテーションが付けられたクラスの自動検出が事実上無効になります。

1.10.5. コンポーネント内での Bean メタデータの定義

Spring コンポーネントは、Bean 定義メタデータをコンテナーに提供することもできます。これは、@Configuration アノテーション付きクラス内で Bean メタデータを定義するために使用されるのと同じ @Bean アノテーションを使用して行うことができます。次の例は、その方法を示しています。

Java
@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }
}
Kotlin
@Component
class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    fun publicInstance() = TestBean("publicInstance")

    fun doWork() {
        // Component method implementation omitted
    }
}

上記のクラスは、doWork() メソッドにアプリケーション固有のコードを持つ Spring コンポーネントです。ただし、メソッド publicInstance() を参照するファクトリメソッドを持つ Bean 定義にも貢献します。@Bean アノテーションは、ファクトリメソッドおよびその他の Bean 定義プロパティ(@Qualifier アノテーションによる修飾子値など)を識別します。指定できるその他のメソッドレベルのアノテーションは、@Scope@Lazy、カスタム修飾子アノテーションです。

コンポーネントの初期化のロールに加えて、@Autowired または @Inject でマークされたインジェクションポイントに @Lazy アノテーションを配置することもできます。このコンテキストでは、遅延解決プロキシの挿入につながります。

前述のように、@Bean メソッドのオートワイヤーの追加サポートとともに、オートワイヤーフィールドとメソッドがサポートされています。次の例は、その方法を示しています。

Java
@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}
Kotlin
@Component
class FactoryMethodComponent {

    companion object {
        private var i: Int = 0
    }

    @Bean
    @Qualifier("public")
    fun publicInstance() = TestBean("publicInstance")

    // use of a custom qualifier and autowiring of method parameters
    @Bean
    protected fun protectedInstance(
            @Qualifier("public") spouse: TestBean,
            @Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
        this.spouse = spouse
        this.country = country
    }

    @Bean
    private fun privateInstance() = TestBean("privateInstance", i++)

    @Bean
    @RequestScope
    fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}

この例では、String メソッドパラメーター country を、privateInstance という名前の別の Bean の age プロパティの値に自動接続します。Spring Expression Language エレメントは、表記 #{ <expression> } を介してプロパティの値を定義します。@Value アノテーションの場合、式テキストを解決するときに Bean 名を検索するように式リゾルバーが事前構成されています。

Spring Framework 4.3 以降では、型 InjectionPoint (またはそのより具象サブクラス: DependencyDescriptor)のファクトリメソッドパラメーターを宣言して、現在の Bean の作成をトリガーするリクエストしているインジェクションポイントにアクセスすることもできます。これは、Bean インスタンスの実際の作成にのみ適用され、既存のインスタンスの挿入には適用されないことに注意してください。結果として、この機能はプロトタイプスコープの Bean に最も意味があります。他のスコープの場合、ファクトリメソッドは、指定されたスコープで新しい Bean インスタンスの作成をトリガーしたインジェクションポイントのみを確認します(たとえば、遅延シングルトン Bean の作成をトリガーした依存関係)。このようなシナリオでは、提供されたインジェクションポイントメタデータをセマンティックケアで使用できます。次の例は、InjectionPoint の使用方法を示しています。

Java
@Component
public class FactoryMethodComponent {

    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}
Kotlin
@Component
class FactoryMethodComponent {

    @Bean
    @Scope("prototype")
    fun prototypeInstance(injectionPoint: InjectionPoint) =
            TestBean("prototypeInstance for ${injectionPoint.member}")
}

通常の Spring コンポーネントの @Bean メソッドは、Spring @Configuration クラス内の対応する @Bean メソッドとは異なる方法で処理されます。違いは、@Component クラスがメソッドとフィールドの呼び出しをインターセプトするために CGLIB で拡張されていないことです。CGLIB プロキシは、@Configuration クラスの @Bean メソッド内のメソッドまたはフィールドを呼び出すことにより、コラボレーションオブジェクトへの Bean メタデータ参照を作成する手段です。このようなメソッドは、通常の Java セマンティクスで呼び出されるのではなく、@Bean メソッドのプログラム呼び出しで他の Bean を参照する場合でも、Spring Bean の通常のライフサイクル管理とプロキシを提供するためにコンテナーを通過します。対照的に、プレーン @Component クラス内の @Bean メソッドでメソッドまたはフィールドを呼び出すには、標準の Java セマンティクスがあり、特別な CGLIB 処理やその他の制約は適用されません。

@Bean メソッドを static として宣言すると、含む構成クラスをインスタンスとして作成せずに呼び出すことができます。これは、ポストプロセッサー Bean(たとえば、型 BeanFactoryPostProcessor または BeanPostProcessor)を定義するときに特に意味があります。そのような Bean は、コンテナーライフサイクルの初期に初期化され、その時点で構成の他の部分をトリガーしないようにする必要があるためです

静的な @Bean メソッドの呼び出しは、技術的な制限のため、コンテナーによって(このセクションで前述したように) @Configuration クラス内でさえもインターセプトされません。CGLIB サブクラス化は、非静的メソッドのみをオーバーライドできます。その結果、別の @Bean メソッドへの直接呼び出しには標準の Java セマンティクスがあり、その結果、独立したインスタンスがファクトリメソッド自体から直接返されます。

@Bean メソッドの Java 言語の可視性は、Spring のコンテナーで生成される Bean 定義に直接的な影響を与えません。@Configuration 以外のクラスに収まると思われるように、またどこにいても静的メソッドに適合するように、ファクトリメソッドを自由に宣言できます。ただし、@Configuration クラスの通常の @Bean メソッドはオーバーライド可能である必要があります。つまり、private または final として宣言してはなりません。

@Bean メソッドは、特定のコンポーネントまたは構成クラスの基本クラス、およびコンポーネントまたは構成クラスによって実装されるインターフェースで宣言された Java 8 デフォルトメソッドでも検出されます。これにより、Spring 4.2 以降の Java 8 のデフォルトのメソッドを使用して複数の継承も可能になり、複雑な構成の配置を柔軟に構成できます。

最後に、実行時に利用可能な依存関係に応じて使用する複数のファクトリメソッドの配置として、単一のクラスが同じ Bean に対して複数の @Bean メソッドを保持する場合があります。これは、他の構成シナリオで「最も貪欲な」コンストラクターまたはファクトリメソッドを選択する場合と同じアルゴリズムです。コンテナーが複数の @Autowired コンストラクターを選択する方法に類似して、充足可能な依存関係の数が最も多いバリアントが構築時に選択されます。

1.10.6. 自動検出されたコンポーネントの命名

コンポーネントがスキャンプロセスの一部として自動検出されると、その Bean 名は、そのスキャナーに認識されている BeanNameGenerator 戦略によって生成されます。デフォルトでは、value という名前を含む Spring ステレオタイプアノテーション(@Component@Repository@Service@Controller)は、対応する Bean 定義にその名前を提供します。

そのようなアノテーションに名前 value が含まれていない場合、またはその他の検出されたコンポーネント(カスタムフィルターによって検出されたコンポーネントなど)の場合、デフォルトの Bean 名前ジェネレーターは大文字ではない非修飾クラス名を返します。例: 次のコンポーネントクラスが検出された場合、名前は myMovieLister および movieFinderImpl になります。

Java
@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
Kotlin
@Service("myMovieLister")
class SimpleMovieLister {
    // ...
}
Java
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
Kotlin
@Repository
class MovieFinderImpl : MovieFinder {
    // ...
}

デフォルトの Bean 命名戦略に依存したくない場合は、カスタムの Bean 命名戦略を提供できます。まず、BeanNameGenerator (Javadoc) インターフェースを実装し、デフォルトの引数なしのコンストラクターを必ず含めてください。次に、以下のアノテーションの例と Bean 定義が示すように、スキャナーの構成時に完全修飾クラス名を指定します。

修飾されていない同じクラス名を持つ複数の自動検出されたコンポーネント(つまり、名前が同じで異なるパッケージにあるクラス)が原因で名前の競合が発生した場合は、デフォルトで完全修飾クラス名になる BeanNameGenerator を構成する必要があります。生成された Bean 名。Spring Framework 5.2.3 以降、パッケージ org.springframework.context.annotation にある FullyQualifiedAnnotationBeanNameGenerator をそのような目的に使用できます。
Java
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

一般的な規則として、他のコンポーネントが明示的に参照している場合は、アノテーションで名前を指定することを検討してください。一方、コンテナーが接続を担当する場合は、自動生成された名前で十分です。

1.10.7. 自動検出されたコンポーネントのスコープを提供する

一般に Spring 管理コンポーネントと同様に、自動検出されたコンポーネントのデフォルトで最も一般的なスコープは singleton です。ただし、@Scope アノテーションで指定できる別のスコープが必要になる場合があります。次の例に示すように、アノテーション内でスコープの名前を指定できます。

Java
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
Kotlin
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
    // ...
}
@Scope アノテーションは、具体的な Bean クラス(アノテーション付きコンポーネントの場合)またはファクトリメソッド(@Bean メソッドの場合)でのみイントロスペクトされます。XML Bean 定義とは対照的に、Bean 定義の継承という概念はなく、クラスレベルの継承階層はメタデータの目的には関係ありません。

Spring コンテキストでの「リクエスト」や「セッション」などの Web 固有のスコープの詳細については、リクエスト、セッション、アプリケーション、WebSocket スコープを参照してください。これらのスコープ用に事前に作成されたアノテーションと同様に、Spring のメタアノテーションアプローチを使用して独自のスコープアノテーションを作成することもできます。たとえば、@Scope("prototype") でアノテーションが付けられたカスタムアノテーションメタは、カスタムスコーププロキシモードを宣言することもできます。

アノテーションベースのアプローチに依存するのではなく、スコープ解決のカスタム戦略を提供するために、ScopeMetadataResolver (Javadoc) インターフェースを実装できます。デフォルトの引数なしのコンストラクターを必ず含めてください。次に、以下のアノテーションと Bean 定義の例に示すように、スキャナーの構成時に完全修飾クラス名を指定できます。
Java
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

特定の非シングルトンスコープを使用する場合、スコープオブジェクトのプロキシを生成する必要がある場合があります。推論は依存関係としてのスコープ Bean で説明されています。この目的のために、scoped-proxy 属性を component-scan 要素で使用できます。可能な 3 つの値は次のとおりです。nointerfacestargetClass。例: 次の構成では、標準の JDK 動的プロキシが生成されます。

Java
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

1.10.8. アノテーション付きの修飾子メタデータの提供

@Qualifier アノテーションについては、修飾子を使用したアノテーションベースのオートワイヤーの微調整で説明しています。そのセクションの例では、@Qualifier アノテーションとカスタム修飾子アノテーションを使用して、オートワイヤーの候補を解決するときにきめ細かな制御を提供します。これらの例は XML Bean 定義に基づいているため、XML の bean 要素の qualifier または meta 子要素を使用して、候補の Bean 定義に修飾子メタデータが提供されました。コンポーネントの自動検出をクラスパススキャンに依存している場合、候補クラスの型レベルのアノテーションを修飾子メタデータに提供できます。次の 3 つの例は、この手法を示しています。

Java
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
Kotlin
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
Java
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
Kotlin
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
    // ...
}
Java
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}
Kotlin
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
    // ...
}
ほとんどのアノテーションベースの代替方法と同様に、XML を使用すると、同じ型の複数の Bean が修飾子メタデータのバリエーションを提供できる一方で、アノテーションメタデータはクラス定義自体にバインドされることに留意してください。クラスごとではなくインスタンス。

1.10.9. 候補コンポーネントのインデックスの生成

クラスパススキャンは非常に高速ですが、コンパイル時に候補の静的リストを作成することで、大規模なアプリケーションの起動パフォーマンスを向上させることができます。このモードでは、コンポーネントスキャンの対象となるすべてのモジュールがこのメカニズムを使用する必要があります。

特定のパッケージ内の候補をスキャンするコンテキストをリクエストするには、既存の @ComponentScan または <context:component-scan/> ディレクティブを変更しないでおく必要があります。ApplicationContext はそのようなインデックスを検出すると、クラスパスをスキャンするのではなく、自動的にそれを使用します。

インデックスを生成するには、コンポーネントスキャンディレクティブのターゲットであるコンポーネントを含む各モジュールに追加の依存関係を追加します。次の例は、Maven でこれを行う方法を示しています。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.3.8</version>
        <optional>true</optional>
    </dependency>
</dependencies>

Gradle 4.5 以前では、次の例に示すように、compileOnly 構成で依存関係を宣言する必要があります。

dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.3.8"
}

Gradle 4.6 以降では、次の例に示すように、annotationProcessor 構成で依存関係を宣言する必要があります。

dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}

spring-context-indexer アーティファクトは、jar ファイルに含まれる META-INF/spring.components ファイルを生成します。

IDE でこのモードを使用する場合は、spring-context-indexer をアノテーションプロセッサーとして登録して、候補コンポーネントが更新されたときにインデックスが最新であることを確認する必要があります。
クラスパスで META-INF/spring.components ファイルが見つかると、インデックスが自動的に有効になります。一部のライブラリ(またはユースケース)についてはインデックスが部分的に利用可能ですが、アプリケーション全体については構築できなかった場合、JVM システムプロパティとして、または SpringProperties メカニズムを通じて spring.index.ignore から true を設定することにより、(インデックスがまったく存在しないかのように)通常のクラスパス配置に戻ることができます。

1.11. JSR 330 標準アノテーションの使用

Spring 3.0 以降、Spring は JSR-330 標準アノテーション(依存性注入)をサポートしています。これらのアノテーションは、Spring アノテーションと同じ方法でスキャンされます。使用するには、クラスパスに関連する jar が必要です。

Maven を使用する場合、javax.inject アーティファクトは標準 Maven リポジトリ(https://repo1.maven.org/maven2/javax/inject/javax.inject/1/ (英語) )で使用可能です。ファイル pom.xml に次の依存関係を追加できます。

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

1.11.1. @Inject および @Named による依存性注入

@Autowired の代わりに、次のように @javax.inject.Inject を使用できます。

Java
import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.findMovies(...);
        // ...
    }
}
Kotlin
import javax.inject.Inject

class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder


    fun listMovies() {
        movieFinder.findMovies(...)
        // ...
    }
}

@Autowired と同様に、フィールドレベル、メソッドレベル、コンストラクター引数レベルで @Inject を使用できます。さらに、インジェクションポイントを Provider として宣言して、Provider.get() 呼び出しを介して、より短いスコープの Bean へのオンデマンドアクセスまたは他の Bean への遅延アクセスを許可することができます。次の例は、前述の例の変形を示しています。

Java
import javax.inject.Inject;
import javax.inject.Provider;

public class SimpleMovieLister {

    private Provider<MovieFinder> movieFinder;

    @Inject
    public void setMovieFinder(Provider<MovieFinder> movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void listMovies() {
        this.movieFinder.get().findMovies(...);
        // ...
    }
}
Kotlin
import javax.inject.Inject

class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder


    fun listMovies() {
        movieFinder.findMovies(...)
        // ...
    }
}

挿入する依存関係に修飾名を使用する場合は、次の例に示すように、@Named アノテーションを使用する必要があります。

Java
import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
Kotlin
import javax.inject.Inject
import javax.inject.Named

class SimpleMovieLister {

    private lateinit var movieFinder: MovieFinder

    @Inject
    fun setMovieFinder(@Named("main") movieFinder: MovieFinder) {
        this.movieFinder = movieFinder
    }

    // ...
}

@Autowired と同様に、@Inject は java.util.Optional または @Nullable でも使用できます。@Inject には required 属性がないため、これはさらに適切です。次の例のペアは、@Inject と @Nullable の使用方法を示しています。

public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        // ...
    }
}
Java
public class SimpleMovieLister {

    @Inject
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        // ...
    }
}
Kotlin
class SimpleMovieLister {

    @Inject
    var movieFinder: MovieFinder? = null
}

1.11.2. @Named および @ManagedBean@Component アノテーションの標準的な同等物

@Component の代わりに、次の例に示すように、@javax.inject.Named または javax.annotation.ManagedBean を使用できます。

Java
import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
Kotlin
import javax.inject.Inject
import javax.inject.Named

@Named("movieListener")  // @ManagedBean("movieListener") could be used as well
class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder

    // ...
}

コンポーネントの名前を指定せずに @Component を使用することは非常に一般的です。@Named は、次の例に示すように、同様の方法で使用できます。

Java
import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}
Kotlin
import javax.inject.Inject
import javax.inject.Named

@Named
class SimpleMovieLister {

    @Inject
    lateinit var movieFinder: MovieFinder

    // ...
}

@Named または @ManagedBean を使用する場合、次の例に示すように、Spring アノテーションを使用する場合とまったく同じ方法でコンポーネントスキャンを使用できます。

Java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig  {
    // ...
}
@Component とは対照的に、JSR-330 @Named および JSR-250 ManagedBean アノテーションは作成できません。カスタムコンポーネントアノテーションを作成するには、Spring のステレオタイプモデルを使用する必要があります。

1.11.3. JSR-330 標準アノテーションの制限

標準のアノテーションを使用する場合、次の表に示すように、いくつかの重要な機能が利用できないことを知っておく必要があります。

表 6: Spring コンポーネントモデル要素と JSR-330 バリアント
Springjavax.inject.*javax.inject の制限 / コメント

@Autowired

@Inject

@Inject has no 'required' attribute. Can be used with Java 8’s Optional instead.

@Component

@Named / @ManagedBean

JSR-330 does not provide a composable model, only a way to identify named components.

@Scope("singleton")

@Singleton

The JSR-330 default scope is like Spring’s prototype. However, in order to keep it consistent with Spring’s general defaults, a JSR-330 bean declared in the Spring container is a singleton by default. In order to use a scope other than singleton, you should use Spring’s @Scope annotation. javax.inject also provides a @Scope [Oracle] (英語) annotation. Nevertheless, this one is only intended to be used for creating your own annotations.

@Qualifier

@Qualifier / @Named

javax.inject.Qualifier is just a meta-annotation for building custom qualifiers. Concrete String qualifiers (like Spring’s @Qualifier with a value) can be associated through javax.inject.Named.

@Value

-

no equivalent

@Required

-

no equivalent

@Lazy

-

no equivalent

ObjectFactory

Provider

javax.inject.Provider is a direct alternative to Spring’s ObjectFactory, only with a shorter get() method name. It can also be used in combination with Spring’s @Autowired or with non-annotated constructors and setter methods.

1.12. Java-based Container Configuration

This section covers how to use annotations in your Java code to configure the Spring container. It includes the following topics:

1.12.1. Basic Concepts: @Bean and @Configuration

The central artifacts in Spring’s new Java-configuration support are @Configuration-annotated classes and @Bean-annotated methods.

The @Bean annotation is used to indicate that a method instantiates, configures, and initializes a new object to be managed by the Spring IoC container. For those familiar with Spring’s <beans/> XML configuration, the @Bean annotation plays the same role as the <bean/> element. You can use @Bean-annotated methods with any Spring @Component. However, they are most often used with @Configuration beans.

Annotating a class with @Configuration indicates that its primary purpose is as a source of bean definitions. Furthermore, @Configuration classes let inter-bean dependencies be defined by calling other @Bean methods in the same class. The simplest possible @Configuration class reads as follows:

Java
@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun myService(): MyService {
        return MyServiceImpl()
    }
}

The preceding AppConfig class is equivalent to the following Spring <beans/> XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
Full @Configuration vs “lite” @Bean mode?

When @Bean methods are declared within classes that are not annotated with @Configuration, they are referred to as being processed in a “lite” mode. Bean methods declared in a @Component or even in a plain old class are considered to be “lite”, with a different primary purpose of the containing class and a @Bean method being a sort of bonus there. For example, service components may expose management views to the container through an additional @Bean method on each applicable component class. In such scenarios, @Bean methods are a general-purpose factory method mechanism.

Unlike full @Configuration, lite @Bean methods cannot declare inter-bean dependencies. Instead, they operate on their containing component’s internal state and, optionally, on arguments that they may declare. Such a @Bean method should therefore not invoke other @Bean methods. Each such method is literally only a factory method for a particular bean reference, without any special runtime semantics. The positive side-effect here is that no CGLIB subclassing has to be applied at runtime, so there are no limitations in terms of class design (that is, the containing class may be final and so forth).

In common scenarios, @Bean methods are to be declared within @Configuration classes, ensuring that “full” mode is always used and that cross-method references therefore get redirected to the container’s lifecycle management. This prevents the same @Bean method from accidentally being invoked through a regular Java call, which helps to reduce subtle bugs that can be hard to track down when operating in “lite” mode.

The @Bean and @Configuration annotations are discussed in depth in the following sections. First, however, we cover the various ways of creating a spring container using by Java-based configuration.

1.12.2. Instantiating the Spring Container by Using AnnotationConfigApplicationContext

The following sections document Spring’s AnnotationConfigApplicationContext, introduced in Spring 3.0. This versatile ApplicationContext implementation is capable of accepting not only @Configuration classes as input but also plain @Component classes and classes annotated with JSR-330 metadata.

When @Configuration classes are provided as input, the @Configuration class itself is registered as a bean definition and all declared @Bean methods within the class are also registered as bean definitions.

When @Component and JSR-330 classes are provided, they are registered as bean definitions, and it is assumed that DI metadata such as @Autowired or @Inject are used within those classes where necessary.

Simple Construction

In much the same way that Spring XML files are used as input when instantiating a ClassPathXmlApplicationContext, you can use @Configuration classes as input when instantiating an AnnotationConfigApplicationContext. This allows for completely XML-free usage of the Spring container, as the following example shows:

Java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}

As mentioned earlier, AnnotationConfigApplicationContext is not limited to working only with @Configuration classes. Any @Component or JSR-330 annotated class may be supplied as input to the constructor, as the following example shows:

Java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}

The preceding example assumes that MyServiceImpl, Dependency1, and Dependency2 use Spring dependency injection annotations such as @Autowired.

Building the Container Programmatically by Using register(Class<?>…​)

You can instantiate an AnnotationConfigApplicationContext by using a no-arg constructor and then configure it by using the register() method. This approach is particularly useful when programmatically building an AnnotationConfigApplicationContext. The following example shows how to do so:

Java
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.register(AppConfig::class.java, OtherConfig::class.java)
    ctx.register(AdditionalConfig::class.java)
    ctx.refresh()
    val myService = ctx.getBean<MyService>()
    myService.doStuff()
}
Enabling Component Scanning with scan(String…​)

To enable component scanning, you can annotate your @Configuration class as follows:

Java
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig  {
    ...
}
1 This annotation enables component scanning.
Kotlin
@Configuration
@ComponentScan(basePackages = ["com.acme"]) (1)
class AppConfig  {
    // ...
}
1 This annotation enables component scanning.

Experienced Spring users may be familiar with the XML declaration equivalent from Spring’s context: namespace, shown in the following example:

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

In the preceding example, the com.acme package is scanned to look for any @Component-annotated classes, and those classes are registered as Spring bean definitions within the container. AnnotationConfigApplicationContext exposes the scan(String…​) method to allow for the same component-scanning functionality, as the following example shows:

Java
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
Kotlin
fun main() {
    val ctx = AnnotationConfigApplicationContext()
    ctx.scan("com.acme")
    ctx.refresh()
    val myService = ctx.getBean<MyService>()
}
Remember that @Configuration classes are meta-annotated with @Component, so they are candidates for component-scanning. In the preceding example, assuming that AppConfig is declared within the com.acme package (or any package underneath), it is picked up during the call to scan(). Upon refresh(), all its @Bean methods are processed and registered as bean definitions within the container.
Support for Web Applications with AnnotationConfigWebApplicationContext

A WebApplicationContext variant of AnnotationConfigApplicationContext is available with AnnotationConfigWebApplicationContext. You can use this implementation when configuring the Spring ContextLoaderListener servlet listener, Spring MVC DispatcherServlet, and so forth. The following web.xml snippet configures a typical Spring MVC web application (note the use of the contextClass context-param and init-param):

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

1.12.3. Using the @Bean Annotation

@Bean is a method-level annotation and a direct analog of the XML <bean/> element. The annotation supports some of the attributes offered by <bean/>, such as: * init-method * destroy-method * autowiring * name.

You can use the @Bean annotation in a @Configuration-annotated or in a @Component-annotated class.

Declaring a Bean

To declare a bean, you can annotate a method with the @Bean annotation. You use this method to register a bean definition within an ApplicationContext of the type specified as the method’s return value. By default, the bean name is the same as the method name. The following example shows a @Bean method declaration:

Java
@Configuration
public class AppConfig {

    @Bean
    public TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun transferService() = TransferServiceImpl()
}

The preceding configuration is exactly equivalent to the following Spring XML:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

Both declarations make a bean named transferService available in the ApplicationContext, bound to an object instance of type TransferServiceImpl, as the following text image shows:

transferService -> com.acme.TransferServiceImpl

You can also declare your @Bean method with an interface (or base class) return type, as the following example shows:

Java
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl()
    }
}

However, this limits the visibility for advance type prediction to the specified interface type (TransferService). Then, with the full type (TransferServiceImpl) known to the container only once, the affected singleton bean has been instantiated. Non-lazy singleton beans get instantiated according to their declaration order, so you may see different type matching results depending on when another component tries to match by a non-declared type (such as @Autowired TransferServiceImpl, which resolves only once the transferService bean has been instantiated).

If you consistently refer to your types by a declared service interface, your @Bean return types may safely join that design decision. However, for components that implement several interfaces or for components potentially referred to by their implementation type, it is safer to declare the most specific return type possible (at least as specific as required by the injection points that refer to your bean).
Bean Dependencies

A @Bean-annotated method can have an arbitrary number of parameters that describe the dependencies required to build that bean. For instance, if our TransferService requires an AccountRepository, we can materialize that dependency with a method parameter, as the following example shows:

Java
@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

The resolution mechanism is pretty much identical to constructor-based dependency injection. See the relevant section for more details.

Receiving Lifecycle Callbacks

Any classes defined with the @Bean annotation support the regular lifecycle callbacks and can use the @PostConstruct and @PreDestroy annotations from JSR-250. See JSR-250 annotations for further details.

The regular Spring lifecycle callbacks are fully supported as well. If a bean implements InitializingBean, DisposableBean, or Lifecycle, their respective methods are called by the container.

The standard set of *Aware interfaces (such as BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware, and so on) are also fully supported.

The @Bean annotation supports specifying arbitrary initialization and destruction callback methods, much like Spring XML’s init-method and destroy-method attributes on the bean element, as the following example shows:

Java
public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
Kotlin
class BeanOne {

    fun init() {
        // initialization logic
    }
}

class BeanTwo {

    fun cleanup() {
        // destruction logic
    }
}

@Configuration
class AppConfig {

    @Bean(initMethod = "init")
    fun beanOne() = BeanOne()

    @Bean(destroyMethod = "cleanup")
    fun beanTwo() = BeanTwo()
}

By default, beans defined with Java configuration that have a public close or shutdown method are automatically enlisted with a destruction callback. If you have a public close or shutdown method and you do not wish for it to be called when the container shuts down, you can add @Bean(destroyMethod="") to your bean definition to disable the default (inferred) mode.

You may want to do that by default for a resource that you acquire with JNDI, as its lifecycle is managed outside the application. In particular, make sure to always do it for a DataSource, as it is known to be problematic on Java EE application servers.

The following example shows how to prevent an automatic destruction callback for a DataSource:

Java
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
    return (DataSource) jndiTemplate.lookup("MyDS");
}
Kotlin
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
    return jndiTemplate.lookup("MyDS") as DataSource
}

Also, with @Bean methods, you typically use programmatic JNDI lookups, either by using Spring’s JndiTemplate or JndiLocatorDelegate helpers or straight JNDI InitialContext usage but not the JndiObjectFactoryBean variant (which would force you to declare the return type as the FactoryBean type instead of the actual target type, making it harder to use for cross-reference calls in other @Bean methods that intend to refer to the provided resource here).

In the case of BeanOne from the example above the preceding note, it would be equally valid to call the init() method directly during construction, as the following example shows:

Java
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun beanOne() = BeanOne().apply {
        init()
    }

    // ...
}
When you work directly in Java, you can do anything you like with your objects and do not always need to rely on the container lifecycle.
Specifying Bean Scope

Spring includes the @Scope annotation so that you can specify the scope of a bean.

Using the @Scope Annotation

You can specify that your beans defined with the @Bean annotation should have a specific scope. You can use any of the standard scopes specified in the Bean Scopes section.

The default scope is singleton, but you can override this with the @Scope annotation, as the following example shows:

Java
@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}
Kotlin
@Configuration
class MyConfiguration {

    @Bean
    @Scope("prototype")
    fun encryptor(): Encryptor {
        // ...
    }
}
@Scope and scoped-proxy

Spring offers a convenient way of working with scoped dependencies through scoped proxies. The easiest way to create such a proxy when using the XML configuration is the <aop:scoped-proxy/> element. Configuring your beans in Java with a @Scope annotation offers equivalent support with the proxyMode attribute. The default is ScopedProxyMode.DEFAULT, which typically indicates that no scoped proxy should be created unless a different default has been configured at the component-scan instruction level. You can specify ScopedProxyMode.TARGET_CLASS, ScopedProxyMode.INTERFACES or ScopedProxyMode.NO.

If you port the scoped proxy example from the XML reference documentation (see scoped proxies) to our @Bean using Java, it resembles the following:

Java
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}
Kotlin
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()

@Bean
fun userService(): Service {
    return SimpleUserService().apply {
        // a reference to the proxied userPreferences bean
        setUserPreferences(userPreferences())
    }
}
Customizing Bean Naming

By default, configuration classes use a @Bean method’s name as the name of the resulting bean. This functionality can be overridden, however, with the name attribute, as the following example shows:

Java
@Configuration
public class AppConfig {

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean("myThing")
    fun thing() = Thing()
}
Bean Aliasing

As discussed in Naming Beans, it is sometimes desirable to give a single bean multiple names, otherwise known as bean aliasing. The name attribute of the @Bean annotation accepts a String array for this purpose. The following example shows how to set a number of aliases for a bean:

Java
@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
    fun dataSource(): DataSource {
        // instantiate, configure and return DataSource bean...
    }
}
Bean Description

Sometimes, it is helpful to provide a more detailed textual description of a bean. This can be particularly useful when beans are exposed (perhaps through JMX) for monitoring purposes.

To add a description to a @Bean, you can use the @Description (Javadoc) annotation, as the following example shows:

Java
@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    fun thing() = Thing()
}

1.12.4. Using the @Configuration annotation

@Configuration is a class-level annotation indicating that an object is a source of bean definitions. @Configuration classes declare beans through @Bean annotated methods. Calls to @Bean methods on @Configuration classes can also be used to define inter-bean dependencies. See Basic Concepts: @Bean and @Configuration for a general introduction.

Injecting Inter-bean Dependencies

When beans have dependencies on one another, expressing that dependency is as simple as having one bean method call another, as the following example shows:

Java
@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun beanOne() = BeanOne(beanTwo())

    @Bean
    fun beanTwo() = BeanTwo()
}

In the preceding example, beanOne receives a reference to beanTwo through constructor injection.

This method of declaring inter-bean dependencies works only when the @Bean method is declared within a @Configuration class. You cannot declare inter-bean dependencies by using plain @Component classes.
Lookup Method Injection

As noted earlier, lookup method injection is an advanced feature that you should use rarely. It is useful in cases where a singleton-scoped bean has a dependency on a prototype-scoped bean. Using Java for this type of configuration provides a natural means for implementing this pattern. The following example shows how to use lookup method injection:

Java
public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}
Kotlin
abstract class CommandManager {
    fun process(commandState: Any): Any {
        // grab a new instance of the appropriate Command interface
        val command = createCommand()
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState)
        return command.execute()
    }

    // okay... but where is the implementation of this method?
    protected abstract fun createCommand(): Command
}

By using Java configuration, you can create a subclass of CommandManager where the abstract createCommand() method is overridden in such a way that it looks up a new (prototype) command object. The following example shows how to do so:

Java
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}
Kotlin
@Bean
@Scope("prototype")
fun asyncCommand(): AsyncCommand {
    val command = AsyncCommand()
    // inject dependencies here as required
    return command
}

@Bean
fun commandManager(): CommandManager {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return object : CommandManager() {
        override fun createCommand(): Command {
            return asyncCommand()
        }
    }
}
Further Information About How Java-based Configuration Works Internally

Consider the following example, which shows a @Bean annotated method being called twice:

Java
@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun clientService1(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }

    @Bean
    fun clientService2(): ClientService {
        return ClientServiceImpl().apply {
            clientDao = clientDao()
        }
    }

    @Bean
    fun clientDao(): ClientDao {
        return ClientDaoImpl()
    }
}

clientDao() has been called once in clientService1() and once in clientService2(). Since this method creates a new instance of ClientDaoImpl and returns it, you would normally expect to have two instances (one for each service). That definitely would be problematic: In Spring, instantiated beans have a singleton scope by default. This is where the magic comes in: All @Configuration classes are subclassed at startup-time with CGLIB. In the subclass, the child method checks the container first for any cached (scoped) beans before it calls the parent method and creates a new instance.

The behavior could be different according to the scope of your bean. We are talking about singletons here.

As of Spring 3.2, it is no longer necessary to add CGLIB to your classpath because CGLIB classes have been repackaged under org.springframework.cglib and included directly within the spring-core JAR.

There are a few restrictions due to the fact that CGLIB dynamically adds features at startup-time. In particular, configuration classes must not be final. However, as of 4.3, any constructors are allowed on configuration classes, including the use of @Autowired or a single non-default constructor declaration for default injection.

If you prefer to avoid any CGLIB-imposed limitations, consider declaring your @Bean methods on non-@Configuration classes (for example, on plain @Component classes instead). Cross-method calls between @Bean methods are not then intercepted, so you have to exclusively rely on dependency injection at the constructor or method level there.

1.12.5. Composing Java-based Configurations

Spring’s Java-based configuration feature lets you compose annotations, which can reduce the complexity of your configuration.

Using the @Import Annotation

Much as the <import/> element is used within Spring XML files to aid in modularizing configurations, the @Import annotation allows for loading @Bean definitions from another configuration class, as the following example shows:

Java
@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}
Kotlin
@Configuration
class ConfigA {

    @Bean
    fun a() = A()
}

@Configuration
@Import(ConfigA::class)
class ConfigB {

    @Bean
    fun b() = B()
}

Now, rather than needing to specify both ConfigA.class and ConfigB.class when instantiating the context, only ConfigB needs to be supplied explicitly, as the following example shows:

Java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)

    // now both beans A and B will be available...
    val a = ctx.getBean<A>()
    val b = ctx.getBean<B>()
}

This approach simplifies container instantiation, as only one class needs to be dealt with, rather than requiring you to remember a potentially large number of @Configuration classes during construction.

As of Spring Framework 4.2, @Import also supports references to regular component classes, analogous to the AnnotationConfigApplicationContext.register method. This is particularly useful if you want to avoid component scanning, by using a few configuration classes as entry points to explicitly define all your components.
Injecting Dependencies on Imported @Bean Definitions

The preceding example works but is simplistic. In most practical scenarios, beans have dependencies on one another across configuration classes. When using XML, this is not an issue, because no compiler is involved, and you can declare ref="someBean" and trust Spring to work it out during container initialization. When using @Configuration classes, the Java compiler places constraints on the configuration model, in that references to other beans must be valid Java syntax.

Fortunately, solving this problem is simple. As we already discussed, a @Bean method can have an arbitrary number of parameters that describe the bean dependencies. Consider the following more real-world scenario with several @Configuration classes, each depending on beans declared in the others:

Java
@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
Kotlin
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Bean
    fun transferService(accountRepository: AccountRepository): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

@Configuration
class RepositoryConfig {

    @Bean
    fun accountRepository(dataSource: DataSource): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return new DataSource
    }
}


fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // everything wires up across configuration classes...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}

There is another way to achieve the same result. Remember that @Configuration classes are ultimately only another bean in the container: This means that they can take advantage of @Autowired and @Value injection and other features the same as any other bean.

Make sure that the dependencies you inject that way are of the simplest kind only. @Configuration classes are processed quite early during the initialization of the context, and forcing a dependency to be injected this way may lead to unexpected early initialization. Whenever possible, resort to parameter-based injection, as in the preceding example.

Also, be particularly careful with BeanPostProcessor and BeanFactoryPostProcessor definitions through @Bean. Those should usually be declared as static @Bean methods, not triggering the instantiation of their containing configuration class. Otherwise, @Autowired and @Value may not work on the configuration class itself, since it is possible to create it as a bean instance earlier than AutowiredAnnotationBeanPostProcessor (Javadoc) .

The following example shows how one bean can be autowired to another bean:

Java
@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
Kotlin
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Autowired
    lateinit var accountRepository: AccountRepository

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(accountRepository)
    }
}

@Configuration
class RepositoryConfig(private val dataSource: DataSource) {

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return new DataSource
    }
}

fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    // everything wires up across configuration classes...
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}
Constructor injection in @Configuration classes is only supported as of Spring Framework 4.3. Note also that there is no need to specify @Autowired if the target bean defines only one constructor.
Fully-qualifying imported beans for ease of navigation

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do you know exactly where the @Autowired AccountRepository bean is declared? It is not explicit in the code, and this may be just fine. Remember that the Spring Tools for Eclipse (英語) provides tooling that can render graphs showing how everything is wired, which may be all you need. Also, your Java IDE can easily find all declarations and uses of the AccountRepository type and quickly show you the location of @Bean methods that return that type.

In cases where this ambiguity is not acceptable and you wish to have direct navigation from within your IDE from one @Configuration class to another, consider autowiring the configuration classes themselves. The following example shows how to do so:

Java
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}
Kotlin
@Configuration
class ServiceConfig {

    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig

    @Bean
    fun transferService(): TransferService {
        // navigate 'through' the config class to the @Bean method!
        return TransferServiceImpl(repositoryConfig.accountRepository())
    }
}

In the preceding situation, where AccountRepository is defined is completely explicit. However, ServiceConfig is now tightly coupled to RepositoryConfig. That is the tradeoff. This tight coupling can be somewhat mitigated by using interface-based or abstract class-based @Configuration classes. Consider the following example:

Java
@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}
Kotlin
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

    @Autowired
    private lateinit var repositoryConfig: RepositoryConfig

    @Bean
    fun transferService(): TransferService {
        return TransferServiceImpl(repositoryConfig.accountRepository())
    }
}

@Configuration
interface RepositoryConfig {

    @Bean
    fun accountRepository(): AccountRepository
}

@Configuration
class DefaultRepositoryConfig : RepositoryConfig {

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(...)
    }
}

@Configuration
@Import(ServiceConfig::class, DefaultRepositoryConfig::class)  // import the concrete config!
class SystemTestConfig {

    @Bean
    fun dataSource(): DataSource {
        // return DataSource
    }

}

fun main() {
    val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    transferService.transfer(100.00, "A123", "C456")
}

Now ServiceConfig is loosely coupled with respect to the concrete DefaultRepositoryConfig, and built-in IDE tooling is still useful: You can easily get a type hierarchy of RepositoryConfig implementations. In this way, navigating @Configuration classes and their dependencies becomes no different than the usual process of navigating interface-based code.

If you want to influence the startup creation order of certain beans, consider declaring some of them as @Lazy (for creation on first access instead of on startup) or as @DependsOn certain other beans (making sure that specific other beans are created before the current bean, beyond what the latter’s direct dependencies imply).
Conditionally Include @Configuration Classes or @Bean Methods

It is often useful to conditionally enable or disable a complete @Configuration class or even individual @Bean methods, based on some arbitrary system state. One common example of this is to use the @Profile annotation to activate beans only when a specific profile has been enabled in the Spring Environment (see Bean Definition Profiles for details).

The @Profile annotation is actually implemented by using a much more flexible annotation called @Conditional (Javadoc) . The @Conditional annotation indicates specific org.springframework.context.annotation.Condition implementations that should be consulted before a @Bean is registered.

Implementations of the Condition interface provide a matches(…​) method that returns true or false. For example, the following listing shows the actual Condition implementation used for @Profile:

Java
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    // Read the @Profile annotation attributes
    MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
    if (attrs != null) {
        for (Object value : attrs.get("value")) {
            if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                return true;
            }
        }
        return false;
    }
    return true;
}
Kotlin
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
    // Read the @Profile annotation attributes
    val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
    if (attrs != null) {
        for (value in attrs["value"]!!) {
            if (context.environment.acceptsProfiles(Profiles .of(*value as Array<String>))) {
                return true
            }
        }
        return false
    }
    return true
}

See the @Conditional (Javadoc) javadoc for more detail.

Combining Java and XML Configuration

Spring’s @Configuration class support does not aim to be a 100% complete replacement for Spring XML. Some facilities, such as Spring XML namespaces, remain an ideal way to configure the container. In cases where XML is convenient or necessary, you have a choice: either instantiate the container in an “XML-centric” way by using, for example, ClassPathXmlApplicationContext, or instantiate it in a “Java-centric” way by using AnnotationConfigApplicationContext and the @ImportResource annotation to import XML as needed.

XML-centric Use of @Configuration Classes

It may be preferable to bootstrap the Spring container from XML and include @Configuration classes in an ad-hoc fashion. For example, in a large existing codebase that uses Spring XML, it is easier to create @Configuration classes on an as-needed basis and include them from the existing XML files. Later in this section, we cover the options for using @Configuration classes in this kind of “XML-centric” situation.

Declaring @Configuration classes as plain Spring <bean/> elements

Remember that @Configuration classes are ultimately bean definitions in the container. In this series examples, we create a @Configuration class named AppConfig and include it within system-test-config.xml as a <bean/> definition. Because <context:annotation-config/> is switched on, the container recognizes the @Configuration annotation and processes the @Bean methods declared in AppConfig properly.

The following example shows an ordinary configuration class in Java:

Java
@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }
}
Kotlin
@Configuration
class AppConfig {

    @Autowired
    private lateinit var dataSource: DataSource

    @Bean
    fun accountRepository(): AccountRepository {
        return JdbcAccountRepository(dataSource)
    }

    @Bean
    fun transferService() = TransferService(accountRepository())
}

The following example shows part of a sample system-test-config.xml file:

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

The following example shows a possible jdbc.properties file:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
Java
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
Kotlin
fun main() {
    val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
    val transferService = ctx.getBean<TransferService>()
    // ...
}
In system-test-config.xml file, the AppConfig <bean/> does not declare an id element. While it would be acceptable to do so, it is unnecessary, given that no other bean ever refers to it, and it is unlikely to be explicitly fetched from the container by name. Similarly, the DataSource bean is only ever autowired by type, so an explicit bean id is not strictly required.
Using <context:component-scan/> to pick up @Configuration classes

Because @Configuration is meta-annotated with @Component, @Configuration-annotated classes are automatically candidates for component scanning. Using the same scenario as describe in the previous example, we can redefine system-test-config.xml to take advantage of component-scanning. Note that, in this case, we need not explicitly declare <context:annotation-config/>, because <context:component-scan/> enables the same functionality.

The following example shows the modified system-test-config.xml file:

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
@Configuration Class-centric Use of XML with @ImportResource

In applications where @Configuration classes are the primary mechanism for configuring the container, it is still likely necessary to use at least some XML. In these scenarios, you can use @ImportResource and define only as much XML as you need. Doing so achieves a “Java-centric” approach to configuring the container and keeps XML to a bare minimum. The following example (which includes a configuration class, an XML file that defines a bean, a properties file, and the main class) shows how to use the @ImportResource annotation to achieve “Java-centric” configuration that uses XML as needed:

Java
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }
}
Kotlin
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {

    @Value("\${jdbc.url}")
    private lateinit var url: String

    @Value("\${jdbc.username}")
    private lateinit var username: String

    @Value("\${jdbc.password}")
    private lateinit var password: String

    @Bean
    fun dataSource(): DataSource {
        return DriverManagerDataSource(url, username, password)
    }
}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
Java
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
Kotlin
import org.springframework.beans.factory.getBean

fun main() {
    val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
    val transferService = ctx.getBean<TransferService>()
    // ...
}

1.13. Environment Abstraction

The Environment (Javadoc) interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties.

A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or with annotations. The role of the Environment object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default.

Properties play an important role in almost all applications and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Map objects, and so on. The role of the Environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.

1.13.1. Bean Definition Profiles

Bean definition profiles provide a mechanism in the core container that allows for registration of different beans in different environments. The word, “environment,” can mean different things to different users, and this feature can help with many use cases, including:

  • Working against an in-memory datasource in development versus looking up that same datasource from JNDI when in QA or production.

  • Registering monitoring infrastructure only when deploying an application into a performance environment.

  • Registering customized implementations of beans for customer A versus customer B deployments.

Consider the first use case in a practical application that requires a DataSource. In a test environment, the configuration might resemble the following:

Java
@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}
Kotlin
@Bean
fun dataSource(): DataSource {
    return EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("my-schema.sql")
            .addScript("my-test-data.sql")
            .build()
}

Now consider how this application can be deployed into a QA or production environment, assuming that the datasource for the application is registered with the production application server’s JNDI directory. Our dataSource bean now looks like the following listing:

Java
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
Kotlin
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
    val ctx = InitialContext()
    return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}

The problem is how to switch between using these two variations based on the current environment. Over time, Spring users have devised a number of ways to get this done, usually relying on a combination of system environment variables and XML <import/> statements containing ${placeholder} tokens that resolve to the correct configuration file path depending on the value of an environment variable. Bean definition profiles is a core container feature that provides a solution to this problem.

If we generalize the use case shown in the preceding example of environment-specific bean definitions, we end up with the need to register certain bean definitions in certain contexts but not in others. You could say that you want to register a certain profile of bean definitions in situation A and a different profile in situation B. We start by updating our configuration to reflect this need.

Using @Profile

The @Profile (Javadoc) annotation lets you indicate that a component is eligible for registration when one or more specified profiles are active. Using our preceding example, we can rewrite the dataSource configuration as follows:

Java
@Configuration
@Profile("development")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
Kotlin
@Configuration
@Profile("development")
class StandaloneDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }
}
Java
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
Kotlin
@Configuration
@Profile("production")
class JndiDataConfig {

    @Bean(destroyMethod = "")
    fun dataSource(): DataSource {
        val ctx = InitialContext()
        return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
    }
}
As mentioned earlier, with @Bean methods, you typically choose to use programmatic JNDI lookups, by using either Spring’s JndiTemplate/JndiLocatorDelegate helpers or the straight JNDI InitialContext usage shown earlier but not the JndiObjectFactoryBean variant, which would force you to declare the return type as the FactoryBean type.

The profile string may contain a simple profile name (for example, production) or a profile expression. A profile expression allows for more complicated profile logic to be expressed (for example, production & us-east). The following operators are supported in profile expressions:

  • !: A logical “not” of the profile

  • &: A logical “and” of the profiles

  • |: A logical “or” of the profiles

You cannot mix the & and | operators without using parentheses. For example, production & us-east | eu-central is not a valid expression. It must be expressed as production & (us-east | eu-central).

You can use @Profile as a meta-annotation for the purpose of creating a custom composed annotation. The following example defines a custom @Production annotation that you can use as a drop-in replacement for @Profile("production"):

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
If a @Configuration class is marked with @Profile, all of the @Bean methods and @Import annotations associated with that class are bypassed unless one or more of the specified profiles are active. If a @Component or @Configuration class is marked with @Profile({"p1", "p2"}), that class is not registered or processed unless profiles 'p1' or 'p2' have been activated. If a given profile is prefixed with the NOT operator (!), the annotated element is registered only if the profile is not active. For example, given @Profile({"p1", "!p2"}), registration will occur if profile 'p1' is active or if profile 'p2' is not active.

@Profile can also be declared at the method level to include only one particular bean of a configuration class (for example, for alternative variants of a particular bean), as the following example shows:

Java
@Configuration
public class AppConfig {

    @Bean("dataSource")
    @Profile("development") (1)
    public DataSource standaloneDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

    @Bean("dataSource")
    @Profile("production") (2)
    public DataSource jndiDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
1 The standaloneDataSource method is available only in the development profile.
2 The jndiDataSource method is available only in the production profile.
Kotlin
@Configuration
class AppConfig {

    @Bean("dataSource")
    @Profile("development") (1)
    fun standaloneDataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .addScript("classpath:com/bank/config/sql/test-data.sql")
                .build()
    }

    @Bean("dataSource")
    @Profile("production") (2)
    fun jndiDataSource() =
        InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
1 The standaloneDataSource method is available only in the development profile.
2 The jndiDataSource method is available only in the production profile.

With @Profile on @Bean methods, a special scenario may apply: In the case of overloaded @Bean methods of the same Java method name (analogous to constructor overloading), a @Profile condition needs to be consistently declared on all overloaded methods. If the conditions are inconsistent, only the condition on the first declaration among the overloaded methods matters. Therefore, @Profile can not be used to select an overloaded method with a particular argument signature over another. Resolution between all factory methods for the same bean follows Spring’s constructor resolution algorithm at creation time.

If you want to define alternative beans with different profile conditions, use distinct Java method names that point to the same bean name by using the @Bean name attribute, as shown in the preceding example. If the argument signatures are all the same (for example, all of the variants have no-arg factory methods), this is the only way to represent such an arrangement in a valid Java class in the first place (since there can only be one method of a particular name and argument signature).

XML Bean Definition Profiles

The XML counterpart is the profile attribute of the <beans> element. Our preceding sample configuration can be rewritten in two XML files, as follows:

<beans profile="development"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>
<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

It is also possible to avoid that split and nest <beans/> elements within the same file, as the following example shows:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="development">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>

The spring-bean.xsd has been constrained to allow such elements only as the last ones in the file. This should help provide flexibility without incurring clutter in the XML files.

The XML counterpart does not support the profile expressions described earlier. It is possible, however, to negate a profile by using the ! operator. It is also possible to apply a logical “and” by nesting the profiles, as the following example shows:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- other bean definitions -->

    <beans profile="production">
        <beans profile="us-east">
            <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
        </beans>
    </beans>
</beans>

In the preceding example, the dataSource bean is exposed if both the production and us-east profiles are active.

Activating a Profile

Now that we have updated our configuration, we still need to instruct Spring which profile is active. If we started our sample application right now, we would see a NoSuchBeanDefinitionException thrown, because the container could not find the Spring bean named dataSource.

Activating a profile can be done in several ways, but the most straightforward is to do it programmatically against the Environment API which is available through an ApplicationContext. The following example shows how to do so:

Java
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
Kotlin
val ctx = AnnotationConfigApplicationContext().apply {
    environment.setActiveProfiles("development")
    register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
    refresh()
}

In addition, you can also declaratively activate profiles through the spring.profiles.active property, which may be specified through system environment variables, JVM system properties, servlet context parameters in web.xml, or even as an entry in JNDI (see PropertySource Abstraction). In integration tests, active profiles can be declared by using the @ActiveProfiles annotation in the spring-test module (see context configuration with environment profiles).

Note that profiles are not an “either-or” proposition. You can activate multiple profiles at once. Programmatically, you can provide multiple profile names to the setActiveProfiles() method, which accepts String…​ varargs. The following example activates multiple profiles:

Java
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
Kotlin
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")

Declaratively, spring.profiles.active may accept a comma-separated list of profile names, as the following example shows:

    -Dspring.profiles.active="profile1,profile2"
Default Profile

The default profile represents the profile that is enabled by default. Consider the following example:

Java
@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}
Kotlin
@Configuration
@Profile("default")
class DefaultDataConfig {

    @Bean
    fun dataSource(): DataSource {
        return EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .addScript("classpath:com/bank/config/sql/schema.sql")
                .build()
    }
}

If no profile is active, the dataSource is created. You can see this as a way to provide a default definition for one or more beans. If any profile is enabled, the default profile does not apply.

You can change the name of the default profile by using setDefaultProfiles() on the Environment or ,declaratively, by using the spring.profiles.default property.

1.13.2. PropertySource Abstraction

Spring’s Environment abstraction provides search operations over a configurable hierarchy of property sources. Consider the following listing:

Java
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
Kotlin
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")

In the preceding snippet, we see a high-level way of asking Spring whether the my-property property is defined for the current environment. To answer this question, the Environment object performs a search over a set of PropertySource (Javadoc) objects. A PropertySource is a simple abstraction over any source of key-value pairs, and Spring’s StandardEnvironment (Javadoc) is configured with two PropertySource objects — one representing the set of JVM system properties (System.getProperties()) and one representing the set of system environment variables (System.getenv()).

These default property sources are present for StandardEnvironment, for use in standalone applications. StandardServletEnvironment (Javadoc) is populated with additional default property sources including servlet config and servlet context parameters. It can optionally enable a JndiPropertySource (Javadoc) . See the javadoc for details.

Concretely, when you use the StandardEnvironment, the call to env.containsProperty("my-property") returns true if a my-property system property or my-property environment variable is present at runtime.

The search performed is hierarchical. By default, system properties have precedence over environment variables. So, if the my-property property happens to be set in both places during a call to env.getProperty("my-property"), the system property value “wins” and is returned. Note that property values are not merged but rather completely overridden by a preceding entry.

For a common StandardServletEnvironment, the full hierarchy is as follows, with the highest-precedence entries at the top:

  1. ServletConfig parameters (if applicable — for example, in case of a DispatcherServlet context)

  2. ServletContext parameters (web.xml context-param entries)

  3. JNDI environment variables (java:comp/env/ entries)

  4. JVM system properties (-D command-line arguments)

  5. JVM system environment (operating system environment variables)

Most importantly, the entire mechanism is configurable. Perhaps you have a custom source of properties that you want to integrate into this search. To do so, implement and instantiate your own PropertySource and add it to the set of PropertySources for the current Environment. The following example shows how to do so:

Java
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
Kotlin
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())

In the preceding code, MyPropertySource has been added with highest precedence in the search. If it contains a my-property property, the property is detected and returned, in favor of any my-property property in any other PropertySource. The MutablePropertySources (Javadoc) API exposes a number of methods that allow for precise manipulation of the set of property sources.

1.13.3. Using @PropertySource

The @PropertySource (Javadoc) annotation provides a convenient and declarative mechanism for adding a PropertySource to Spring’s Environment.

Given a file called app.properties that contains the key-value pair testbean.name=myTestBean, the following @Configuration class uses @PropertySource in such a way that a call to testBean.getName() returns myTestBean:

Java
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}
Kotlin
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {

    @Autowired
    private lateinit var env: Environment

    @Bean
    fun testBean() = TestBean().apply {
        name = env.getProperty("testbean.name")!!
    }
}

Any ${…​} placeholders present in a @PropertySource resource location are resolved against the set of property sources already registered against the environment, as the following example shows:

Java
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}
Kotlin
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {

    @Autowired
    private lateinit var env: Environment

    @Bean
    fun testBean() = TestBean().apply {
        name = env.getProperty("testbean.name")!!
    }
}

Assuming that my.placeholder is present in one of the property sources already registered (for example, system properties or environment variables), the placeholder is resolved to the corresponding value. If not, then default/path is used as a default. If no default is specified and a property cannot be resolved, an IllegalArgumentException is thrown.

The @PropertySource annotation is repeatable, according to Java 8 conventions. However, all such @PropertySource annotations need to be declared at the same level, either directly on the configuration class or as meta-annotations within the same custom annotation. Mixing direct annotations and meta-annotations is not recommended, since direct annotations effectively override meta-annotations.

1.13.4. Placeholder Resolution in Statements

Historically, the value of placeholders in elements could be resolved only against JVM system properties or environment variables. This is no longer the case. Because the Environment abstraction is integrated throughout the container, it is easy to route resolution of placeholders through it. This means that you may configure the resolution process in any way you like. You can change the precedence of searching through system properties and environment variables or remove them entirely. You can also add your own property sources to the mix, as appropriate.

Concretely, the following statement works regardless of where the customer property is defined, as long as it is available in the Environment:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

1.14. Registering a LoadTimeWeaver

The LoadTimeWeaver is used by Spring to dynamically transform classes as they are loaded into the Java virtual machine (JVM).

To enable load-time weaving, you can add the @EnableLoadTimeWeaving to one of your @Configuration classes, as the following example shows:

Java
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
Kotlin
@Configuration
@EnableLoadTimeWeaving
class AppConfig

Alternatively, for XML configuration, you can use the context:load-time-weaver element:

<beans>
    <context:load-time-weaver/>
</beans>

Once configured for the ApplicationContext, any bean within that ApplicationContext may implement LoadTimeWeaverAware, thereby receiving a reference to the load-time weaver instance. This is particularly useful in combination with Spring’s JPA support where load-time weaving may be necessary for JPA class transformation. Consult the LocalContainerEntityManagerFactoryBean (Javadoc) javadoc for more detail. For more on AspectJ load-time weaving, see Load-time Weaving with AspectJ in the Spring Framework.

1.15. Additional Capabilities of the ApplicationContext

As discussed in the chapter introduction, the org.springframework.beans.factory package provides basic functionality for managing and manipulating beans, including in a programmatic way. The org.springframework.context package adds the ApplicationContext (Javadoc) interface, which extends the BeanFactory interface, in addition to extending other interfaces to provide additional functionality in a more application framework-oriented style. Many people use the ApplicationContext in a completely declarative fashion, not even creating it programmatically, but instead relying on support classes such as ContextLoader to automatically instantiate an ApplicationContext as part of the normal startup process of a Java EE web application.

To enhance BeanFactory functionality in a more framework-oriented style, the context package also provides the following functionality:

  • Access to messages in i18n-style, through the MessageSource interface.

  • Access to resources, such as URLs and files, through the ResourceLoader interface.

  • Event publication, namely to beans that implement the ApplicationListener interface, through the use of the ApplicationEventPublisher interface.

  • Loading of multiple (hierarchical) contexts, letting each be focused on one particular layer, such as the web layer of an application, through the HierarchicalBeanFactory interface.

1.15.1. Internationalization using MessageSource

The ApplicationContext interface extends an interface called MessageSource and, therefore, provides internationalization (“i18n”) functionality. Spring also provides the HierarchicalMessageSource interface, which can resolve messages hierarchically. Together, these interfaces provide the foundation upon which Spring effects message resolution. The methods defined on these interfaces include:

  • String getMessage(String code, Object[] args, String default, Locale loc): The basic method used to retrieve a message from the MessageSource. When no message is found for the specified locale, the default message is used. Any arguments passed in become replacement values, using the MessageFormat functionality provided by the standard library.

  • String getMessage(String code, Object[] args, Locale loc): Essentially the same as the previous method but with one difference: No default message can be specified. If the message cannot be found, a NoSuchMessageException is thrown.

  • String getMessage(MessageSourceResolvable resolvable, Locale locale): All properties used in the preceding methods are also wrapped in a class named MessageSourceResolvable, which you can use with this method.

When an ApplicationContext is loaded, it automatically searches for a MessageSource bean defined in the context. The bean must have the name messageSource. If such a bean is found, all calls to the preceding methods are delegated to the message source. If no message source is found, the ApplicationContext attempts to find a parent containing a bean with the same name. If it does, it uses that bean as the MessageSource. If the ApplicationContext cannot find any source for messages, an empty DelegatingMessageSource is instantiated in order to be able to accept calls to the methods defined above.

Spring provides three MessageSource implementations, ResourceBundleMessageSource, ReloadableResourceBundleMessageSource and StaticMessageSource. All of them implement HierarchicalMessageSource in order to do nested messaging. The StaticMessageSource is rarely used but provides programmatic ways to add messages to the source. The following example shows 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>

The example assumes that you have three resource bundles called format, exceptions and windows defined in your classpath. Any request to resolve a message is handled in the JDK-standard way of resolving messages through ResourceBundle objects. For the purposes of the example, assume the contents of two of the above resource bundle files are as follows:

    # in format.properties
    message=Alligators rock!
    # in exceptions.properties
    argument.required=The {0} argument is required.

The next example shows a program to run the MessageSource functionality. Remember that all ApplicationContext implementations are also MessageSource implementations and so can be cast to the MessageSource interface.

Java
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);
}
Kotlin
fun main() {
    val resources = ClassPathXmlApplicationContext("beans.xml")
    val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
    println(message)
}

The resulting output from the above program is as follows:

Alligators rock!

To summarize, the MessageSource is defined in a file called beans.xml, which exists at the root of your classpath. The messageSource bean definition refers to a number of resource bundles through its basenames property. The three files that are passed in the list to the basenames property exist as files at the root of your classpath and are called format.properties, exceptions.properties, and windows.properties, respectively.

The next example shows arguments passed to the message lookup. These arguments are converted into String objects and inserted into placeholders in the lookup message.

<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
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);
    }
}
Kotlin
    class Example {

    lateinit var messages: MessageSource

    fun execute() {
        val message = messages.getMessage("argument.required",
                arrayOf("userDao"), "Required", Locale.ENGLISH)
        println(message)
    }
}

The resulting output from the invocation of the execute() method is as follows:

The userDao argument is required.

With regard to internationalization (“i18n”), Spring’s various MessageSource implementations follow the same locale resolution and fallback rules as the standard JDK ResourceBundle. In short, and continuing with the example messageSource defined previously, if you want to resolve messages against the British (en-GB) locale, you would create files called format_en_GB.properties, exceptions_en_GB.properties, and windows_en_GB.properties, respectively.

Typically, locale resolution is managed by the surrounding environment of the application. In the following example, the locale against which (British) messages are resolved is specified manually:

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
Java
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);
}
Kotlin
fun main() {
    val resources = ClassPathXmlApplicationContext("beans.xml")
    val message = resources.getMessage("argument.required",
            arrayOf("userDao"), "Required", Locale.UK)
    println(message)
}

The resulting output from the running of the above program is as follows:

Ebagum lad, the 'userDao' argument is required, I say, required.

You can also use the MessageSourceAware interface to acquire a reference to any MessageSource that has been defined. Any bean that is defined in an ApplicationContext that implements the MessageSourceAware interface is injected with the application context’s MessageSource when the bean is created and configured.

As an alternative to ResourceBundleMessageSource, Spring provides a ReloadableResourceBundleMessageSource class. This variant supports the same bundle file format but is more flexible than the standard JDK based ResourceBundleMessageSource implementation. In particular, it allows for reading files from any Spring resource location (not only from the classpath) and supports hot reloading of bundle property files (while efficiently caching them in between). See the ReloadableResourceBundleMessageSource (Javadoc) javadoc for details.

1.15.2. Standard and Custom Events

Event handling in the ApplicationContext is provided through the ApplicationEvent class and the ApplicationListener interface. If a bean that implements the ApplicationListener interface is deployed into the context, every time an ApplicationEvent gets published to the ApplicationContext, that bean is notified. Essentially, this is the standard Observer design pattern.

As of Spring 4.2, the event infrastructure has been significantly improved and offers an annotation-based model as well as the ability to publish any arbitrary event (that is, an object that does not necessarily extend from ApplicationEvent). When such an object is published, we wrap it in an event for you.

The following table describes the standard events that Spring provides:

Table 7. Built-in Events
Event Explanation

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
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...
}
Kotlin
class BlockedListEvent(source: Any,
                    val address: String,
                    val content: String) : ApplicationEvent(source)

カスタム ApplicationEvent を公開するには、ApplicationEventPublisher で publishEvent() メソッドを呼び出します。通常、これは、ApplicationEventPublisherAware を実装するクラスを作成し、Spring Bean として登録することにより行われます。次の例は、このようなクラスを示しています。

Java
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...
    }
}
Kotlin
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
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...
    }
}
Kotlin
class BlockedListNotifier : ApplicationListener<BlockedListEvent> {

    lateinit var notificationAddres: String

    override fun onApplicationEvent(event: BlockedListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}

ApplicationListener は、カスタムイベントの型(前の例では BlockedListEvent)で一般的にパラメーター化されていることに注意してください。これは、onApplicationEvent() メソッドが型安全のままであり、ダウンキャストの必要性を回避できることを意味します。必要な数のイベントリスナーを登録できますが、デフォルトでは、イベントリスナーはイベントを同期的に受信します。これは、すべてのリスナーがイベントの処理を完了するまで、publishEvent() メソッドがブロックすることを意味します。この同期シングルスレッドアプローチの利点の 1 つは、リスナーがイベントを受信すると、トランザクションコンテキストが利用可能な場合、パブリッシャーのトランザクションコンテキスト内で動作することです。イベントを公開するための別の戦略が必要になった場合は、Spring の ApplicationEventMulticaster (Javadoc) インターフェースの javadoc および構成オプションの SimpleApplicationEventMulticaster (Javadoc) 実装を参照してください。

次の例は、上記の各クラスを登録および構成するために使用される 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>

これらすべてをまとめると、emailService Bean の sendEmail() メソッドが呼び出されたときに、ブロックする必要があるメールメッセージがある場合、BlockedListEvent 型のカスタムイベントが発行されます。blockedListNotifier Bean は ApplicationListener として登録され、BlockedListEvent を受信します。この時点で適切な関係者に通知できます。

Spring のイベントメカニズムは、同じアプリケーションコンテキスト内で Spring Bean 間の単純な通信用に設計されています。ただし、より高度なエンタープライズ統合のニーズに対応するため、別途保守される Spring Integration プロジェクトは、よく知られている Spring プログラミングモデルに基づいた、軽量でパターン指向 (英語) のイベント駆動型アーキテクチャの構築を完全にサポートします。
アノテーションベースのイベントリスナー

@EventListener アノテーションを使用して、マネージド Bean の任意のメソッドにイベントリスナーを登録できます。BlockedListNotifier は次のように書き直すことができます。

Java
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...
    }
}
Kotlin
class BlockedListNotifier {

    lateinit var notificationAddress: String

    @EventListener
    fun processBlockedListEvent(event: BlockedListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}

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

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

Java
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}
Kotlin
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
    // ...
}

特定のイベントのメソッドを実際に呼び出すために一致する SpEL 式を定義するアノテーションの condition 属性を使用して、追加のランタイムフィルタリングを追加することもできます。

次の例は、イベントの content 属性が my-event と等しい場合にのみ呼び出されるようにノーティファイアを書き換える方法を示しています。

Java
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}
Kotlin
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
    // notify appropriate parties via notificationAddress...
}

各 SpEL 式は、専用のコンテキストに対して評価されます。次の表に、条件付きイベント処理に使用できるように、コンテキストで使用できるようにするアイテムを示します。

表 8: イベント SpEL の利用可能なメタデータ
名前 ロケーション 説明 サンプル

イベント

ルートオブジェクト

実際の ApplicationEvent

#root.event or event

引数配列

ルートオブジェクト

メソッドの呼び出しに使用される引数(オブジェクト配列として)。

#root.args or args; args[0] to access the first argument, etc.

Argument name

evaluation context

The name of any of the method arguments. If, for some reason, the names are not available (for example, because there is no debug information in the compiled byte code), individual arguments are also available using the #a<#arg> syntax where <#arg> stands for the argument index (starting from 0).

#blEvent or #a0 (you can also use #p0 or #p<#arg> parameter notation as an alias)

Note that #root.event gives you access to the underlying event, even if your method signature actually refers to an arbitrary object that was published.

If you need to publish an event as the result of processing another event, you can change the method signature to return the event that should be published, as the following example shows:

Java
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
Kotlin
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
This feature is not supported for asynchronous listeners.

The handleBlockedListEvent() method publishes a new ListUpdateEvent for every BlockedListEvent that it handles. If you need to publish several events, you can return a Collection or an array of events instead.

Asynchronous Listeners

If you want a particular listener to process events asynchronously, you can reuse the regular @Async support. The following example shows how to do so:

Java
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}
Kotlin
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
    // BlockedListEvent is processed in a separate thread
}

Be aware of the following limitations when using asynchronous events:

  • If an asynchronous event listener throws an Exception, it is not propagated to the caller. See AsyncUncaughtExceptionHandler for more details.

  • Asynchronous event listener methods cannot publish a subsequent event by returning a value. If you need to publish another event as the result of the processing, inject an ApplicationEventPublisher (Javadoc) to publish the event manually.

Ordering Listeners

If you need one listener to be invoked before another one, you can add the @Order annotation to the method declaration, as the following example shows:

Java
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}
Kotlin
@EventListener
@Order(42)
fun processBlockedListEvent(event: BlockedListEvent) {
    // notify appropriate parties via notificationAddress...
}
Generic Events

You can also use generics to further define the structure of your event. Consider using an EntityCreatedEvent<T> where T is the type of the actual entity that got created. For example, you can create the following listener definition to receive only EntityCreatedEvent for a Person:

Java
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}
Kotlin
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
    // ...
}

Due to type erasure, this works only if the event that is fired resolves the generic parameters on which the event listener filters (that is, something like class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ }).

In certain circumstances, this may become quite tedious if all events follow the same structure (as should be the case for the event in the preceding example). In such a case, you can implement ResolvableTypeProvider to guide the framework beyond what the runtime environment provides. The following event shows how to do so:

Java
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()));
    }
}
Kotlin
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {

    override fun getResolvableType(): ResolvableType? {
        return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource()))
    }
}
This works not only for ApplicationEvent but any arbitrary object that you send as an event.

1.15.3. Convenient Access to Low-level Resources

For optimal usage and understanding of application contexts, you should familiarize yourself with Spring’s Resource abstraction, as described in Resources.

An application context is a ResourceLoader, which can be used to load Resource objects. A Resource is essentially a more feature rich version of the JDK java.net.URL class. In fact, the implementations of the Resource wrap an instance of java.net.URL, where appropriate. A Resource can obtain low-level resources from almost any location in a transparent fashion, including from the classpath, a filesystem location, anywhere describable with a standard URL, and some other variations. If the resource location string is a simple path without any special prefixes, where those resources come from is specific and appropriate to the actual application context type.

You can configure a bean deployed into the application context to implement the special callback interface, ResourceLoaderAware, to be automatically called back at initialization time with the application context itself passed in as the ResourceLoader. You can also expose properties of type Resource, to be used to access static resources. They are injected into it like any other properties. You can specify those Resource properties as simple String paths and rely on automatic conversion from those text strings to actual Resource objects when the bean is deployed.

The location path or paths supplied to an ApplicationContext constructor are actually resource strings and, in simple form, are treated appropriately according to the specific context implementation. For example ClassPathXmlApplicationContext treats a simple location path as a classpath location. You can also use location paths (resource strings) with special prefixes to force loading of definitions from the classpath or a URL, regardless of the actual context type.

1.15.4. Application Startup Tracking

The ApplicationContext manages the lifecycle of Spring applications and provides a rich programming model around components. As a result, complex applications can have equally complex component graphs and startup phases.

Tracking the application startup steps with specific metrics can help understand where time is being spent during the startup phase, but it can also be used as a way to better understand the context lifecycle as a whole.

The AbstractApplicationContext (and its subclasses) is instrumented with an ApplicationStartup, which collects StartupStep data about various startup phases:

  • application context lifecycle (base packages scanning, config classes management)

  • beans lifecycle (instantiation, smart initialization, post processing)

  • application events processing

Here is an example of instrumentation in the AnnotationConfigApplicationContext:

Java
// create a startup step and start recording
StartupStep scanPackages = this.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();
Kotlin
// create a startup step and start recording
val scanPackages = this.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()

The application context is already instrumented with multiple steps. Once recorded, these startup steps can be collected, displayed and analyzed with specific tools. For a complete list of existing startup steps, you can check out the dedicated appendix section.

The default ApplicationStartup implementation is a no-op variant, for minimal overhead. This means no metrics will be collected during application startup by default. Spring Framework ships with an implementation for tracking startup steps with Java Flight Recorder: FlightRecorderApplicationStartup. To use this variant, you must configure an instance of it to the ApplicationContext as soon as it’s been created.

Developers can also use the ApplicationStartup infrastructure if they’re providing their own AbstractApplicationContext subclass, or if they wish to collect more precise data.

ApplicationStartup is meant to be only used during application startup and for the core container; this is by no means a replacement for Java profilers or metrics libraries like Micrometer (英語) .

To start collecting custom StartupStep, components can either get the ApplicationStartup instance from the application context directly, make their component implement ApplicationStartupAware, or ask for the ApplicationStartup type on any injection point.

Developers should not use the "spring.*" namespace when creating custom startup steps. This namespace is reserved for internal Spring usage and is subject to change.

1.15.5. Convenient ApplicationContext Instantiation for Web Applications

You can create ApplicationContext instances declaratively by using, for example, a ContextLoader. Of course, you can also create ApplicationContext instances programmatically by using one of the ApplicationContext implementations.

You can register an ApplicationContext by using the ContextLoaderListener, as the following example shows:

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

The listener inspects the contextConfigLocation parameter. If the parameter does not exist, the listener uses /WEB-INF/applicationContext.xml as a default. When the parameter does exist, the listener separates the String by using predefined delimiters (comma, semicolon, and whitespace) and uses the values as locations where application contexts are searched. Ant-style path patterns are supported as well. Examples are /WEB-INF/*Context.xml (for all files with names that end with Context.xml and that reside in the WEB-INF directory) and /WEB-INF/**/*Context.xml (for all such files in any subdirectory of WEB-INF).

1.15.6. Deploying a Spring ApplicationContext as a Java EE RAR File

It is possible to deploy a Spring ApplicationContext as a RAR file, encapsulating the context and all of its required bean classes and library JARs in a Java EE RAR deployment unit. This is the equivalent of bootstrapping a stand-alone ApplicationContext (only hosted in Java EE environment) being able to access the Java EE servers facilities. RAR deployment is a more natural alternative to a scenario of deploying a headless WAR file — in effect, a WAR file without any HTTP entry points that is used only for bootstrapping a Spring ApplicationContext in a Java EE environment.

RAR deployment is ideal for application contexts that do not need HTTP entry points but rather consist only of message endpoints and scheduled jobs. Beans in such a context can use application server resources such as the JTA transaction manager and JNDI-bound JDBC DataSource instances and JMS ConnectionFactory instances and can also register with the platform’s JMX server — all through Spring’s standard transaction management and JNDI and JMX support facilities. Application components can also interact with the application server’s JCA WorkManager through Spring’s TaskExecutor abstraction.

See the javadoc of the SpringContextResourceAdapter (Javadoc) class for the configuration details involved in RAR deployment.

For a simple deployment of a Spring ApplicationContext as a Java EE RAR file:

  1. Package all application classes into a RAR file (which is a standard JAR file with a different file extension). .Add all required library JARs into the root of the RAR archive. .Add a META-INF/ra.xml deployment descriptor (as shown in the javadoc for SpringContextResourceAdapter) and the corresponding Spring XML bean definition file(s) (typically META-INF/applicationContext.xml).

  2. Drop the resulting RAR file into your application server’s deployment directory.

Such RAR deployment units are usually self-contained. They do not expose components to the outside world, not even to other modules of the same application. Interaction with a RAR-based ApplicationContext usually occurs through JMS destinations that it shares with other modules. A RAR-based ApplicationContext may also, for example, schedule some jobs or react to new files in the file system (or the like). If it needs to allow synchronous access from the outside, it could (for example) export RMI endpoints, which may be used by other application modules on the same machine.

1.16. The BeanFactory

The BeanFactory API provides the underlying basis for Spring’s IoC functionality. Its specific contracts are mostly used in integration with other parts of Spring and related third-party frameworks, and its DefaultListableBeanFactory implementation is a key delegate within the higher-level GenericApplicationContext container.

BeanFactory and related interfaces (such as BeanFactoryAware, InitializingBean, DisposableBean) are important integration points for other framework components. By not requiring any annotations or even reflection, they allow for very efficient interaction between the container and its components. Application-level beans may use the same callback interfaces but typically prefer declarative dependency injection instead, either through annotations or through programmatic configuration.

Note that the core BeanFactory API level and its DefaultListableBeanFactory implementation do not make assumptions about the configuration format or any component annotations to be used. All of these flavors come in through extensions (such as XmlBeanDefinitionReader and AutowiredAnnotationBeanPostProcessor) and operate on shared BeanDefinition objects as a core metadata representation. This is the essence of what makes Spring’s container so flexible and extensible.

1.16.1. BeanFactory or ApplicationContext?

This section explains the differences between the BeanFactory and ApplicationContext container levels and the implications on bootstrapping.

You should use an ApplicationContext unless you have a good reason for not doing so, with GenericApplicationContext and its subclass AnnotationConfigApplicationContext as the common implementations for custom bootstrapping. These are the primary entry points to Spring’s core container for all common purposes: loading of configuration files, triggering a classpath scan, programmatically registering bean definitions and annotated classes, and (as of 5.0) registering functional bean definitions.

Because an ApplicationContext includes all the functionality of a BeanFactory, it is generally recommended over a plain BeanFactory, except for scenarios where full control over bean processing is needed. Within an ApplicationContext (such as the GenericApplicationContext implementation), several kinds of beans are detected by convention (that is, by bean name or by bean type — in particular, post-processors), while a plain DefaultListableBeanFactory is agnostic about any special beans.

For many extended container features, such as annotation processing and AOP proxying, the BeanPostProcessor extension point is essential. If you use only a plain DefaultListableBeanFactory, such post-processors do not get detected and activated by default. This situation could be confusing, because nothing is actually wrong with your bean configuration. Rather, in such a scenario, the container needs to be fully bootstrapped through additional setup.

The following table lists features provided by the BeanFactory and ApplicationContext interfaces and implementations.

Table 9. Feature Matrix
Feature BeanFactory ApplicationContext

Bean instantiation/wiring

Yes

Yes

Integrated lifecycle management

No

Yes

Automatic BeanPostProcessor registration

No

Yes

Automatic BeanFactoryPostProcessor registration

No

Yes

Convenient MessageSource access (for internalization)

No

Yes

Built-in ApplicationEvent publication mechanism

No

Yes

To explicitly register a bean post-processor with a DefaultListableBeanFactory, you need to programmatically call addBeanPostProcessor, as the following example shows:

Java
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// now start using the factory
Kotlin
val factory = DefaultListableBeanFactory()
// populate the factory with bean definitions

// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(AutowiredAnnotationBeanPostProcessor())
factory.addBeanPostProcessor(MyBeanPostProcessor())

// now start using the factory

To apply a BeanFactoryPostProcessor to a plain DefaultListableBeanFactory, you need to call its postProcessBeanFactory method, as the following example shows:

Java
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);
Kotlin
val factory = DefaultListableBeanFactory()
val reader = XmlBeanDefinitionReader(factory)
reader.loadBeanDefinitions(FileSystemResource("beans.xml"))

// bring in some property values from a Properties file
val cfg = PropertySourcesPlaceholderConfigurer()
cfg.setLocation(FileSystemResource("jdbc.properties"))

// now actually do the replacement
cfg.postProcessBeanFactory(factory)

In both cases, the explicit registration steps are inconvenient, which is why the various ApplicationContext variants are preferred over a plain DefaultListableBeanFactory in Spring-backed applications, especially when relying on BeanFactoryPostProcessor and BeanPostProcessor instances for extended container functionality in a typical enterprise setup.

An AnnotationConfigApplicationContext has all common annotation post-processors registered and may bring in additional processors underneath the covers through configuration annotations, such as @EnableTransactionManagement. At the abstraction level of Spring’s annotation-based configuration model, the notion of bean post-processors becomes a mere internal container detail.

2. Resources

This chapter covers how Spring handles resources and how you can work with resources in Spring. It includes the following topics:

2.1. Introduction

Java’s standard java.net.URL class and standard handlers for various URL prefixes, unfortunately, are not quite adequate enough for all access to low-level resources. For example, there is no standardized URL implementation that may be used to access a resource that needs to be obtained from the classpath or relative to a ServletContext. While it is possible to register new handlers for specialized URL prefixes (similar to existing handlers for prefixes such as http:), this is generally quite complicated, and the URL interface still lacks some desirable functionality, such as a method to check for the existence of the resource being pointed to.

2.2. The Resource Interface

Spring’s Resource interface located in the org.springframework.core.io. package is meant to be a more capable interface for abstracting access to low-level resources. The following listing provides an overview of the Resource interface. See the Resource javadoc for further details.

public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isReadable();

    boolean isOpen();

    boolean isFile();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    ReadableByteChannel readableChannel() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}

As the definition of the Resource interface shows, it extends the InputStreamSource interface. The following listing shows the definition of the InputStreamSource interface:

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}

Some of the most important methods from the Resource interface are:

  • getInputStream(): Locates and opens the resource, returning an InputStream for reading from the resource. It is expected that each invocation returns a fresh InputStream. It is the responsibility of the caller to close the stream.

  • exists(): Returns a boolean indicating whether this resource actually exists in physical form.

  • isOpen(): Returns a boolean indicating whether this resource represents a handle with an open stream. If true, the InputStream cannot be read multiple times and must be read once only and then closed to avoid resource leaks. Returns false for all usual resource implementations, with the exception of InputStreamResource.

  • getDescription(): Returns a description for this resource, to be used for error output when working with the resource. This is often the fully qualified file name or the actual URL of the resource.

Other methods let you obtain an actual URL or File object representing the resource (if the underlying implementation is compatible and supports that functionality).

Some implementations of the Resource interface also implement the extended WritableResource (Javadoc) interface for a resource that supports writing to it.

Spring itself uses the Resource abstraction extensively, as an argument type in many method signatures when a resource is needed. Other methods in some Spring APIs (such as the constructors to various ApplicationContext implementations) take a String which in unadorned or simple form is used to create a Resource appropriate to that context implementation or, via special prefixes on the String path, let the caller specify that a specific Resource implementation must be created and used.

While the Resource interface is used a lot with Spring and by Spring, it is actually very convenient to use as a general utility class by itself in your own code, for access to resources, even when your code does not know or care about any other parts of Spring. While this couples your code to Spring, it really only couples it to this small set of utility classes, which serves as a more capable replacement for URL and can be considered equivalent to any other library you would use for this purpose.

The Resource abstraction does not replace functionality. It wraps it where possible. For example, a UrlResource wraps a URL and uses the wrapped URL to do its work.

2.3. Built-in Resource Implementations

Spring includes several built-in Resource implementations:

For a complete list of Resource implementations available in Spring, consult the "All Known Implementing Classes" section of the Resource javadoc.

2.3.1. UrlResource

UrlResource wraps a java.net.URL and can be used to access any object that is normally accessible with a URL, such as files, an HTTPS target, an FTP target, and others. All URLs have a standardized String representation, such that appropriate standardized prefixes are used to indicate one URL type from another. This includes file: for accessing filesystem paths, https: for accessing resources through the HTTPS protocol, ftp: for accessing resources through FTP, and others.

A UrlResource is created by Java code by explicitly using the UrlResource constructor but is often created implicitly when you call an API method that takes a String argument meant to represent a path. For the latter case, a JavaBeans PropertyEditor ultimately decides which type of Resource to create. If the path string contains a well-known (to property editor, that is) prefix (such as classpath:), it creates an appropriate specialized Resource for that prefix. However, if it does not recognize the prefix, it assumes the string is a standard URL string and creates a UrlResource.

2.3.2. ClassPathResource

This class represents a resource that should be obtained from the classpath. It uses either the thread context class loader, a given class loader, or a given class for loading resources.

This Resource implementation supports resolution as a java.io.File if the class path resource resides in the file system but not for classpath resources that reside in a jar and have not been expanded (by the servlet engine or whatever the environment is) to the filesystem. To address this, the various Resource implementations always support resolution as a java.net.URL.

A ClassPathResource is created by Java code by explicitly using the ClassPathResource constructor but is often created implicitly when you call an API method that takes a String argument meant to represent a path. For the latter case, a JavaBeans PropertyEditor recognizes the special prefix, classpath:, on the string path and creates a ClassPathResource in that case.

2.3.3. FileSystemResource

This is a Resource implementation for java.io.File handles. It also supports java.nio.file.Path handles, applying Spring’s standard String-based path transformations but performing all operations via the java.nio.file.Files API. For pure java.nio.path.Path based support use a PathResource instead. FileSystemResource supports resolution as a File and as a URL.

2.3.4. PathResource

This is a Resource implementation for java.nio.file.Path handles, performing all operations and transformations via the Path API. It supports resolution as a File and as a URL and also implements the extended WritableResource interface. PathResource is effectively a pure java.nio.path.Path based alternative to FileSystemResource with different createRelative behavior.

2.3.5. ServletContextResource

This is a Resource implementation for ServletContext resources that interprets relative paths within the relevant web application’s root directory.

It always supports stream access and URL access but allows java.io.File access only when the web application archive is expanded and the resource is physically on the filesystem. Whether or not it is expanded and on the filesystem or accessed directly from the JAR or somewhere else like a database (which is conceivable) is actually dependent on the Servlet container.

2.3.6. InputStreamResource

An InputStreamResource is a Resource implementation for a given InputStream. It should be used only if no specific Resource implementation is applicable. In particular, prefer ByteArrayResource or any of the file-based Resource implementations where possible.

In contrast to other Resource implementations, this is a descriptor for an already-opened resource. Therefore, it returns true from isOpen(). Do not use it if you need to keep the resource descriptor somewhere or if you need to read a stream multiple times.

2.3.7. ByteArrayResource

This is a Resource implementation for a given byte array. It creates a ByteArrayInputStream for the given byte array.

It is useful for loading content from any given byte array without having to resort to a single-use InputStreamResource.

2.4. The ResourceLoader Interface

The ResourceLoader interface is meant to be implemented by objects that can return (that is, load) Resource instances. The following listing shows the ResourceLoader interface definition:

public interface ResourceLoader {

    Resource getResource(String location);

    ClassLoader getClassLoader();
}

All application contexts implement the ResourceLoader interface. Therefore, all application contexts may be used to obtain Resource instances.

When you call getResource() on a specific application context, and the location path specified doesn’t have a specific prefix, you get back a Resource type that is appropriate to that particular application context. For example, assume the following snippet of code was run against a ClassPathXmlApplicationContext instance:

Java
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("some/resource/path/myTemplate.txt")

Against a ClassPathXmlApplicationContext, that code returns a ClassPathResource. If the same method were run against a FileSystemXmlApplicationContext instance, it would return a FileSystemResource. For a WebApplicationContext, it would return a ServletContextResource. It would similarly return appropriate objects for each context.

As a result, you can load resources in a fashion appropriate to the particular application context.

On the other hand, you may also force ClassPathResource to be used, regardless of the application context type, by specifying the special classpath: prefix, as the following example shows:

Java
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")

Similarly, you can force a UrlResource to be used by specifying any of the standard java.net.URL prefixes. The following examples use the file and https prefixes:

Java
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
Java
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
Kotlin
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")

The following table summarizes the strategy for converting String objects to Resource objects:

Table 10. Resource strings
Prefix Example Explanation

classpath:

classpath:com/myapp/config.xml

クラスパスからロードされます。

ファイル:

file:///data/config.xml

ファイルシステムから URL としてロードされます。FileSystemResource 警告も参照してください。

https:

https://myserver/logo.png

URL としてロードされます。

(なし)

/data/config.xml

基礎となる ApplicationContext に依存します。

2.5. ResourcePatternResolver インターフェース

ResourcePatternResolver インターフェースは ResourceLoader インターフェースの拡張であり、ロケーションパターン(たとえば、Ant スタイルのパスパターン)を Resource オブジェクトに解決するための戦略を定義します。

public interface ResourcePatternResolver extends ResourceLoader {

    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    Resource[] getResources(String locationPattern) throws IOException;
}

上記のように、このインターフェースは、クラスパスから一致するすべてのリソースに対して特別な classpath*: リソースプレフィックスも定義します。この場合、リソースの場所はプレースホルダーのないパスであると想定されていることに注意してください(たとえば、classpath*:/config/beans.xml)。クラスパス内の JAR ファイルまたは異なるディレクトリには、同じパスと同じ名前の複数のファイルを含めることができます。classpath*: リソースプレフィックスを使用したワイルドカードサポートの詳細については、アプリケーションコンテキストコンストラクターリソースパスのワイルドカードとそのサブセクションを参照してください。

渡された ResourceLoader (たとえば、ResourceLoaderAware セマンティクスを介して提供されるもの)は、この拡張インターフェースも実装しているかどうかを確認できます。

PathMatchingResourcePatternResolver は、ApplicationContext の外部で使用できるスタンドアロンの実装であり、Resource[] Bean プロパティを設定するために ResourceArrayPropertyEditor によっても使用されます。PathMatchingResourcePatternResolver は、指定されたリソースロケーションパスを 1 つ以上の一致する Resource オブジェクトに解決できます。ソースパスは、ターゲット Resource への 1 対 1 のマッピングを持つ単純なパスである場合もあれば、特別な classpath*: プレフィックスや内部 Ant スタイルの正規表現(Spring の org.springframework.util.AntPathMatcher ユーティリティを使用して照合される)を含む場合もあります。後者はどちらも事実上ワイルドカードです。

標準の ApplicationContext のデフォルトの ResourceLoader は、実際には ResourcePatternResolver インターフェースを実装する PathMatchingResourcePatternResolver のインスタンスです。同じことが ApplicationContext インスタンス自体にも当てはまります。ApplicationContext インスタンス自体も ResourcePatternResolver インターフェースを実装し、デフォルトの PathMatchingResourcePatternResolver に委譲します。

2.6. ResourceLoaderAware インターフェース

ResourceLoaderAware インターフェースは、ResourceLoader 参照が提供されることを期待するコンポーネントを識別する特別なコールバックインターフェースです。次のリストは、ResourceLoaderAware インターフェースの定義を示しています。

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

クラスが ResourceLoaderAware を実装し、アプリケーションコンテキストに(Spring 管理の Bean として)デプロイされると、アプリケーションコンテキストによって ResourceLoaderAware として認識されます。次に、アプリケーションコンテキストは setResourceLoader(ResourceLoader) を呼び出し、自身を引数として提供します(Spring のすべてのアプリケーションコンテキストが ResourceLoader インターフェースを実装することを思い出してください)。

ApplicationContext は ResourceLoader であるため、Bean は ApplicationContextAware インターフェースを実装し、提供されたアプリケーションコンテキストを直接使用してリソースをロードすることもできます。ただし、一般的には、必要な場合は専用の ResourceLoader インターフェースを使用することをお勧めします。コードは、Spring ApplicationContext インターフェース全体ではなく、リソースローディングインターフェース(ユーティリティインターフェースと見なすことができます)にのみ結合されます。

アプリケーションコンポーネントでは、ResourceLoaderAware インターフェースを実装する代わりに、ResourceLoader のオートワイヤーに依存することもできます。従来の constructor および byType オートワイヤーモード(オートワイヤーのコラボレーターで説明)は、コンストラクター引数または setter メソッドパラメーターのいずれかにそれぞれ ResourceLoader を提供できます。柔軟性を高めるために(フィールドと複数のパラメーターメソッドをオートワイヤーする機能を含む)、アノテーションベースのオートワイヤー機能の使用を検討してください。その場合、ResourceLoader は、問題のフィールド、コンストラクター、メソッドが @Autowired アノテーションを持っている限り、ResourceLoader 型を予期するフィールド、コンストラクター引数、メソッドパラメーターにオートワイヤーされます。詳細については、@Autowired を使用するを参照してください。

ワイルドカードを含む、または特別な classpath*: リソースプレフィックスを使用するリソースパスの 1 つ以上の Resource オブジェクトをロードするには、ResourceLoader の代わりに ResourcePatternResolver のインスタンスをアプリケーションコンポーネントにオートワイヤーすることを検討してください。

2.7. 依存関係としてのリソース

Bean 自体が何らかの動的プロセスを介してリソースパスを決定して提供する場合、Bean が ResourceLoader または ResourcePatternResolver インターフェースを使用してリソースをロードすることはおそらく理にかなっています。例: 必要な特定のリソースがユーザーのロールに依存する、ある種のテンプレートのロードを検討してください。リソースが静的である場合は、ResourceLoader インターフェース(または ResourcePatternResolver インターフェース)の使用を完全に排除し、Bean に必要な Resource プロパティを公開させ、それらが注入されることを期待することは理にかなっています。

これらのプロパティを挿入するのが簡単なのは、すべてのアプリケーションコンテキストが登録され、String パスを Resource オブジェクトに変換できる特別な JavaBeans PropertyEditor を使用することです。例: 次の MyBean クラスには、型 Resource の template プロパティがあります。

Java
package example;

public class MyBean {

    private Resource template;

    public setTemplate(Resource template) {
        this.template = template;
    }

    // ...
}
Kotlin
class MyBean(var template: Resource)

次の例に示すように、XML 構成ファイルでは、template プロパティをそのリソースの単純な文字列で構成できます。

<bean id="myBean" class="example.MyBean">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

リソースパスにはプレフィックスがないことに注意してください。アプリケーションコンテキスト自体が ResourceLoader として使用されるため、リソースは、アプリケーションコンテキストの正確な型に応じて、ClassPathResourceFileSystemResourceServletContextResource を介してロードされます。

特定の Resource 型を強制的に使用する必要がある場合は、プレフィックスを使用できます。次の 2 つの例は、ClassPathResource と UrlResource (後者はファイルシステム内のファイルにアクセスするために使用されます)を強制する方法を示しています。

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

MyBean クラスがアノテーション駆動型構成で使用するためにリファクタリングされる場合、myTemplate.txt へのパスは、template.path という名前のキーに格納できます。たとえば、Spring Environment で使用できるようになっているプロパティファイルに格納できます(環境の抽象化を参照)。テンプレートパスは、プロパティプレースホルダーを使用して @Value アノテーションを介して参照できます(@Value の使用を参照)。Spring は、テンプレートパスの値を文字列として取得し、特別な PropertyEditor は、文字列を Resource オブジェクトに変換して、MyBean コンストラクターに挿入します。次の例は、これを実現する方法を示しています。

Java
@Component
public class MyBean {

    private final Resource template;

    public MyBean(@Value("${template.path}") Resource template) {
        this.template = template;
    }

    // ...
}
Kotlin
@Component
class MyBean(@Value("\${template.path}") private val template: Resource)

クラスパス内の複数の場所(クラスパス内の複数の jar など)の同じパスで検出された複数のテンプレートをサポートする場合は、特別な classpath*: プレフィックスとワイルドカードを使用して templates.path キーを classpath*:/config/templates/*.txt として定義できます。MyBean クラスを次のように再定義すると、Spring は、テンプレートパスパターンを、MyBean コンストラクターに挿入できる Resource オブジェクトの配列に変換します。

Java
@Component
public class MyBean {

    private final Resource[] templates;

    public MyBean(@Value("${templates.path}") Resource[] templates) {
        this.templates = templates;
    }

    // ...
}
Kotlin
@Component
class MyBean(@Value("\${templates.path}") private val templates: Resource[])

2.8. アプリケーションコンテキストとリソースパス

このセクションでは、XML で機能するショートカット、ワイルドカードの使用方法、その他の詳細など、リソースを使用してアプリケーションコンテキストを作成する方法について説明します。

2.8.1. アプリケーションコンテキストの構築

(特定のアプリケーションコンテキスト型の)アプリケーションコンテキストコンストラクターは、通常、コンテキストの定義を構成する XML ファイルなど、リソースのロケーションパスとして文字列または文字列の配列を取ります。

そのようなロケーションパスにプレフィックスがない場合、そのパスから作成され、Bean 定義のロードに使用される特定の Resource 型は、特定のアプリケーションコンテキストに依存し、適切です。例: ClassPathXmlApplicationContext を作成する次の例を検討してください。

Java
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
Kotlin
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")

ClassPathResource が使用されるため、Bean 定義はクラスパスからロードされます。ただし、FileSystemXmlApplicationContext を作成する次の例を検討してください。

Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/appContext.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")

これで、Bean 定義がファイルシステムの場所からロードされます(この場合、現在の作業ディレクトリを基準にしています)。

ロケーションパスで特別な classpath プレフィックスまたは標準の URL プレフィックスを使用すると、Bean 定義をロードするために作成されたデフォルト型の Resource が上書きされることに注意してください。次の例を考えてみましょう。

Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")

FileSystemXmlApplicationContext を使用すると、クラスパスから Bean 定義がロードされます。ただし、まだ FileSystemXmlApplicationContext です。その後 ResourceLoader として使用される場合、接頭辞のないパスはファイルシステムパスとして扱われます。

ClassPathXmlApplicationContext インスタンスの構築 — ショートカット

ClassPathXmlApplicationContext は、便利なインスタンス化を可能にするために、多数のコンストラクターを公開しています。基本的な考え方は、XML ファイル自体のファイル名のみを含む(先頭のパス情報を含まない)文字列配列のみを提供し、Class も提供できるということです。次に、ClassPathXmlApplicationContext は、提供されたクラスからパス情報を取得します。

次のディレクトリレイアウトを検討してください。

com/
  example/
    services.xml
    repositories.xml
    MessengerService.class

次の例は、services.xml および repositories.xml (クラスパス上にある)という名前のファイルで定義された Bean で構成される ClassPathXmlApplicationContext インスタンスをインスタンス化する方法を示しています。

Java
ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] {"services.xml", "repositories.xml"}, MessengerService.class);
Kotlin
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "repositories.xml"), MessengerService::class.java)

さまざまなコンストラクターの詳細については、ClassPathXmlApplicationContext javadoc を参照してください。

2.8.2. アプリケーションコンテキストコンストラクターリソースパスのワイルドカード

アプリケーションコンテキストコンストラクター値のリソースパスは、(前に示したように)単純なパスであり、それぞれがターゲット Resource への 1 対 1 のマッピングを持っているか、、特別な classpath*: プレフィックスまたは内部 Ant スタイルパターンを含むことができます(Spring の PathMatcher ユーティリティを使用して一致します)。後者はどちらも事実上ワイルドカードです。

このメカニズムの用途の 1 つは、コンポーネントスタイルのアプリケーションアセンブリを実行する必要がある場合です。すべてのコンポーネントはコンテキスト定義フラグメントを既知のロケーションパスに公開でき、最終的なアプリケーションコンテキストが classpath*: で始まる同じパスを使用して作成されると、すべてのコンポーネントフラグメントが自動的に取得されます。

このワイルドカードは、アプリケーションコンテキストコンストラクター(または PathMatcher ユーティリティクラス階層を直接使用する場合)でのリソースパスの使用に固有であり、構築時に解決されることに注意してください。Resource 型自体とは関係ありません。リソースは一度に 1 つのリソースのみを指すため、classpath*: プレフィックスを使用して実際の Resource を構築することはできません。

Ant スタイルのパターン

次の例に示すように、パスの場所には Ant スタイルのパターンを含めることができます。

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml

パスの場所に Ant スタイルのパターンが含まれている場合、リゾルバーはより複雑な手順に従ってワイルドカードの解決を試みます。最後の非ワイルドカードセグメントまでのパスに対して Resource を生成し、そこから URL を取得します。この URL が jar: URL またはコンテナー固有のバリアント(WebLogic の zip:、WebSphere の wsjar など)ではない場合、java.io.File がそこから取得され、ファイルシステムを走査してワイルドカードを解決するために使用されます。jar URL の場合、リゾルバーはそこから java.net.JarURLConnection を取得するか、jar URL を手動で解析してから、jar ファイルの内容を走査してワイルドカードを解決します。

移植性への影響

指定されたパスがすでに file URL である場合(ベース ResourceLoader がファイルシステム 1 であるために暗黙的に、または明示的に)、ワイルドカードは完全に移植可能な方法で機能することが保証されます。

指定されたパスが classpath の場所である場合、リゾルバーは Classloader.getResource() 呼び出しを行うことにより、最後の非ワイルドカードパスセグメント URL を取得する必要があります。これはパスの単なるノードであるため(最後のファイルではありません)、実際には(ClassLoader javadoc では)この場合に返される URL の種類は正確には定義されていません。実際には、これは常にディレクトリ(クラスパスリソースがファイルシステムの場所に解決される)またはある種の jar URL(クラスパスリソースが jar の場所に解決される)を表す java.io.File です。それでも、この操作には移植性の懸念があります。

jar URL が最後の非ワイルドカードセグメントについて取得された場合、リゾルバーは、jar から java.net.JarURLConnection を取得するか、jar URL を手動で解析して、jar の内容を調べてワイルドカードを解決できる必要があります。これはほとんどの環境で機能しますが、他の環境では失敗します。jar からのリソースのワイルドカード解決は、依存する前に特定の環境で徹底的にテストすることを強くお勧めします。

classpath*: プレフィックス

XML ベースのアプリケーションコンテキストを構築するとき、次の例に示すように、ロケーション文字列は特別な classpath*: プレフィックスを使用する場合があります。

Java
ApplicationContext ctx =
    new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
Kotlin
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")

この特別なプレフィックスは、指定された名前に一致するすべてのクラスパスリソースを取得し(内部的には、本質的に ClassLoader.getResources(…​) の呼び出しによって発生)、その後、マージして最終的なアプリケーションコンテキスト定義を形成する必要があることを指定します。

ワイルドカードクラスパスは、基になる ClassLoader の getResources() メソッドに依存しています。最近のほとんどのアプリケーションサーバーは独自の ClassLoader 実装を提供しているため、特に jar ファイルを処理する場合は、動作が異なる可能性があります。classpath* が機能するかどうかを確認する簡単なテストは、ClassLoader を使用して、クラスパス getClass().getClassLoader().getResources("<someFileInsideTheJar>") の jar 内からファイルをロードすることです。同じ名前で 2 つの異なる場所にあるファイル(たとえば、同じ名前で同じパスであるがクラスパス上の異なる jar にあるファイル)でこのテストを試してください。不適切な結果が返された場合は、アプリケーションサーバーのドキュメントで ClassLoader の動作に影響を与える可能性のある設定を確認してください。

また、ロケーションパスの残りの部分で、classpath*: プレフィックスと PathMatcher パターンを組み合わせることができます(たとえば、classpath*:META-INF/*-beans.xml)。この場合、解決戦略は非常に簡単です: ClassLoader.getResources() 呼び出しを最後の非ワイルドカードパスセグメントで使用して、クラスローダー階層内のすべての一致するリソースを取得し、次に各リソースから、前述の同じ PathMatcher 解決戦略を取得します。ワイルドカードサブパスに使用されます。

ワイルドカードに関するその他の注意事項

classpath* は、Ant スタイルのパターンと組み合わせると、実際のターゲットファイルがファイルシステムに存在しない限り、パターンが開始する前に少なくとも 1 つのルートディレクトリでのみ確実に動作することに注意してください。つまり、classpath*:*.xml などのパターンは、jar ファイルのルートからではなく、展開されたディレクトリのルートからのみファイルを取得する可能性があります。

Spring のクラスパスエントリを取得する機能は、JDK の ClassLoader.getResources() メソッドに由来します。このメソッドは、空の文字列(検索する潜在的なルートを示す)のファイルシステムの場所のみを返します。Spring は URLClassLoader ランタイム構成と jar ファイル内の java.class.path マニフェストも評価しますが、これは移植性のある動作を保証するものではありません。

クラスパスパッケージをスキャンするには、クラスパスに対応するディレクトリエントリが存在する必要があります。Ant を使用して JAR を作成する場合は、JAR タスクの files-only スイッチをアクティブにしないでください。また、一部の環境では、セキュリティポリシーに基づいてクラスパスディレクトリが公開されない場合があります。たとえば、JDK 1.7.0_45 以降のスタンドアロンアプリケーション(マニフェストに "Trusted-Library" を設定する必要があります。https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources (英語) を参照)。

JDK 9 のモジュールパス(Jigsaw)では、Spring のクラスパススキャンは通常期待どおりに機能します。ここでもリソースを専用ディレクトリに配置することを強くお勧めします。これにより、前述の jar ファイルのルートレベルの検索に関する移植性の問題を回避できます。

検索するルートパッケージが複数のクラスパスの場所で利用できる場合、classpath: リソースを使用する Ant スタイルのパターンが一致するリソースを見つけることは保証されません。リソースの場所の次の例について考えてみます。

com/mycompany/package1/service-context.xml

次に、誰かがそのファイルを見つけるために使用する Ant スタイルのパスを考えてみましょう。

classpath:com/mycompany/**/service-context.xml

このようなリソースは、クラスパス内の 1 つの場所にのみ存在する可能性がありますが、前の例のようなパスを使用してリソースを解決しようとすると、リゾルバーは getResource("com/mycompany"); によって返される(最初の)URL を処理します。この基本パッケージノードが複数の ClassLoader の場所に存在する場合、最初に見つかった場所に目的のリソースが存在しない可能性があります。このような場合は、同じ Ant スタイルのパターンで classpath*: を使用することをお勧めします。これにより、com.mycompany 基本パッケージ classpath*:com/mycompany/**/service-context.xml を含むすべてのクラスパスの場所が検索されます。

2.8.3. FileSystemResource 警告

FileSystemApplicationContext に接続されていない FileSystemResource (つまり、FileSystemApplicationContext が実際の ResourceLoader ではない場合)は、予想どおりに絶対パスと相対パスを処理します。相対パスは現在の作業ディレクトリからの相対パスですが、絶対パスはファイルシステムのルートからの相対パスです。

ただし、下位互換性(履歴)の理由により、FileSystemApplicationContext が ResourceLoader の場合、これは変更されます。FileSystemApplicationContext は、接続されているすべての FileSystemResource インスタンスに、先頭のスラッシュで始まるかどうかに関係なく、すべてのロケーションパスを強制的に相対パスとして処理させます。実際には、これは次の例が同等であることを意味します。

Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/context.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
Java
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("/conf/context.xml");
Kotlin
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")

次の例も同等です(1 つのケースは相対的で、もう 1 つのケースは絶対的であるため、それらが異なることは理にかなっていますが)。

Java
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
Kotlin
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
Java
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
Kotlin
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")

実際には、真の絶対ファイルシステムパスが必要な場合は、FileSystemResource または FileSystemXmlApplicationContext での絶対パスの使用を避け、file: URL プレフィックスを使用して UrlResource の使用を強制する必要があります。次の例は、その方法を示しています。

Java
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
Kotlin
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt")
Java
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:///conf/context.xml");
Kotlin
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")

3. 検証、データバインディング、型変換

ビジネスロジックとして検証を検討することには長所と短所があり、Spring は検証(およびデータバインディング)の設計を提供します。具体的には、検証は Web 層に結び付けられるべきではなく、ローカライズが容易である必要があり、利用可能な検証ツールをプラグインできる必要があります。これらの関心事を考慮して、Spring は Validator 契約を提供します。これは、基本的であり、アプリケーションのすべてのレイヤーで非常に有用です。

データバインディングは、ユーザー入力をアプリケーションのドメインモデル(またはユーザー入力の処理に使用するオブジェクト)に動的にバインドできます。Spring は、まさにそれを行うために適切な名前の DataBinder を提供します。Validator および DataBinder は validation パッケージを構成し、これは主に Web レイヤーで使用されますが、これに限定されません。

BeanWrapper は Spring Framework の基本概念であり、多くの場所で使用されています。ただし、おそらく BeanWrapper を直接使用する必要はありません。ただし、これはリファレンスドキュメントであるため、何らかの説明が適切であると感じました。BeanWrapper については、この章で説明します。これを使用する場合は、データをオブジェクトにバインドするときに使用する可能性が高いからです。

Spring の DataBinder と下位レベルの BeanWrapper はどちらも PropertyEditorSupport 実装を使用して、プロパティ値を解析およびフォーマットします。PropertyEditor および PropertyEditorSupport 型は JavaBeans 仕様の一部であり、この章でも説明されています。Spring 3 は、一般的な型変換機能を提供する core.convert パッケージと、UI フィールド値をフォーマットするための高レベルの「フォーマット」パッケージを導入しました。これらのパッケージは、PropertyEditorSupport 実装のより簡単な代替手段として使用できます。これらについても、この章で説明します。

Spring は、セットアップインフラストラクチャと Spring 独自の Validator 契約へのアダプターを通じて Java Bean 検証をサポートします。アプリケーションは、Java Bean 検証に従って、Bean 検証をグローバルに一度有効にして、すべての検証ニーズに対してのみ使用できます。Web レイヤーでは、DataBinder の構成に従って、アプリケーションは DataBinder ごとにコントローラーローカル Spring Validator インスタンスをさらに登録できます。これは、カスタム検証ロジックのプラグインに役立ちます。

3.1. Spring の検証インターフェースを使用した検証

Spring は、オブジェクトの検証に使用できる Validator インターフェースを備えています。Validator インターフェースは Errors オブジェクトを使用して機能するため、検証中にバリデーターは Errors オブジェクトに検証エラーを報告できます。

次の小さなデータオブジェクトの例を考えてみましょう。

Java
public class Person {

    private String name;
    private int age;

    // the usual getters and setters...
}
Kotlin
class Person(val name: String, val age: Int)

次の例では、org.springframework.validation.Validator インターフェースの以下の 2 つのメソッドを実装することにより、Person クラスの検証動作を提供します。

  • supports(Class): この Validator は、提供された Class のインスタンスを検証できますか?

  • validate(Object, org.springframework.validation.Errors): 指定されたオブジェクトを検証し、検証エラーの場合、指定された Errors オブジェクトに登録します。

Spring Framework が提供する ValidationUtils ヘルパークラスを知っている場合は特に、Validator の実装は非常に簡単です。次の例では、Person インスタンスに Validator を実装しています。

Java
public class PersonValidator implements Validator {

    /**
     * This Validator validates only Person instances
     */
    public boolean supports(Class clazz) {
        return Person.class.equals(clazz);
    }

    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negativevalue");
        } else if (p.getAge() > 110) {
            e.rejectValue("age", "too.darn.old");
        }
    }
}
Kotlin
class PersonValidator : Validator {

    /**
     * This Validator validates only Person instances
     */
    override fun supports(clazz: Class<*>): Boolean {
        return Person::class.java == clazz
    }

    override fun validate(obj: Any, e: Errors) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty")
        val p = obj as Person
        if (p.age < 0) {
            e.rejectValue("age", "negativevalue")
        } else if (p.age > 110) {
            e.rejectValue("age", "too.darn.old")
        }
    }
}

ValidationUtils クラスの static rejectIfEmpty(..) メソッドは、null または空の文字列である場合、name プロパティを拒否するために使用されます。ValidationUtils javadoc を見て、前に示した例以外にどのような機能が提供されているかを確認してください。

リッチオブジェクトの各ネストされたオブジェクトを検証するために単一の Validator クラスを実装することは確かに可能ですが、独自の Validator 実装でオブジェクトの各ネストされたクラスの検証ロジックをカプセル化する方が良い場合があります。「リッチ」オブジェクトの簡単な例は、2 つの String プロパティ(1 番目と 2 番目の名前)と複雑な Address オブジェクトで構成される Customer です。Address オブジェクトは Customer オブジェクトとは独立して使用できるため、別個の AddressValidator が実装されています。CustomerValidator で AddressValidator クラスに含まれるロジックをコピーアンドペーストに頼らずに再利用したい場合、次の例に示すように、CustomerValidator 内で AddressValidator を依存性注入またはインスタンス化できます。

Java
public class CustomerValidator implements Validator {

    private final Validator addressValidator;

    public CustomerValidator(Validator addressValidator) {
        if (addressValidator == null) {
            throw new IllegalArgumentException("The supplied [Validator] is " +
                "required and must not be null.");
        }
        if (!addressValidator.supports(Address.class)) {
            throw new IllegalArgumentException("The supplied [Validator] must " +
                "support the validation of [Address] instances.");
        }
        this.addressValidator = addressValidator;
    }

    /**
     * This Validator validates Customer instances, and any subclasses of Customer too
     */
    public boolean supports(Class clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath("address");
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}
Kotlin
class CustomerValidator(private val addressValidator: Validator) : Validator {

    init {
        if (addressValidator == null) {
            throw IllegalArgumentException("The supplied [Validator] is required and must not be null.")
        }
        if (!addressValidator.supports(Address::class.java)) {
            throw IllegalArgumentException("The supplied [Validator] must support the validation of [Address] instances.")
        }
    }

    /*
    * This Validator validates Customer instances, and any subclasses of Customer too
    */
    override fun supports(clazz: Class<>): Boolean {
        return Customer::class.java.isAssignableFrom(clazz)
    }

    override fun validate(target: Any, errors: Errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required")
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required")
        val customer = target as Customer
        try {
            errors.pushNestedPath("address")
            ValidationUtils.invokeValidator(this.addressValidator, customer.address, errors)
        } finally {
            errors.popNestedPath()
        }
    }
}

検証エラーは、バリデーターに渡される Errors オブジェクトに報告されます。Spring Web MVC の場合、<spring:bind/> タグを使用してエラーメッセージをインスペクションできますが、Errors オブジェクトを自分でインスペクションすることもできます。提供するメソッドの詳細については、javadoc を参照してください

3.2. コードをエラーメッセージに解決する

データバインディングと検証について説明しました。このセクションでは、検証エラーに対応するメッセージの出力について説明します。前のセクションで示した例では、name フィールドと age フィールドを拒否しました。MessageSource を使用してエラーメッセージを出力する場合は、フィールドを拒否するときに指定するエラーコードを使用します (この場合の 'name' と 'age' )。Errors インターフェースから (ValidationUtils クラスなどを使用して、直接または間接的に) rejectValue または他の reject メソッドのいずれかを呼び出すと、基礎となるインプリメンテーションは、渡されたコードを登録するだけでなく、多数の追加エラーコードも登録します。MessageCodesResolver は、Errors インターフェースがどのエラーコードを登録するかを決定します。デフォルトでは、DefaultMessageCodesResolver が使用されます。DefaultMessageCodesResolver (たとえば) は、指定したコードでメッセージを登録するだけでなく、reject メソッドに渡したフィールド名を含むメッセージも登録します。そのため、rejectValue("age", "too.darn.old") を使用してフィールドを拒否した場合、too.darn.old コードとは別に、Spring は too.darn.old.age および too.darn.old.age.int (最初のフィールドにはフィールド名が含まれ、2 番目のフィールドにはフィールドの型が含まれます) も登録します。これは、開発者がエラーメッセージをターゲットにする際の便宜を図るために行われます。

MessageCodesResolver およびデフォルト戦略の詳細は、それぞれ MessageCodesResolver (Javadoc) および DefaultMessageCodesResolver (Javadoc) の javadoc にあります。

3.3. Bean 操作と BeanWrapper

org.springframework.beans パッケージは、JavaBeans 標準に準拠しています。JavaBean は、デフォルトの引数なしのコンストラクターを持つクラスであり、命名規則に従います(たとえば、bingoMadness という名前のプロパティには setter メソッド setBingoMadness(..) および getter メソッド getBingoMadness() があります)。JavaBeans と仕様の詳細については、javabeans (標準 Javadoc) を参照してください。

Bean パッケージの非常に重要なクラスの 1 つは、BeanWrapper インターフェースとそれに対応する実装(BeanWrapperImpl)です。javadoc から引用されているように、BeanWrapper は、プロパティ値の設定と取得(個別または一括)、プロパティ記述子の取得、プロパティのクエリを行って読み取り可能または書き込み可能かどうかを判断する機能を提供します。また、BeanWrapper はネストされたプロパティをサポートし、サブプロパティのプロパティを無制限の深さに設定できます。BeanWrapper は、ターゲットクラスのコードをサポートする必要なく、標準 JavaBeans PropertyChangeListeners および VetoableChangeListeners を追加する機能もサポートしています。最後になりましたが、BeanWrapper はインデックス付きプロパティの設定をサポートします。BeanWrapper は通常、アプリケーションコードによって直接使用されるのではなく、DataBinder および BeanFactory によって使用されます。

BeanWrapper の動作方法は、その名前によって部分的に示されます。Bean をラップして、プロパティの設定や取得など、その Bean でアクションを実行します。

3.3.1. 基本およびネストされたプロパティの設定と取得

プロパティの設定と取得は、setPropertyValue および getPropertyValue のオーバーロードされた BeanWrapper のメソッドバリアントを介して行われます。詳細については、Javadoc を参照してください。次の表に、これらの規則の例をいくつか示します。

表 11: プロパティの例
説明

name

getName() または isName() および setName(..) メソッドに対応するプロパティ name を示します。

account.name

(たとえば) getAccount().setName() または getAccount().getName() メソッドに対応するプロパティ account のネストされたプロパティ name を示します。

account[2]

インデックス付きプロパティ account3 番目の要素を示します。インデックス付きプロパティは、型 arraylist、その他の自然に順序付けられたコレクションにすることができます。

account[COMPANYNAME]

account Map プロパティの COMPANYNAME キーによって索引付けされたマップ項目の値を示します。

BeanWrapper を直接使用する予定がない場合、この次のセクションはそれほど重要ではありません。DataBinder と BeanFactory とそれらのデフォルト実装のみを使用する場合は、PropertyEditorsセクションに進んでください

次の 2 つのサンプルクラスは、BeanWrapper を使用してプロパティを取得および設定します。

Java
public class Company {

    private String name;
    private Employee managingDirector;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Employee getManagingDirector() {
        return this.managingDirector;
    }

    public void setManagingDirector(Employee managingDirector) {
        this.managingDirector = managingDirector;
    }
}
Kotlin
class Company {
    var name: String? = null
    var managingDirector: Employee? = null
}
Java
public class Employee {

    private String name;

    private float salary;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }
}
Kotlin
class Employee {
    var name: String? = null
    var salary: Float? = null
}

次のコードスニペットは、インスタンス化された Companies および Employees のプロパティの一部を取得および操作する方法の例を示しています。

Java
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
Kotlin
val company = BeanWrapperImpl(Company())
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.")
// ... can also be done like this:
val value = PropertyValue("name", "Some Company Inc.")
company.setPropertyValue(value)

// ok, let's create the director and tie it to the company:
val jim = BeanWrapperImpl(Employee())
jim.setPropertyValue("name", "Jim Stravinsky")
company.setPropertyValue("managingDirector", jim.wrappedInstance)

// retrieving the salary of the managingDirector through the company
val salary = company.getPropertyValue("managingDirector.salary") as Float?

3.3.2. ビルトイン PropertyEditor 実装

Spring は、PropertyEditor の概念を使用して、Object と String の間の変換を行います。オブジェクト自体とは異なる方法でプロパティを表すと便利です。例: Date は人間が読める形式で表現できます(String'2007-14-09' のように)が、人間が読める形式を元の日付に戻すことができます(あるいは、人間が読める形式で入力した日付を戻すことができます) Date オブジェクトへ)。この動作は、型 java.beans.PropertyEditor のカスタムエディターを登録することで実現できます。BeanWrapper または特定の IoC コンテナー(前の章で説明)でカスタムエディターを登録すると、プロパティを目的の型に変換する方法がわかります。PropertyEditor の詳細については、Oracle の java.beans パッケージの javadoc を参照してください。

Spring でプロパティの編集が使用されるいくつかの例:

  • Bean のプロパティの設定は、PropertyEditor 実装を使用して行われます。XML ファイルで宣言する Bean のプロパティの値として String を使用する場合、Spring(対応するプロパティの setter に Class パラメーターがある場合)は、ClassEditor を使用してパラメーターを Class オブジェクトに解決しようとします。

  • Spring の MVC フレームワークでの HTTP リクエストパラメーターの解析は、CommandController のすべてのサブクラスで手動でバインドできる、あらゆる種類の PropertyEditor 実装を使用して行われます。

Spring には、PropertyEditor が多数実装されており、簡単に作業できます。それらはすべて org.springframework.beans.propertyeditors パッケージにあります。ほとんど(すべてではありませんが、次の表に示すように)は、デフォルトで BeanWrapperImpl によって登録されています。プロパティエディターを何らかの方法で構成できる場合でも、独自のバリアントを登録してデフォルトのバリアントをオーバーライドできます。次の表は、Spring が提供するさまざまな PropertyEditor 実装を示しています。

表 12: ビルトイン PropertyEditor 実装
クラス 説明

ByteArrayPropertyEditor

バイト配列のエディター。文字列を対応するバイト表現に変換します。BeanWrapperImpl によってデフォルトで登録されています。

ClassEditor

クラスを実際のクラスに、またはその逆に表す文字列を解析します。クラスが見つからない場合、IllegalArgumentException がスローされます。デフォルトでは、BeanWrapperImpl によって登録されます。

CustomBooleanEditor

Boolean プロパティ用のカスタマイズ可能なプロパティエディター。デフォルトでは、BeanWrapperImpl によって登録されますが、そのカスタムインスタンスをカスタムエディターとして登録することでオーバーライドできます。

CustomCollectionEditor

ソース Collection を特定のターゲット Collection 型に変換するコレクションのプロパティエディター。

CustomDateEditor

java.util.Date 用のカスタマイズ可能なプロパティエディター、カスタム DateFormat をサポートします。デフォルトでは登録されていません。必要に応じて適切な形式でユーザー登録する必要があります。

CustomNumberEditor

IntegerLongFloat や Double など、Number サブクラスのカスタマイズ可能なプロパティエディター。デフォルトでは、BeanWrapperImpl によって登録されますが、そのカスタムインスタンスをカスタムエディターとして登録することでオーバーライドできます。

FileEditor

文字列を java.io.File オブジェクトに解決します。デフォルトでは、BeanWrapperImpl によって登録されます。

InputStreamEditor

文字列を取得し、InputStream プロパティを文字列として直接設定できるように、ResourceEditor および Resource を介して InputStream を生成できる一方向プロパティエディター。デフォルトの使用箇所では、InputStream が閉じられないことに注意してください。デフォルトでは、BeanWrapperImpl によって登録されます。

LocaleEditor

文字列を Locale オブジェクトに、またはその逆に解決できます(文字列形式は [language]_[country]_[variant] であり、Locale の toString() メソッドと同じです)。アンダースコアの代わりに、区切り文字としてスペースも使用できます。デフォルトでは、BeanWrapperImpl によって登録されます。

PatternEditor

文字列を java.util.regex.Pattern オブジェクトに、またはその逆に解決できます。

PropertiesEditor

文字列(java.util.Properties クラスの javadoc で定義された形式でフォーマットされた)を Properties オブジェクトに変換できます。デフォルトでは、BeanWrapperImpl によって登録されます。

StringTrimmerEditor

文字列をトリムするプロパティエディター。オプションで、空の文字列を null 値に変換できます。デフォルトでは登録されていません — ユーザー登録する必要があります。

URLEditor

URL の文字列表現を実際の URL オブジェクトに解決できます。デフォルトでは、BeanWrapperImpl によって登録されます。

Spring は、java.beans.PropertyEditorManager を使用して、必要になる可能性のあるプロパティエディターの検索パスを設定します。検索パスには sun.bean.editors も含まれます。これには、FontColor、ほとんどのプリミティブ型などの型の PropertyEditor 実装が含まれます。また、標準の JavaBeans インフラストラクチャは、PropertyEditor クラスが処理するクラスと同じパッケージにあり、そのクラスと同じ名前で Editor が追加されている場合、自動的に検出します(明示的に登録する必要はありません)。例: 次のクラスとパッケージ構造を持つことができます。これは、SomethingEditor クラスが Something -typed プロパティの PropertyEditor として認識され、使用されるのに十分です。

com
  chank
    pop
      Something
      SomethingEditor // the PropertyEditor for the Something class

ここでも標準の BeanInfo JavaBeans メカニズムを使用できることに注意してください(ここである程度説明します)。次の例では、BeanInfo メカニズムを使用して、1 つ以上の PropertyEditor インスタンスを関連するクラスのプロパティに明示的に登録します。

com
  chank
    pop
      Something
      SomethingBeanInfo // the BeanInfo for the Something class

参照される SomethingBeanInfo クラスの次の Java ソースコードは、CustomNumberEditor を Something クラスの age プロパティに関連付けます。

Java
public class SomethingBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
                @Override
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                }
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}
Kotlin
class SomethingBeanInfo : SimpleBeanInfo() {

    override fun getPropertyDescriptors(): Array<PropertyDescriptor> {
        try {
            val numberPE = CustomNumberEditor(Int::class.java, true)
            val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) {
                override fun createPropertyEditor(bean: Any): PropertyEditor {
                    return numberPE
                }
            }
            return arrayOf(ageDescriptor)
        } catch (ex: IntrospectionException) {
            throw Error(ex.toString())
        }

    }
}
追加のカスタム PropertyEditor 実装の登録

Bean プロパティを文字列値として設定する場合、Spring IoC コンテナーは最終的に標準 JavaBeans PropertyEditor 実装を使用して、これらの文字列をプロパティの複雑な型に変換します。Spring は、多数のカスタム PropertyEditor 実装を事前登録します(たとえば、ストリングとして表現されたクラス名を Class オブジェクトに変換するため)。さらに、Java の標準 JavaBeans PropertyEditor ルックアップメカニズムにより、クラスの PropertyEditor に適切な名前を付けて、サポートを提供するクラスと同じパッケージに配置して、自動的に検出できるようにします。

他のカスタム PropertyEditors を登録する必要がある場合、いくつかのメカニズムが利用可能です。通常、便利または推奨されない最も手動のアプローチは、BeanFactory 参照があると仮定して、ConfigurableBeanFactory インターフェースの registerCustomEditor() メソッドを使用することです。別の(少し便利な)メカニズムは、CustomEditorConfigurer と呼ばれる特別な Bean ファクトリポストプロセッサーを使用することです。Bean ファクトリポストプロセッサーは BeanFactory 実装で使用できますが、CustomEditorConfigurer にはネストされたプロパティ設定があるため、ApplicationContext で使用することを強くお勧めします。他の Bean と同様の方法でデプロイできます。自動的に検出および適用されます。

すべての Bean ファクトリとアプリケーションコンテキストは、プロパティ変換を処理するために BeanWrapper を使用することで、いくつかの組み込みプロパティエディターを自動的に使用することに注意してください。BeanWrapper が登録する標準プロパティエディターは、前のセクションにリストされています。さらに、ApplicationContexts は、特定のアプリケーションコンテキスト型に適した方法でリソース検索を処理するために、エディターをオーバーライドまたは追加します。

標準の JavaBeans PropertyEditor インスタンスは、文字列として表現されたプロパティ値をプロパティの実際の複合型に変換するために使用されます。Bean ファクトリポストプロセッサーである CustomEditorConfigurer を使用して、ApplicationContext に追加の PropertyEditor インスタンスのサポートを簡単に追加できます。

ExoticType というユーザークラスと、ExoticType をプロパティとして設定する必要がある DependsOnExoticType という別のクラスを定義する次の例を考えてみましょう。

Java
package example;

public class ExoticType {

    private String name;

    public ExoticType(String name) {
        this.name = name;
    }
}

public class DependsOnExoticType {

    private ExoticType type;

    public void setType(ExoticType type) {
        this.type = type;
    }
}
Kotlin
package example

class ExoticType(val name: String)

class DependsOnExoticType {

    var type: ExoticType? = null
}

物事が適切に設定されたら、type プロパティを文字列として割り当てることができます。これは、PropertyEditor が実際の ExoticType インスタンスに変換します。次の Bean 定義は、この関連をセットアップする方法を示しています。

<bean id="sample" class="example.DependsOnExoticType">
    <property name="type" value="aNameForExoticType"/>
</bean>

PropertyEditor の実装は次のようになります。

Java
// converts string representation to ExoticType object
package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}
Kotlin
// converts string representation to ExoticType object
package example

import java.beans.PropertyEditorSupport

class ExoticTypeEditor : PropertyEditorSupport() {

    override fun setAsText(text: String) {
        value = ExoticType(text.toUpperCase())
    }
}

最後に、次の例は、CustomEditorConfigurer を使用して新しい PropertyEditor を ApplicationContext に登録する方法を示しています。これにより、必要に応じて PropertyEditor を使用できるようになります。

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>
PropertyEditorRegistrar を使用する

プロパティエディターを Spring コンテナーに登録するためのもう 1 つのメカニズムは、PropertyEditorRegistrar を作成して使用することです。このインターフェースは、いくつかの異なる状況で同じプロパティエディターのセットを使用する必要がある場合に特に便利です。対応するレジストラを作成し、それぞれの場合に再利用できます。PropertyEditorRegistrar インスタンスは、PropertyEditorRegistry というインターフェースと連携して動作します。このインターフェースは、Spring BeanWrapper (および DataBinder) によって実装されます。PropertyEditorRegistrar インスタンスは、setPropertyEditorRegistrars(..) と呼ばれるプロパティを公開する CustomEditorConfigurer (ここで説明) と組み合わせて使用すると特に便利です。この方法で CustomEditorConfigurer に追加された PropertyEditorRegistrar インスタンスは、DataBinder および Spring MVC コントローラーと簡単に共有できます。さらに、カスタムエディターでの同期の必要性が回避されます。PropertyEditorRegistrar は、Bean 作成試行ごとに新しい PropertyEditor インスタンスを作成することが期待されます。

次の例は、独自の PropertyEditorRegistrar 実装を作成する方法を示しています。

Java
package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

        // it is expected that new PropertyEditor instances are created
        registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

        // you could register as many custom property editors as are required here...
    }
}
Kotlin
package com.foo.editors.spring

import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry

class CustomPropertyEditorRegistrar : PropertyEditorRegistrar {

    override fun registerCustomEditors(registry: PropertyEditorRegistry) {

        // it is expected that new PropertyEditor instances are created
        registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor())

        // you could register as many custom property editors as are required here...
    }
}

PropertyEditorRegistrar の実装例については、org.springframework.beans.support.ResourceEditorRegistrar も参照してください。registerCustomEditors(..) メソッドの実装で、各プロパティエディターの新しいインスタンスがどのように作成されるかに注意してください。

次の例は、CustomEditorConfigurer を構成し、それに CustomPropertyEditorRegistrar のインスタンスを挿入する方法を示しています。

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <list>
            <ref bean="customPropertyEditorRegistrar"/>
        </list>
    </property>
</bean>

<bean id="customPropertyEditorRegistrar"
    class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>

最後に(そして Spring の MVC Web フレームワークを使用している人のためにこの章の焦点から少し離れて)、PropertyEditorRegistrars をデータバインディング Controllers (SimpleFormController など)と組み合わせて使用すると非常に便利です。次の例では、initBinder(..) メソッドの実装で PropertyEditorRegistrar を使用しています。

Java
public final class RegisterUserController extends SimpleFormController {

    private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    protected void initBinder(HttpServletRequest request,
            ServletRequestDataBinder binder) throws Exception {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // other methods to do with registering a User
}
Kotlin
class RegisterUserController(
    private val customPropertyEditorRegistrar: PropertyEditorRegistrar) : SimpleFormController() {

    protected fun initBinder(request: HttpServletRequest,
                            binder: ServletRequestDataBinder) {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder)
    }

    // other methods to do with registering a User
}

このスタイルの PropertyEditor 登録は簡潔なコードにつながり(initBinder(..) の実装は 1 行のみです)、共通の PropertyEditor 登録コードをクラスにカプセル化し、必要な数の Controllers 間で共有することができます。

3.4. Spring 型変換

Spring 3 は、一般的な型変換システムを提供する core.convert パッケージを導入しました。システムは、型変換ロジックを実装する SPI と、実行時に型変換を実行する API を定義します。Spring コンテナー内では、このシステムを PropertyEditor 実装の代わりとして使用して、外部化された Bean プロパティ値文字列を必要なプロパティ型に変換できます。型変換が必要なアプリケーションの任意の場所でパブリック API を使用することもできます。

3.4.1. コンバーター SPI

次のインターフェース定義が示すように、型変換ロジックを実装するための SPI は単純で厳密に型指定されています。

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);
}

独自のコンバーターを作成するには、Converter インターフェースを実装し、変換元の型として S を、変換先の型として T をパラメーター化します。S のコレクションまたは配列を T の配列またはコレクションに変換する必要がある場合、委譲配列またはコレクションコンバーターも登録されている場合(DefaultConversionService はデフォルトで行います)、このようなコンバーターを透過的に適用することもできます。

convert(S) を呼び出すたびに、ソース引数が null でないことが保証されます。変換が失敗した場合、Converter は未チェックの例外をスローする場合があります。具体的には、IllegalArgumentException をスローして、無効なソース値を報告する必要があります。Converter 実装がスレッドセーフであることを確認してください。

core.convert.support パッケージには、便宜上いくつかのコンバーター実装が提供されています。これらには、文字列から数値およびその他の一般的な型へのコンバーターが含まれます。次のリストは、典型的な Converter 実装である StringToInteger クラスを示しています。

package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}

3.4.2. ConverterFactory を使用する

クラス階層全体の変換ロジックを集中化する必要がある場合(たとえば、String から Enum オブジェクトに変換する場合)、次の例に示すように ConverterFactory を実装できます。

package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

S を変換元の型に、R を変換先のクラスの範囲を定義する基本型にパラメーター化します。次に getConverter(Class<T>) を実装します。ここで、T は R のサブクラスです。

例として StringToEnumConverterFactory を検討してください。

package org.springframework.core.convert.support;

final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {

    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToEnumConverter(targetType);
    }

    private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {

        private Class<T> enumType;

        public StringToEnumConverter(Class<T> enumType) {
            this.enumType = enumType;
        }

        public T convert(String source) {
            return (T) Enum.valueOf(this.enumType, source.trim());
        }
    }
}

3.4.3. GenericConverter を使用する

洗練された Converter 実装が必要な場合は、GenericConverter インターフェースの使用を検討してください。Converter よりも柔軟性がありますが、型付けがそれほど強力ではない署名では、GenericConverter は複数のソース型とターゲット型間の変換をサポートします。さらに、GenericConverter は、変換ロジックを実装するときに使用できるソースおよびターゲットフィールドコンテキストを使用可能にします。このようなコンテキストにより、フィールドのアノテーションまたはフィールドの署名で宣言された一般的な情報によって型変換を実行できます。次のリストは、GenericConverter のインターフェース定義を示しています。

package org.springframework.core.convert.converter;

public interface GenericConverter {

    public Set<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

GenericConverter を実装するには、getConvertibleTypes() がサポートされているソース→ターゲット型のペアを返すようにします。次に、convert(Object, TypeDescriptor, TypeDescriptor) を実装して、変換ロジックを含めます。ソース TypeDescriptor は、変換される値を保持するソースフィールドへのアクセスを提供します。ターゲット TypeDescriptor は、変換された値が設定されるターゲットフィールドへのアクセスを提供します。

GenericConverter の良い例は、Java 配列とコレクションの間で変換するコンバーターです。このような ArrayToCollectionConverter は、ターゲットコレクション型を宣言するフィールドをイントロスペクトして、コレクションの要素型を解決します。これにより、コレクションがターゲットフィールドに設定される前に、ソース配列内の各要素がコレクション要素型に変換されます。

GenericConverter はより複雑な SPI インターフェースであるため、必要な場合にのみ使用してください。基本的な型変換のニーズには、Converter または ConverterFactory を優先してください。
ConditionalGenericConverter を使用する

特定の条件が当てはまる場合にのみ Converter を実行したい場合があります。例: 特定のアノテーションがターゲットフィールドに存在する場合にのみ Converter を実行したり、特定のメソッド(static valueOf メソッドなど)がターゲットクラスに定義されている場合にのみ Converter を実行したい場合があります。ConditionalGenericConverter は GenericConverter および ConditionalConverter インターフェースの結合であり、このようなカスタム一致条件を定義できます。

public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

ConditionalGenericConverter の良い例は、永続エンティティ ID とエンティティ参照の間で変換する IdToEntityConverter です。このような IdToEntityConverter は、ターゲットエンティティ型が静的ファインダーメソッドを宣言している場合にのみ一致する場合があります(例: findAccount(Long))。matches(TypeDescriptor, TypeDescriptor) の実装で、このようなファインダーメソッドチェックを実行できます。

3.4.4. ConversionService API

ConversionService は、実行時に型変換ロジックを実行するための統合 API を定義します。多くの場合、コンバーターは次のファサードインターフェースの背後で実行されます。

package org.springframework.core.convert;

public interface ConversionService {

    boolean canConvert(Class<?> sourceType, Class<?> targetType);

    <T> T convert(Object source, Class<T> targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

ほとんどの ConversionService 実装は、コンバーターを登録するための SPI を提供する ConverterRegistry も実装します。内部的に、ConversionService 実装は、登録されたコンバーターに委譲して、型変換ロジックを実行します。

ConversionService 実装は、core.convert.support パッケージで提供されます。GenericConversionService は、ほとんどの環境での使用に適した汎用実装です。ConversionServiceFactory は、一般的な ConversionService 構成を作成するための便利なファクトリを提供します。

3.4.5. ConversionService の構成

ConversionService は、アプリケーションの起動時にインスタンス化され、複数のスレッド間で共有されるように設計されたステートレスオブジェクトです。Spring アプリケーションでは、通常、各 Spring コンテナー(または ApplicationContext)に対して ConversionService インスタンスを構成します。Spring は、その ConversionService を取得し、フレームワークが型変換を実行する必要がある場合にそれを使用します。この ConversionService を任意の Bean に注入して、直接呼び出すこともできます。

ConversionService が Spring に登録されていない場合、元の PropertyEditor ベースのシステムが使用されます。

デフォルト ConversionService を Spring に登録するには、conversionService の id で次の Bean 定義を追加します。

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean"/>

デフォルトの ConversionService は、文字列、数値、列挙型、コレクション、マップ、その他の一般的な型の間で変換できます。デフォルトのコンバーターを独自のカスタムコンバーターで補足またはオーバーライドするには、converters プロパティを設定します。プロパティ値は、ConverterConverterFactoryGenericConverter インターフェースのいずれかを実装できます。

<bean id="conversionService"
        class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>

Spring MVC アプリケーション内で ConversionService を使用することも一般的です。Spring MVC の章の変換とフォーマットを参照してください。

特定の状況では、変換中にフォーマットを適用したい場合があります。FormattingConversionServiceFactoryBean の使用の詳細については、FormatterRegistry SPI を参照してください。

3.4.6. ConversionService をプログラムで使用する

ConversionService インスタンスをプログラムで操作するには、他の Bean の場合と同様に、インスタンスへの参照を注入できます。次の例は、その方法を示しています。

Java
@Service
public class MyService {

    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    public void doIt() {
        this.conversionService.convert(...)
    }
}
Kotlin
@Service
class MyService(private val conversionService: ConversionService) {

    fun doIt() {
        conversionService.convert(...)
    }
}

ほとんどのユースケースでは、targetType を指定する convert メソッドを使用できますが、パラメーター化された要素のコレクションなど、より複雑な型では機能しません。例: Integer の List を String の List にプログラムで変換する場合は、ソース型とターゲット型の正式な定義を提供する必要があります。

幸いなことに、TypeDescriptor には、次の例に示すように、簡単にするためのさまざまなオプションが用意されています。

Java
DefaultConversionService cs = new DefaultConversionService();

List<Integer> input = ...
cs.convert(input,
    TypeDescriptor.forObject(input), // List<Integer> type descriptor
    TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
Kotlin
val cs = DefaultConversionService()

val input: List<Integer> = ...
cs.convert(input,
        TypeDescriptor.forObject(input), // List<Integer> type descriptor
        TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))

DefaultConversionService は、ほとんどの環境に適したコンバーターを自動的に登録することに注意してください。これには、コレクションコンバーター、スカラーコンバーター、基本的な Object から String コンバーターが含まれます。DefaultConversionService クラスの静的 addDefaultConverters メソッドを使用して、同じコンバーターを任意の ConverterRegistry に登録できます。

値型のコンバーターは配列およびコレクションに再利用されるため、標準のコレクション処理が適切であると仮定すると、S の Collection から T の Collection に変換する特定のコンバーターを作成する必要はありません。

3.5. Spring フィールドのフォーマット

前のセクションで説明したように、core.convert は汎用の型変換システムです。統一された ConversionService API と、ある型から別の型への変換ロジックを実装するための厳密に型指定された Converter SPI を提供します。Spring コンテナーは、このシステムを使用して Bean プロパティ値をバインドします。さらに、Spring Expression Language(SpEL)と DataBinder の両方がこのシステムを使用してフィールド値をバインドします。例: SpEL が expression.setValue(Object bean, Object value) の試行を完了するために Short を Long に強制する必要がある場合、core.convert システムは強制を実行します。

次に、Web またはデスクトップアプリケーションなどの一般的なクライアント環境の型変換要件について検討します。このような環境では、通常、String から変換してクライアントのポストバックプロセスをサポートし、String に戻ってビューのレンダリングプロセスをサポートします。さらに、多くの場合、String 値をローカライズする必要があります。より一般的な core.convert Converter SPI は、このようなフォーマット要件に直接対応していません。それらに直接対処するために、Spring 3 は便利な Formatter SPI を導入しました。これは、クライアント環境向けの PropertyEditor 実装のシンプルで堅牢な代替手段を提供します。

一般に、java.util.Date と Long の間の変換など、汎用の型変換ロジックを実装する必要がある場合は、Converter SPI を使用できます。クライアント環境(Web アプリケーションなど)で作業していて、ローカライズされたフィールド値を解析および出力する必要がある場合は、Formatter SPI を使用できます。ConversionService は、両方の SPI に統一型変換 API を提供します。

3.5.1. Formatter SPI

フィールドフォーマットロジックを実装する Formatter SPI は単純で、強く型付けされています。以下のリストは、Formatter インターフェース定義を示しています。

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter は、Printer および Parser ビルドブロックインターフェースから拡張されています。次のリストは、これら 2 つのインターフェースの定義を示しています。

public interface Printer<T> {

    String print(T fieldValue, Locale locale);
}
import java.text.ParseException;

public interface Parser<T> {

    T parse(String clientValue, Locale locale) throws ParseException;
}

独自の Formatter を作成するには、前述の Formatter インターフェースを実装します。T をパラメーター化して、フォーマットするオブジェクトの型(たとえば、java.util.Date)にします。print() 操作を実装して、クライアントロケールで表示するために T のインスタンスを出力します。parse() 操作を実装して、クライアントロケールから返されたフォーマットされた表現から T のインスタンスを解析します。解析が失敗した場合、Formatter は ParseException または IllegalArgumentException をスローする必要があります。Formatter 実装がスレッドセーフであることを確認してください。

format サブパッケージは、便宜上、いくつかの Formatter 実装を提供します。number パッケージは、java.text.NumberFormat を使用する Number オブジェクトをフォーマットするための NumberStyleFormatterCurrencyStyleFormatterPercentStyleFormatter を提供します。datetime パッケージは、java.util.Date オブジェクトを java.text.DateFormat でフォーマットするための DateFormatter を提供します。

次の DateFormatter は、Formatter の実装例です。

Java
package org.springframework.format.datetime;

public final class DateFormatter implements Formatter<Date> {

    private String pattern;

    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }

    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }

    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}
Kotlin
class DateFormatter(private val pattern: String) : Formatter<Date> {

    override fun print(date: Date, locale: Locale)
            = getDateFormat(locale).format(date)

    @Throws(ParseException::class)
    override fun parse(formatted: String, locale: Locale)
            = getDateFormat(locale).parse(formatted)

    protected fun getDateFormat(locale: Locale): DateFormat {
        val dateFormat = SimpleDateFormat(this.pattern, locale)
        dateFormat.isLenient = false
        return dateFormat
    }
}

Spring チームは、コミュニティ主導の Formatter の貢献を歓迎します。投稿するには GitHub の課題 (英語) を参照してください。

3.5.2. アノテーション駆動の書式設定

フィールドのフォーマットは、フィールド型またはアノテーションによって構成できます。Formatter にアノテーションをバインドするには、AnnotationFormatterFactory を実装します。次のリストは、AnnotationFormatterFactory インターフェースの定義を示しています。

package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

    Set<Class<?>> getFieldTypes();

    Printer<?> getPrinter(A annotation, Class<?> fieldType);

    Parser<?> getParser(A annotation, Class<?> fieldType);
}

実装を作成するには:。A をフォーマットロジックに関連付けるフィールド annotationType にパラメーター化します(例: org.springframework.format.annotation.DateTimeFormat)。getFieldTypes() がアノテーションを使用できるフィールドの型を返すようにします。getPrinter() に Printer を返させて、アノテーション付きフィールドの値を出力させます。getParser() に Parser を返させて、アノテーションフィールドの clientValue を解析させます。

次の例の AnnotationFormatterFactory 実装は、@NumberFormat アノテーションをフォーマッターにバインドして、数値スタイルまたはパターンを指定できるようにします。

Java
public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {

    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }

    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberStyleFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}
Kotlin
class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {

    override fun getFieldTypes(): Set<Class<*>> {
        return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
    }

    override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }

    override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
        return configureFormatterFrom(annotation, fieldType)
    }

    private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
        return if (annotation.pattern.isNotEmpty()) {
            NumberStyleFormatter(annotation.pattern)
        } else {
            val style = annotation.style
            when {
                style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
                style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
                else -> NumberStyleFormatter()
            }
        }
    }
}

次の例に示すように、フォーマットをトリガーするために、@NumberFormat でフィールドにアノテーションを付けることができます。

Java
public class MyModel {

    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal decimal;
}
Kotlin
class MyModel(
    @field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
Format Annotation API

ポータブルフォーマットアノテーション API は org.springframework.format.annotation パッケージに含まれています。@NumberFormat を使用して Double や Long などの Number フィールドをフォーマットし、@DateTimeFormat を使用して java.util.Datejava.util.CalendarLong (ミリ秒のタイムスタンプ用)および JSR-310 java.time をフォーマットできます。

次の例では、@DateTimeFormat を使用して、java.util.Date を ISO 日付(yyyy-MM-dd)としてフォーマットします。

Java
public class MyModel {

    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}
Kotlin
class MyModel(
    @DateTimeFormat(iso= ISO.DATE) private val date: Date
)

3.5.3. FormatterRegistry SPI

FormatterRegistry は、フォーマッタとコンバーターを登録するための SPI です。FormattingConversionService は、ほとんどの環境に適した FormatterRegistry の実装です。プログラムまたは宣言的にこのバリアントを Spring Bean として構成できます。FormattingConversionServiceFactoryBean を使用します。この実装では ConversionService も実装しているため、Spring の DataBinder および Spring Expression Language(SpEL)で使用するように直接構成できます。

次のリストは、FormatterRegistry SPI を示しています。

package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addPrinter(Printer<?> printer);

    void addParser(Parser<?> parser);

    void addFormatter(Formatter<?> formatter);

    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}

前のリストに示すように、フィールド型またはアノテーションによってフォーマッタを登録できます。

FormatterRegistry SPI を使用すると、コントローラー全体でこのような構成を複製する代わりに、フォーマット規則を中央で構成できます。例: すべての日付フィールドを特定の方法でフォーマットするか、特定のアノテーションを持つフィールドを特定の方法でフォーマットすることを強制することができます。共有 FormatterRegistry を使用すると、これらのルールを一度定義すると、フォーマットが必要になるたびに適用されます。

3.5.4. FormatterRegistrar SPI

FormatterRegistrar は、FormatterRegistry を介してフォーマッターとコンバーターを登録するための SPI です。次のリストは、そのインターフェース定義を示しています。

package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}

FormatterRegistrar は、日付の書式設定など、特定の書式設定カテゴリに関連する複数のコンバーターとフォーマッターを登録する場合に役立ちます。また、宣言型の登録が不十分な場合にも役立ちます。たとえば、フォーマッタを、それ自体の <T> とは異なる特定のフィールド型でインデックス付けする必要がある場合や、Printer/Parser ペアを登録する場合などです。次のセクションでは、コンバーターとフォーマッターの登録について詳しく説明します。

3.5.5. Spring MVC でのフォーマットの構成

Spring MVC の章の変換とフォーマットを参照してください。

3.6. グローバルな日付と時刻の形式の構成

デフォルトでは、@DateTimeFormat のアノテーションが付いていない日付と時刻のフィールドは、DateFormat.SHORT スタイルを使用して文字列から変換されます。必要に応じて、独自のグローバル形式を定義してこれを変更できます。

そのためには、Spring がデフォルトのフォーマッターを登録しないようにしてください。代わりに、次の助けを借りてフォーマッターを手動で登録します。

  • org.springframework.format.datetime.standard.DateTimeFormatterRegistrar

  • org.springframework.format.datetime.DateFormatterRegistrar

例: 次の Java 構成は、グローバル yyyyMMdd 形式を登録します。

Java
@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {

        // Use the DefaultFormattingConversionService but do not register defaults
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // Ensure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // Register JSR-310 date conversion with a specific global format
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}
Kotlin
@Configuration
class AppConfig {

    @Bean
    fun conversionService(): FormattingConversionService {
        // Use the DefaultFormattingConversionService but do not register defaults
        return DefaultFormattingConversionService(false).apply {

            // Ensure @NumberFormat is still supported
            addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())

            // Register JSR-310 date conversion with a specific global format
            val registrar = DateTimeFormatterRegistrar()
            registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
            registrar.registerFormatters(this)

            // Register date conversion with a specific global format
            val registrar = DateFormatterRegistrar()
            registrar.setFormatter(DateFormatter("yyyyMMdd"))
            registrar.registerFormatters(this)
        }
    }
}

XML ベースの構成が必要な場合は、FormattingConversionServiceFactoryBean を使用できます。次の例は、その方法を示しています。

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

    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="registerDefaultFormatters" value="false" />
        <property name="formatters">
            <set>
                <bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
                    <property name="dateFormatter">
                        <bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
                            <property name="pattern" value="yyyyMMdd"/>
                        </bean>
                    </property>
                </bean>
            </set>
        </property>
    </bean>
</beans>

Web アプリケーションで日付と時刻の形式を構成する際には、追加の考慮事項があることに注意してください。WebMVC 変換およびフォーマットまたは WebFlux の変換とフォーマットを参照してください。

3.7. Java Bean 検証

Spring Framework は、Java Bean 検証 (英語) API のサポートを提供します。

3.7.1. Bean 検証の概要

Bean 検証は、Java アプリケーションの制約宣言とメタデータを介した検証の一般的なメソッドを提供します。これを使用するには、宣言型の検証制約を使用してドメインモデルプロパティにアノテーションを付けてから、ランタイムによって強制されます。組み込みの制約があり、独自のカスタム制約を定義することもできます。

2 つのプロパティを持つ単純な PersonForm モデルを示す次の例を検討してください。

Java
public class PersonForm {
    private String name;
    private int age;
}
Kotlin
class PersonForm(
        private val name: String,
        private val age: Int
)

Bean 検証では、次の例に示すように制約を宣言できます。

Java
public class PersonForm {

    @NotNull
    @Size(max=64)
    private String name;

    @Min(0)
    private int age;
}
Kotlin
class PersonForm(
    @get:NotNull @get:Size(max=64)
    private val name: String,
    @get:Min(0)
    private val age: Int
)

Bean 検証バリデーターは、宣言された制約に基づいてこのクラスのインスタンスを検証します。API に関する一般情報については、Bean バリデーション (英語) を参照してください。特定の制約については、Hibernate バリデーター (英語) の資料を参照してください。Bean 検証プロバイダーを Spring Bean としてセットアップする方法については、読み続けてください。

3.7.2. Bean 検証プロバイダーの構成

Spring は、Bean 検証プロバイダーを Spring Bean としてブートストラップするなど、Bean 検証 API を完全にサポートしています。これにより、アプリケーションで検証が必要な場所に javax.validation.ValidatorFactory または javax.validation.Validator を挿入できます。

次の例に示すように、LocalValidatorFactoryBean を使用して、デフォルトのバリデーターを Spring Bean として構成できます。

Java
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class AppConfig {

    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }
}
XML
<bean id="validator"
    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

前述の例の基本構成は、Bean 検証をトリガーして、デフォルトのブートストラップメカニズムを使用して初期化します。Hibernate Validator などの Bean 検証プロバイダーは、クラスパスに存在すると予想され、自動的に検出されます。

バリデーターの注入

LocalValidatorFactoryBean は、javax.validation.ValidatorFactory と javax.validation.Validator の両方、および Spring の org.springframework.validation.Validator を実装しています。これらのインターフェースのいずれかへの参照を、検証ロジックを呼び出す必要がある Bean に注入できます。

次の例に示すように、Bean Validation API を直接操作する場合は、javax.validation.Validator への参照を挿入できます。

Java
import javax.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}
Kotlin
import javax.validation.Validator;

@Service
class MyService(@Autowired private val validator: Validator)

次の例に示すように、Bean で Spring 検証 API が必要な場合は、org.springframework.validation.Validator への参照を挿入できます。

Java
import org.springframework.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}
Kotlin
import org.springframework.validation.Validator

@Service
class MyService(@Autowired private val validator: Validator)
カスタム制約の構成

各 Bean 検証制約は、2 つの部分で構成されています。

  • 制約とその構成可能なプロパティを宣言する @Constraint アノテーション。

  • 制約の動作を実装する javax.validation.ConstraintValidator インターフェースの実装。

宣言を実装に関連付けるために、各 @Constraint アノテーションは対応する ConstraintValidator 実装クラスを参照します。実行時に、ConstraintValidatorFactory は、ドメインモデルで制約アノテーションが検出されると、参照される実装をインスタンス化します。

デフォルトでは、LocalValidatorFactoryBean は Spring を使用して ConstraintValidator インスタンスを作成する SpringConstraintValidatorFactory を構成します。これにより、カスタム ConstraintValidators は、他の Spring Bean と同様に依存性注入の恩恵を受けます。

次の例は、カスタム @Constraint 宣言の後に、依存性注入に Spring を使用する関連 ConstraintValidator 実装を示しています。

Java
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
Kotlin
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator::class)
annotation class MyConstraint
Java
import javax.validation.ConstraintValidator;

public class MyConstraintValidator implements ConstraintValidator {

    @Autowired;
    private Foo aDependency;

    // ...
}
Kotlin
import javax.validation.ConstraintValidator

class MyConstraintValidator(private val aDependency: Foo) : ConstraintValidator {

    // ...
}

上記の例が示すように、ConstraintValidator 実装は、他の Spring Bean と同様に、@Autowired の依存関係を持つことができます。

Spring 駆動のメソッド検証

Bean バリデーション 1.1(および Hibernate Validator 4.3 によっても)でサポートされるメソッド検証機能を、MethodValidationPostProcessor Bean 定義を介して Spring コンテキストに統合できます。

Java
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class AppConfig {

    @Bean
    public MethodValidationPostProcessor validationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
}
XML
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

Spring 駆動のメソッド検証の対象となるには、すべてのターゲットクラスに Spring の @Validated アノテーションを付ける必要があります。このアノテーションは、使用する検証グループをオプションで宣言することもできます。Hibernate Validator および Bean バリデーション 1.1 プロバイダーを使用したセットアップの詳細については、MethodValidationPostProcessor (Javadoc) を参照してください。

メソッドの検証は、インターフェース上のメソッドの JDK 動的プロキシまたは CGLIB プロキシのいずれかである、ターゲットクラスの周囲の AOP プロキシに依存します。プロキシの使用には特定の制限があり、その一部は AOP プロキシについてで説明されています。さらに、プロキシされたクラスでは常にメソッドとアクセサーを使用することを忘れないでください。直接フィールドアクセスは機能しません。

追加の構成オプション

ほとんどの場合、デフォルトの LocalValidatorFactoryBean 構成で十分です。メッセージの補間からトラバーサル解決まで、さまざまな Bean 検証コンストラクトの構成オプションが多数あります。これらのオプションの詳細については、LocalValidatorFactoryBean javadoc を参照してください。

3.7.3. DataBinder の構成

Spring 3 以降、Validator を使用して DataBinder インスタンスを構成できます。構成が完了すると、binder.validate() を呼び出して Validator を呼び出すことができます。検証 Errors はすべて、バインダーの BindingResult に自動的に追加されます。

次の例は、DataBinder をプログラムで使用して、ターゲットオブジェクトにバインドした後に検証ロジックを呼び出す方法を示しています。

Java
Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
Kotlin
val target = Foo()
val binder = DataBinder(target)
binder.validator = FooValidator()

// bind to the target object
binder.bind(propertyValues)

// validate the target object
binder.validate()

// get BindingResult that includes any validation errors
val results = binder.bindingResult

dataBinder.addValidators および dataBinder.replaceValidators を介して、複数の Validator インスタンスで DataBinder を構成することもできます。これは、グローバルに構成された Bean 検証と、DataBinder インスタンスでローカルに構成された Spring Validator を組み合わせるときに役立ちます。Spring MVC 検証構成を参照してください。

3.7.4. Spring MVC 3 検証

Spring MVC の章の検証を参照してください。

4. Spring 式言語 (SpEL)

Spring Expression Language(略して "SpEL" )は、実行時にオブジェクトグラフのクエリと操作をサポートする強力な式言語です。言語構文は Unified EL に似ていますが、追加機能、特にメソッド呼び出しと基本的な文字列テンプレート機能を提供します。

他にもいくつかの Java 式言語(OGNL、MVEL、JBoss EL など)がありますが、Spring 式言語は、Spring コミュニティに、サポートされているすべての製品で使用できる単一の十分にサポートされた式言語を提供するために作成されました Spring ポートフォリオ。その言語機能は、Pleiades All in One (JDK, STS, Lombok 付属) Eclipse 用 Spring Tools (英語) 内のコード補完サポートのツール要件を含む、Spring ポートフォリオのプロジェクトの要件によって決まります。つまり、SpEL はテクノロジーに依存しない API に基づいており、必要に応じて他の式言語の実装を統合できます。

SpEL は Spring ポートフォリオ内の式評価の基盤として機能しますが、Spring に直接結び付けられておらず、単独で使用できます。自己完結型であるために、この章の例の多くは SpEL を独立した表現言語であるかのように使用しています。これには、パーサーなどのいくつかのブートストラップインフラストラクチャクラスを作成する必要があります。ほとんどの Spring ユーザーは、このインフラストラクチャを扱う必要はなく、代わりに評価用の式文字列のみを作成できます。この典型的な使用例は、Bean 定義を定義するための式のサポートに示すように、XML またはアノテーションベースの Bean 定義の作成への SpEL の統合です。

この章では、式言語、API、言語構文の機能について説明します。いくつかの場所では、Inventor および Society クラスが式評価のターゲットオブジェクトとして使用されます。これらのクラス宣言と設定するために使用されるデータは、この章の最後にリストされています。

式言語は、次の機能をサポートしています。

  • リテラル式

  • ブール演算子と関係演算子

  • 正規表現

  • クラス式

  • プロパティ、配列、リスト、マップへのアクセス

  • メソッド呼び出し

  • 比較演算子

  • 代入

  • コンストラクターの呼び出し

  • Bean 参照

  • 配列構成

  • インラインリスト

  • インラインマップ

  • 三項演算子

  • 変数

  • ユーザー定義関数

  • コレクションの射影

  • コレクションの選択

  • テンプレート式

4.1. 評価

このセクションでは、SpEL インターフェースとその表現言語の簡単な使用箇所を紹介します。完全な言語リファレンスは言語リファレンスにあります。

次のコードは、リテラル文字列式 Hello World を評価する SpEL API を紹介しています。

Java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 メッセージ変数の値は 'Hello World' です。
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 メッセージ変数の値は 'Hello World' です。

使用する可能性が最も高い SpEL クラスとインターフェースは、org.springframework.expression パッケージとそのサブパッケージ(spel.support など)にあります。

ExpressionParser インターフェースは、式ストリングの解析を担当します。前の例では、式文字列は、周囲の単一引用符で示された文字列リテラルです。Expression インターフェースは、以前に定義された式ストリングを評価するロールを果たします。parser.parseExpression および exp.getValue をそれぞれ呼び出した場合にスローできる 2 つの例外、ParseException および EvaluationException

SpEL は、メソッドの呼び出し、プロパティへのアクセス、コンストラクターの呼び出しなど、幅広い機能をサポートしています。

次のメソッド呼び出しの例では、文字列リテラルで concat メソッドを呼び出します。

Java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1message の値は現在 "Hello World!" です。
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1message の値は現在 "Hello World!" です。

JavaBean プロパティを呼び出す次の例は、String プロパティ Bytes を呼び出します。

Java
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
1 この行は、リテラルをバイト配列に変換します。
Kotlin
val parser = SpelExpressionParser()

// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 この行は、リテラルをバイト配列に変換します。

SpEL は、標準のドット表記(prop1.prop2.prop3 など)とそれに対応するプロパティ値の設定を使用して、ネストされたプロパティもサポートします。パブリックフィールドにもアクセスできます。

次の例は、ドット表記を使用してリテラルの長さを取得する方法を示しています。

Java
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();
1'Hello World'.bytes.length は、リテラルの長さを示します。
Kotlin
val parser = SpelExpressionParser()

// invokes 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") (1)
val length = exp.value as Int
1'Hello World'.bytes.length は、リテラルの長さを示します。

次の例に示すように、文字列リテラルを使用する代わりに、文字列のコンストラクターを呼び出すことができます。

Java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
1 リテラルから新しい String を作成し、大文字にします。
Kotlin
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()")  (1)
val message = exp.getValue(String::class.java)
1 リテラルから新しい String を作成し、大文字にします。

ジェネリクスメソッド public <T> T getValue(Class<T> desiredResultType) の使用に注意してください。このメソッドを使用すると、式の値を目的の結果型にキャストする必要がなくなります。値を型 T にキャストできないか、登録済みの型コンバーターを使用して変換できない場合、EvaluationException がスローされます。

SpEL のより一般的な使用箇所は、特定のオブジェクトインスタンス(ルートオブジェクトと呼ばれる)に対して評価される式文字列を提供することです。次の例は、Inventor クラスのインスタンスから name プロパティを取得する方法、またはブール条件を作成する方法を示しています。

Java
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
Kotlin
// Create and set a calendar
val c = GregorianCalendar()
c.set(1856, 7, 9)

// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")

val parser = SpelExpressionParser()

var exp = parser.parseExpression("name") // Parse name as an expression
val name = exp.getValue(tesla) as String
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true

4.1.1. EvaluationContext を理解する

EvaluationContext インターフェースは、式を評価してプロパティ、メソッド、フィールドを解決し、型変換を実行する際に使用されます。Spring は 2 つの実装を提供します。

  • SimpleEvaluationContext: SpEL 言語構文の全範囲を必要とせず、有意に制限される必要がある式のカテゴリに対して、本質的な SpEL 言語機能および構成オプションのサブセットを公開します。例には、データバインディング式およびプロパティベースのフィルターが含まれますが、これらに限定されません。

  • StandardEvaluationContext: SpEL 言語機能と構成オプションの完全なセットを公開します。これを使用して、デフォルトのルートオブジェクトを指定し、利用可能なすべての評価関連戦略を構成できます。

SimpleEvaluationContext は、SpEL 言語構文のサブセットのみをサポートするように設計されています。Java 型参照、コンストラクター、Bean 参照は除外されます。また、式のプロパティとメソッドのサポートのレベルを明示的に選択する必要があります。デフォルトでは、create() 静的ファクトリメソッドはプロパティへの読み取りアクセスのみを有効にします。ビルダーを入手して、必要なサポートの正確なレベルを構成し、次の 1 つまたはいくつかの組み合わせをターゲットにすることもできます。

  • カスタム PropertyAccessor のみ (反射なし)

  • 読み取り専用アクセスのデータバインディングプロパティ

  • 読み取りおよび書き込み用のデータバインディングプロパティ

型変換

デフォルトでは、SpEL は Spring コア(org.springframework.core.convert.ConversionService)で利用可能な変換サービスを使用します。この変換サービスには、一般的な変換用の多くの組み込みコンバーターが付属していますが、完全に拡張可能であるため、型間でカスタム変換を追加できます。さらに、ジェネリクスに対応しています。つまり、式でジェネリクス型を使用する場合、SpEL は変換を試みて、検出したオブジェクトの型の正確性を維持します。

これは実際にはどういう意味でしょうか? setValue() を使用した割り当てが、List プロパティの設定に使用されているとします。プロパティの型は、実際には List<Boolean> です。SpEL は、リストの要素を配置する前に Boolean に変換する必要があることを認識しています。次の例は、その方法を示しています。

Java
class Simple {
    public List<Boolean> booleanList = new ArrayList<Boolean>();
}

Simple simple = new Simple();
simple.booleanList.add(true);

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

// b is false
Boolean b = simple.booleanList.get(0);
Kotlin
class Simple {
    var booleanList: MutableList<Boolean> = ArrayList()
}

val simple = Simple()
simple.booleanList.add(true)

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")

// b is false
val b = simple.booleanList[0]

4.1.2. パーサー構成

パーサー構成オブジェクト(org.springframework.expression.spel.SpelParserConfiguration)を使用して、SpEL 式パーサーを構成することができます。構成オブジェクトは、一部の式コンポーネントの動作を制御します。例: 配列またはコレクションにインデックスを付け、指定されたインデックスの要素が null の場合、SpEL は自動的に要素を作成できます。これは、プロパティ参照のチェーンで構成される式を使用する場合に役立ちます。配列またはリストにインデックスを付け、配列またはリストの現在のサイズの終わりを超えるインデックスを指定すると、SpEL はそのインデックスに対応するように配列またはリストを自動的に拡張できます。指定されたインデックスに要素を追加するために、SpEL は、指定された値を設定する前に、要素型のデフォルトコンストラクターを使用して要素を作成しようとします。要素型にデフォルトのコンストラクターがない場合、null が配列またはリストに追加されます。値の設定メソッドを知っている組み込みまたはカスタムのコンバーターがない場合、null は指定されたインデックスの配列またはリストに残ります。次の例は、リストを自動的に拡大する方法を示しています。

Java
class Demo {
    public List<String> list;
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true,true);

ExpressionParser parser = new SpelExpressionParser(config);

Expression expression = parser.parseExpression("list[3]");

Demo demo = new Demo();

Object o = expression.getValue(demo);

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
Kotlin
class Demo {
    var list: List<String>? = null
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
val config = SpelParserConfiguration(true, true)

val parser = SpelExpressionParser(config)

val expression = parser.parseExpression("list[3]")

val demo = Demo()

val o = expression.getValue(demo)

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String

4.1.3. SpEL のコンパイル

Spring Framework 4.1 には、基本的な式コンパイラーが含まれています。式は通常解釈され、評価中に多くの動的な柔軟性を提供しますが、最適なパフォーマンスは提供しません。ときどき式を使用する場合はこれで問題ありませんが、Spring Integration などの他のコンポーネントで使用する場合、パフォーマンスは非常に重要になる可能性があり、ダイナミズムは実際には必要ありません。

SpEL コンパイラーは、このニーズに対処することを目的としています。評価中に、コンパイラーは実行時の式の動作を具体化する Java クラスを生成し、そのクラスを使用して式の評価をより高速に実行します。式の周囲に入力できないため、コンパイラーは、コンパイルの実行時に式の解釈された評価中に収集された情報を使用します。例: 純粋に式からプロパティ参照の型を知りませんが、最初に解釈された評価の間に、それが何であるかを見つけます。もちろん、そのような派生情報に基づいてコンパイルを行うと、さまざまな式要素の型が時間とともに変化する場合、後でトラブルを引き起こす可能性があります。このため、コンパイルは、評価が繰り返されても型情報が変更されない式に最適です。

以下の基本的な表現を考えてください:

someArray[0].someProperty.someOtherProperty < 0.1

上記の式には配列アクセス、一部のプロパティの逆参照、数値演算が含まれるため、パフォーマンスの向上は非常に顕著です。50000 反復のマイクロベンチマークの実行例では、インタープリターを使用して評価するのに 75 ミリ秒かかり、コンパイルされたバージョンの式を使用して 3 ミリ秒しかかかりませんでした。

コンパイラー構成

コンパイラーはデフォルトではオンになっていませんが、2 つの異なる方法のいずれかでオンにすることができます。これをオンにするには、パーサー構成プロセス(前述)を使用するか、SpEL の使用箇所が別のコンポーネントに埋め込まれている場合は Spring プロパティを使用します。このセクションでは、これらのオプションの両方について説明します。

コンパイラーは、org.springframework.expression.spel.SpelCompilerMode 列挙型でキャプチャーされる 3 つのモードのいずれかで動作できます。モードは次のとおりです。

  • OFF (default): コンパイラーはオフになります。

  • IMMEDIATE: 即時モードでは、式はできるだけ早くコンパイルされます。これは通常、最初に解釈された評価の後です。コンパイルされた式が失敗する場合(通常、前述のように型の変更が原因)、式の評価の呼び出し元は例外を受け取ります。

  • MIXED: 混合モードでは、式は時間の経過とともにサイレントモードとインタープリターモードを切り替えます。いくつかの解釈された実行の後、コンパイルされたフォームに切り替わり、コンパイルされたフォームに何か問題が発生した場合(前述の型変更など)、式は自動的に再び解釈されたフォームに戻ります。しばらくしてから、別のコンパイル済みフォームを生成し、それに切り替える可能性があります。基本的に、ユーザーが IMMEDIATE モードで取得する例外は、代わりに内部的に処理されます。

 MIXED モードは、副作用のある式に課題を引き起こす可能性があるため、IMMEDIATE モードが存在します。コンパイルされた式が部分的に成功した後に展開した場合、システムの状態に影響を与えている何かをすでに行っている可能性があります。これが発生した場合、式の一部が 2 回実行される可能性があるため、呼び出し側はインタープリターモードで静かに再実行することを望まない場合があります。

モードを選択した後、SpelParserConfiguration を使用してパーサーを構成します。次の例は、その方法を示しています。

Java
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
    this.getClass().getClassLoader());

SpelExpressionParser parser = new SpelExpressionParser(config);

Expression expr = parser.parseExpression("payload");

MyMessage message = new MyMessage();

Object payload = expr.getValue(message);
Kotlin
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
        this.javaClass.classLoader)

val parser = SpelExpressionParser(config)

val expr = parser.parseExpression("payload")

val message = MyMessage()

val payload = expr.getValue(message)

コンパイラーモードを指定する場合、クラスローダーも指定できます(null を渡すことは許可されます)。コンパイルされた式は、指定されたものに作成された子クラスローダーで定義されます。クラスローダーが指定されている場合、式評価プロセスに関係するすべての型を確認できるようにすることが重要です。クラスローダーを指定しない場合、デフォルトのクラスローダーが使用されます(通常、式の評価中に実行されているスレッドのコンテキストクラスローダー)。

コンパイラーを構成する 2 番目の方法は、SpEL が他のコンポーネント内に埋め込まれていて、構成オブジェクトを介して構成できない場合に使用することです。このような場合、JVM システムプロパティ(または SpringProperties メカニズム)を介して spring.expression.compiler.mode プロパティを SpelCompilerMode 列挙値(offimmediatemixed)のいずれかに設定することができます。

コンパイラーの制限

Spring Framework 4.1 以降、基本的なコンパイルフレームワークが用意されています。ただし、フレームワークはまだすべての種類の式のコンパイルをサポートしていません。最初の焦点は、パフォーマンスが重要なコンテキストで使用される可能性が高い一般的な表現にありました。現在、次の種類の式はコンパイルできません。

  • 代入を含む式

  • 変換サービスに依存する式

  • カスタムリゾルバーまたはアクセサーを使用する式

  • 選択または射影を使用した式

将来的には、より多くの種類の式がコンパイル可能になる予定です。

4.2. Bean 定義の式

BeanDefinition インスタンスを定義するために、XML ベースまたはアノテーションベースの構成メタデータで SpEL 式を使用できます。どちらの場合も、式を定義する構文は #{ <expression string> } の形式です。

4.2.1. XML 構成

次の例に示すように、式を使用してプロパティまたはコンストラクターの引数値を設定できます。

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

アプリケーションコンテキスト内のすべての Bean は、共通の Bean 名を持つ定義済み変数として使用できます。これには、ランタイム環境にアクセスするための environment (型 org.springframework.core.env.Environment の)および systemProperties および systemEnvironment (型 Map<String, Object> の)などの標準コンテキスト Bean が含まれます。

次の例は、systemProperties Bean への SpEL 変数としてのアクセスを示しています。

<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

    <!-- other properties -->
</bean>

ここでは、事前定義された変数の前に # 記号を付ける必要がないことに注意してください。

次の例に示すように、他の Bean プロパティを名前で参照することもできます。

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>

    <!-- other properties -->
</bean>

4.2.2. アノテーション設定

デフォルト値を指定するには、フィールド、メソッド、メソッドまたはコンストラクターのパラメーターに @Value アノテーションを配置できます。

次の例では、フィールドのデフォルト値を設定します。

Java
public class FieldValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    private String defaultLocale;

    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}
Kotlin
class FieldValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    var defaultLocale: String? = null
}

次の例は、同等のプロパティ setter メソッドを示しています。

Java
public class PropertyValueTestBean {

    private String defaultLocale;

    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }
}
Kotlin
class PropertyValueTestBean {

    @Value("#{ systemProperties['user.region'] }")
    var defaultLocale: String? = null
}

次の例に示すように、オートワイヤーされたメソッドとコンストラクターも @Value アノテーションを使用できます。

Java
public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
            @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
Kotlin
class SimpleMovieLister {

    private lateinit var movieFinder: MovieFinder
    private lateinit var defaultLocale: String

    @Autowired
    fun configure(movieFinder: MovieFinder,
                @Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
        this.movieFinder = movieFinder
        this.defaultLocale = defaultLocale
    }

    // ...
}
Java
public class MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
            @Value("#{systemProperties['user.country']}") String defaultLocale) {
        this.customerPreferenceDao = customerPreferenceDao;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
Kotlin
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
            @Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
    // ...
}

4.3. 言語リファレンス

このセクションでは、Spring 式言語の機能について説明します。次のトピックについて説明します。

4.3.1. リテラル式

サポートされているリテラル式の型は、文字列、数値(int、real、hex)、boolean、null です。文字列は単一引用符で区切られます。文字列に単一引用符自体を挿入するには、2 つの単一引用符文字を使用します。

次のリストは、リテラルの簡単な使用箇所を示しています。通常、これらはこのように単独で使用されるのではなく、より複雑な式の一部として使用されます。たとえば、論理比較演算子の片側でリテラルを使用します。

Java
ExpressionParser parser = new SpelExpressionParser();

// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();

Object nullValue = parser.parseExpression("null").getValue();
Kotlin
val parser = SpelExpressionParser()

// evals to "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String

val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double

// evals to 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int

val trueValue = parser.parseExpression("true").value as Boolean

val nullValue = parser.parseExpression("null").value

数値は、負の符号、指数表記、小数点の使用をサポートします。デフォルトでは、実数は Double.parseDouble() を使用して解析されます。

4.3.2. プロパティ、配列、リスト、マップ、インデクサー

プロパティ参照を使用したナビゲートは簡単です。これを行うには、ピリオドを使用してネストされたプロパティ値を示します。Inventor クラスのインスタンスである pupin および tesla には、例で使用されているクラスセクションにリストされているデータが入力されています。オブジェクトグラフを「下」に移動して、テスラの誕生年とピューピンの誕生都市を取得するには、次の式を使用します。

Java
// evals to 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
Kotlin
// evals to 1856
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int

val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String

プロパティ名の最初の文字では、大文字と小文字を区別しないことが許可されています。上記の例の式は、それぞれ Birthdate.Year + 1900 および PlaceOfBirth.City と書くことができます。さらに、プロパティには、オプションでメソッド呼び出しを介してアクセスできます(たとえば、placeOfBirth.city ではなく getPlaceOfBirth().getCity())。

配列とリストの内容は、次の例に示すように、角括弧表記を使用して取得されます。

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// Inventions Array

// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String.class);

// Members List

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
        context, ieee, String.class);
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

// Inventions Array

// evaluates to "Induction motor"
val invention = parser.parseExpression("inventions[3]").getValue(
        context, tesla, String::class.java)

// Members List

// evaluates to "Nikola Tesla"
val name = parser.parseExpression("members[0].name").getValue(
        context, ieee, String::class.java)

// List and Array navigation
// evaluates to "Wireless communication"
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
        context, ieee, String::class.java)

マップの内容は、括弧内のリテラルキー値を指定することにより取得されます。次の例では、officers マップのキーは文字列であるため、文字列リテラルを指定できます。

Java
// Officer's Dictionary

Inventor pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor.class);

// evaluates to "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String.class);

// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia");
Kotlin
// Officer's Dictionary

val pupin = parser.parseExpression("officers['president']").getValue(
        societyContext, Inventor::class.java)

// evaluates to "Idvor"
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
        societyContext, String::class.java)

// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
        societyContext, "Croatia")

4.3.3. インラインリスト

{} 表記を使用して、式でリストを直接表現できます。

Java
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
Kotlin
// evaluates to a Java list containing the four numbers
val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*>

val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>

{} 自体は、空のリストを意味します。パフォーマンス上の理由から、リスト自体が固定リテラルで完全に構成されている場合、(各評価で新しいリストを作成するのではなく)式を表す定数リストが作成されます。

4.3.4. インラインマップ

{key:value} 表記を使用して、式でマップを直接表現することもできます。次の例は、その方法を示しています。

Java
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
Kotlin
// evaluates to a Java map containing the two entries
val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, *>

val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<*, *>

{:} 自体は、空のマップを意味します。パフォーマンス上の理由から、マップ自体が固定リテラルまたはその他のネストされた定数構造(リストまたはマップ)で構成されている場合、式を表す定数マップが作成されます(評価ごとに新しいマップを作成するのではありません)。マップキーの引用はオプションです(キーにピリオド(.)が含まれている場合を除く)。上記の例では、引用符で囲まれたキーを使用していません。

4.3.5. 配列構成

使い慣れた Java 構文を使用して配列を構築できます。オプションで、構築時に配列を設定する初期化子を指定できます。次の例は、その方法を示しています。

Java
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
Kotlin
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray

// Array with initializer
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray

// Multi dimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>

現在、多次元配列を作成するときに初期化子を指定することはできません。

4.3.6. メソッド

一般的な Java プログラミング構文を使用してメソッドを呼び出すことができます。リテラルでメソッドを呼び出すこともできます。可変引数もサポートされています。次の例は、メソッドを呼び出す方法を示しています。

Java
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);
Kotlin
// string literal, evaluates to "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java)

// evaluates to true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean::class.java)

4.3.7. 演算子

Spring Expression Language は、次の種類の演算子をサポートしています。

比較演算子

関係演算子(等しい、等しくない、より小さい、以下、より大きい、以上)は、標準の演算子表記を使用してサポートされます。次のリストは、演算子のいくつかの例を示しています。

Java
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
Kotlin
// evaluates to true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)

// evaluates to false
val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java)

// evaluates to true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)

null との大小比較は、単純なルールに従います。null はゼロとしてではなく、ゼロとして扱われます。結果として、他の値は常に null より大きく(X > null は常に true)、他の値がゼロより小さくなることはありません(X < null は常に false です)。

代わりに数値比較を使用する場合は、ゼロとの比較を優先して、数値ベースの null 比較を避けます(たとえば、X > 0 または X < 0)。

標準の関係演算子に加えて、SpEL は instanceof および正規表現ベースの matches 演算子をサポートしています。次のリストは、両方の例を示しています。

Java
// evaluates to false
boolean falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
Kotlin
// evaluates to false
val falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean::class.java)

// evaluates to true
val trueValue = parser.parseExpression(
        "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)

// evaluates to false
val falseValue = parser.parseExpression(
        "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
プリミティブ型はラッパー型にすぐにボックス化されるため、注意してください。例: 予想どおり、1 instanceof T(int) は false と評価され、1 instanceof T(Integer) は true と評価されます。

各記号演算子は、純粋にアルファベットの同等物として指定することもできます。これにより、使用されているシンボルが、式が埋め込まれているドキュメント型(XML ドキュメントなど)に対して特別な意味を持つという問題が回避されます。同等のテキストは次のとおりです。

  • lt (<)

  • gt (>)

  • le (<=)

  • ge (>=)

  • eq (==)

  • ne (!=)

  • div (/)

  • mod (%)

  • not (!).

テキスト演算子はすべて大文字と小文字を区別しません。

論理演算子

SpEL は次の論理演算子をサポートしています。

  • and (&&)

  • or (||)

  • not (!)

次の例は、論理演算子の使用方法を示しています。

Java
// -- AND --

// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- OR --

// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- NOT --

// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
Kotlin
// -- AND --

// evaluates to false
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)

// evaluates to true
val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)

// -- OR --

// evaluates to true
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)

// evaluates to true
val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)

// -- NOT --

// evaluates to false
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)

// -- AND and NOT --
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
数学演算子

数値と文字列の両方で加算演算子(+)を使用できます。減算(-)、乗算(*)、除算(/)演算子は、数値に対してのみ使用できます。数値に対して、モジュラス(%)および指数パワー(^)演算子を使用することもできます。標準の演算子の優先順位が適用されます。次の例は、使用中の数学演算子を示しています。

Java
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class);  // 2

String testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String.class);  // 'test string'

// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class);  // 4

double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class);  // -9000

// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class);  // 6

double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class);  // 24.0

// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class);  // -2

double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class);  // 1.0

// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class);  // 3

int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class);  // 1

// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class);  // -21
Kotlin
// Addition
val two = parser.parseExpression("1 + 1").getValue(Int::class.java)  // 2

val testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String::class.java)  // 'test string'

// Subtraction
val four = parser.parseExpression("1 - -3").getValue(Int::class.java)  // 4

val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java)  // -9000

// Multiplication
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java)  // 6

val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java)  // 24.0

// Division
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java)  // -2

val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java)  // 1.0

// Modulus
val three = parser.parseExpression("7 % 4").getValue(Int::class.java)  // 3

val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java)  // 1

// Operator precedence
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java)  // -21
割り当て演算子

プロパティを設定するには、代入演算子(=)を使用します。これは通常、setValue の呼び出し内で実行されますが、getValue の呼び出し内で実行することもできます。次のリストは、代入演算子を使用する両方の方法を示しています。

Java
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");

// alternatively
String aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
Kotlin
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()

parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic")

// alternatively
val aleks = parser.parseExpression(
        "name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)

4.3.8. タイプ

特別な T 演算子を使用して、java.lang.Class のインスタンス(型)を指定できます。静的メソッドは、この演算子も使用して呼び出されます。StandardEvaluationContext は TypeLocator を使用して型を検索し、StandardTypeLocator (交換可能)は java.lang パッケージを理解して構築されています。つまり、java.lang パッケージ内の型への T() 参照は完全に修飾する必要はありませんが、他のすべての型参照は完全修飾する必要があります。次の例は、T 演算子の使用方法を示しています。

Java
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);
Kotlin
val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java)

val stringClass = parser.parseExpression("T(String)").getValue(Class::class.java)

val trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean::class.java)

4.3.9. コンストラクター

new 演算子を使用して、コンストラクターを呼び出すことができます。java.lang パッケージ(IntegerFloatString など)にあるものを除くすべての型には、完全修飾クラス名を使用する必要があります。次の例は、new 演算子を使用してコンストラクターを呼び出す方法を示しています。

Java
Inventor einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);

// create new Inventor instance within the add() method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);
Kotlin
val einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor::class.java)

// create new Inventor instance within the add() method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
        .getValue(societyContext)

4.3.10. 変数

#variableName 構文を使用して、式の変数を参照できます。変数は、EvaluationContext 実装で setVariable メソッドを使用して設定されます。

有効な変数名は、サポートされている次の文字の 1 つ以上で構成されている必要があります。

  • アルファベット: A から Z および a から z

  • 数字: 0 から 9

  • 下線: _

  • ドル記号: $

次の例は、変数の使用方法を示しています。

Java
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName())  // "Mike Tesla"
Kotlin
val tesla = Inventor("Nikola Tesla", "Serbian")

val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("newName", "Mike Tesla")

parser.parseExpression("name = #newName").getValue(context, tesla)
println(tesla.name)  // "Mike Tesla"
#this および #root 変数

#this 変数は常に定義され、現在の評価オブジェクトを参照します(どの非修飾参照が解決されるかに対して)。#root 変数は常に定義され、ルートコンテキストオブジェクトを参照します。#this は式のコンポーネントが評価されると異なる場合がありますが、#root は常にルートを参照します。次の例は、#this 変数と #root 変数の使用方法を示しています。

Java
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);
Kotlin
// create an array of integers
val primes = ArrayList<Int>()
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))

// create parser and set variable 'primes' as the array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
        "#primes.?[#this>10]").getValue(context) as List<Int>

4.3.11. 関数

式文字列内で呼び出すことができるユーザー定義関数を登録することにより、SpEL を継承できます。関数は EvaluationContext を介して登録されます。次の例は、ユーザー定義関数を登録する方法を示しています。

Java
Method method = ...;

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
Kotlin
val method: Method = ...

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)

例: 文字列を逆にする次のユーティリティメソッドを検討します。

Java
public abstract class StringUtils {

    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}
Kotlin
fun reverseString(input: String): String {
    val backwards = StringBuilder(input.length)
    for (i in 0 until input.length) {
        backwards.append(input[input.length - 1 - i])
    }
    return backwards.toString()
}

その後、次の例に示すように、前述の方法を登録して使用できます。

Java
ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));

String helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String.class);
Kotlin
val parser = SpelExpressionParser()

val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString::javaMethod)

val helloWorldReversed = parser.parseExpression(
        "#reverseString('hello')").getValue(context, String::class.java)

4.3.12. Bean 参照

評価コンテキストが Bean リゾルバーで構成されている場合、@ シンボルを使用して式から Bean を検索できます。次の例は、その方法を示しています。

Java
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
Kotlin
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
val bean = parser.parseExpression("@something").getValue(context)

ファクトリ Bean 自体にアクセスするには、代わりに Bean 名の前に & シンボルを付ける必要があります。次の例は、その方法を示しています。

Java
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
Kotlin
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
val bean = parser.parseExpression("&foo").getValue(context)

4.3.13. 三項演算子 (If-Then-Else)

式内で if-then-else 条件ロジックを実行するには、三項演算子を使用できます。次のリストは、最小限の例を示しています。

Java
String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);
Kotlin
val falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String::class.java)

この場合、ブール値 false は文字列値 'falseExp' を返します。より現実的な例を次に示します。

Java
parser.parseExpression("name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
        "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

String queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
Kotlin
parser.parseExpression("name").setValue(societyContext, "IEEE")
societyContext.setVariable("queryName", "Nikola Tesla")

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"

val queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String::class.java)
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

三項演算子のさらに短い構文については、エルビス演算子の次のセクションを参照してください。

4.3.14. エルヴィスオペレーター

Elvis 演算子は、三項演算子構文の短縮形であり、Groovy (英語) 言語で使用されます。三項演算子構文では、次の例に示すように、通常、変数を 2 回繰り返す必要があります。

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

代わりに、エルビス演算子を使用できます(エルビスの髪型に似ているため)。次の例は、エルビス演算子の使用方法を示しています。

Java
ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name);  // 'Unknown'
Kotlin
val parser = SpelExpressionParser()

val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name)  // 'Unknown'

次のリストは、より複雑な例を示しています。

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla

tesla.setName(null);
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

val tesla = Inventor("Nikola Tesla", "Serbian")
var name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Nikola Tesla

tesla.setName(null)
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name)  // Elvis Presley

Elvis 演算子を使用して、式にデフォルト値を適用できます。次の例は、@Value 式で Elvis 演算子を使用する方法を示しています。

@Value("#{systemProperties['pop3.port'] ?: 25}")

これは、定義されている場合はシステムプロパティ pop3.port を、定義されていない場合は 25 を注入します。

4.3.15. セーフナビゲーション演算子

セーフナビゲーション演算子は、NullPointerException を回避するために使用され、Groovy (英語) 言語に由来します。通常、オブジェクトへの参照がある場合、オブジェクトのメソッドまたはプロパティにアクセスする前に、それが null でないことを確認する必要があります。これを回避するために、セーフナビゲーション演算子は例外をスローする代わりに null を返します。次の例は、セーフナビゲーション演算子の使用方法を示しています。

Java
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // Smiljan

tesla.setPlaceOfBirth(null);
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city);  // null - does not throw NullPointerException!!!
Kotlin
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))

var city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city)  // Smiljan

tesla.setPlaceOfBirth(null)
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city)  // null - does not throw NullPointerException!!!

4.3.16. コレクションの選択

選択は、エントリから選択することでソースコレクションを別のコレクションに変換できる強力な式言語機能です。

選択には .?[selectionExpression] の構文が使用されます。コレクションをフィルタリングし、元の要素のサブセットを含む新しいコレクションを返します。例: 次の例に示すように、選択するとセルビアの発明者のリストを簡単に取得できます。

Java
List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "members.?[nationality == 'Serbian']").getValue(societyContext);
Kotlin
val list = parser.parseExpression(
        "members.?[nationality == 'Serbian']").getValue(societyContext) as List<Inventor>

選択は、配列および java.lang.Iterable または java.util.Map を実装するすべてのものでサポートされています。リストまたは配列の場合、選択条件は個々の要素に対して評価されます。マップに対して、選択条件は各マップエントリ(Java 型 Map.Entry のオブジェクト)に対して評価されます。各マップエントリには、選択で使用するプロパティとしてアクセス可能な key および value があります。

次の式は、エントリの値が 27 未満である元のマップの要素で構成される新しいマップを返します。

Java
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
Kotlin
val newMap = parser.parseExpression("map.?[value<27]").getValue()

選択したすべての要素を返すことに加えて、最初または最後の要素のみを取得できます。選択に一致する最初の要素を取得するための構文は .^[selectionExpression] です。最後に一致する選択を取得するための構文は .$[selectionExpression] です。

4.3.17. コレクションの射影

射影により、コレクションは部分式の評価を促進し、結果は新しいコレクションになります。射影の構文は .![projectionExpression] です。例: 発明者のリストがあるが、彼らが生まれた都市のリストが必要だとします。事実上、発明者リストのすべてのエントリについて "placeOfBirth.city" を評価したいと思います。次の例では、射影を使用してこれを行います。

Java
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
Kotlin
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>

射影は、配列および java.lang.Iterable または java.util.Map を実装するすべてのものでサポートされています。マップを使用して射影を駆動する場合、射影式はマップ内の各エントリ(Java Map.Entry として表されます)に対して評価されます。マップ全体の射影の結果は、各マップエントリに対する射影式の評価で構成されるリストです。

4.3.18. 式テンプレート

式テンプレートを使用すると、リテラルテキストを 1 つ以上の評価ブロックと混合できます。各評価ブロックは、定義可能なプレフィックス文字とサフィックス文字で区切られています。一般的な選択は、次の例に示すように、#{ } を区切り文字として使用することです。

Java
String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        new TemplateParserContext()).getValue(String.class);

// evaluates to "random number is 0.7038186818312008"
Kotlin
val randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        TemplateParserContext()).getValue(String::class.java)

// evaluates to "random number is 0.7038186818312008"

文字列は、リテラルテキスト 'random number is ' と #{ } 区切り文字内の式を評価した結果(この場合、その random() メソッドを呼び出した結果)を連結することにより評価されます。parseExpression() メソッドの 2 番目の引数は、型 ParserContext です。ParserContext インターフェースは、式テンプレート機能をサポートするために、式の解析方法に影響を与えるために使用されます。TemplateParserContext の定義は次のとおりです。

Java
public class TemplateParserContext implements ParserContext {

    public String getExpressionPrefix() {
        return "#{";
    }

    public String getExpressionSuffix() {
        return "}";
    }

    public boolean isTemplate() {
        return true;
    }
}
Kotlin
class TemplateParserContext : ParserContext {

    override fun getExpressionPrefix(): String {
        return "#{"
    }

    override fun getExpressionSuffix(): String {
        return "}"
    }

    override fun isTemplate(): Boolean {
        return true
    }
}

4.4. 例で使用されるクラス

このセクションでは、この章全体の例で使用されるクラスをリストします。

Inventor.Java
package org.spring.samples.spel.inventor;

import java.util.Date;
import java.util.GregorianCalendar;

public class Inventor {

    private String name;
    private String nationality;
    private String[] inventions;
    private Date birthdate;
    private PlaceOfBirth placeOfBirth;

    public Inventor(String name, String nationality) {
        GregorianCalendar c= new GregorianCalendar();
        this.name = name;
        this.nationality = nationality;
        this.birthdate = c.getTime();
    }

    public Inventor(String name, Date birthdate, String nationality) {
        this.name = name;
        this.nationality = nationality;
        this.birthdate = birthdate;
    }

    public Inventor() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }

    public Date getBirthdate() {
        return birthdate;
    }

    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    public PlaceOfBirth getPlaceOfBirth() {
        return placeOfBirth;
    }

    public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
        this.placeOfBirth = placeOfBirth;
    }

    public void setInventions(String[] inventions) {
        this.inventions = inventions;
    }

    public String[] getInventions() {
        return inventions;
    }
}
Inventor.kt
class Inventor(
    var name: String,
    var nationality: String,
    var inventions: Array<String>? = null,
    var birthdate: Date =  GregorianCalendar().time,
    var placeOfBirth: PlaceOfBirth? = null)
PlaceOfBirth.java
package org.spring.samples.spel.inventor;

public class PlaceOfBirth {

    private String city;
    private String country;

    public PlaceOfBirth(String city) {
        this.city=city;
    }

    public PlaceOfBirth(String city, String country) {
        this(city);
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String s) {
        this.city = s;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }
}
PlaceOfBirth.kt
class PlaceOfBirth(var city: String, var country: String? = null) {
Society.java
package org.spring.samples.spel.inventor;

import java.util.*;

public class Society {

    private String name;

    public static String Advisors = "advisors";
    public static String President = "president";

    private List<Inventor> members = new ArrayList<Inventor>();
    private Map officers = new HashMap();

    public List getMembers() {
        return members;
    }

    public Map getOfficers() {
        return officers;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isMember(String name) {
        for (Inventor inventor : members) {
            if (inventor.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }
}
Society.kt
package org.spring.samples.spel.inventor

import java.util.*

class Society {

    val Advisors = "advisors"
    val President = "president"

    var name: String? = null

    val members = ArrayList<Inventor>()
    val officers = mapOf<Any, Any>()

    fun isMember(name: String): Boolean {
        for (inventor in members) {
            if (inventor.name == name) {
                return true
            }
        }
        return false
    }
}

5. Spring によるアスペクト指向プログラミング

アスペクト指向プログラミング(AOP)は、プログラム構造に関する別の考え方を提供することにより、オブジェクト指向プログラミング(OOP)を補完します。OOP のモジュール性の重要な単位はクラスですが、AOP のモジュール性の単位はアスペクトです。アスペクトにより、複数の型とオブジェクトにまたがる関心事(トランザクション管理など)のモジュール化が可能になります。(このような懸念は、AOP の文献ではしばしば「横断的」関心事と呼ばれています。)

Spring の重要なコンポーネントの 1 つは、AOP フレームワークです。Spring IoC コンテナーは AOP に依存しませんが(必要ない場合は AOP を使用する必要はありません)、AOP は Spring IoC を補完して、非常に有能なミドルウェアソリューションを提供します。

AspectJ ポイントカットを使用した Spring AOP

Spring は、スキーマベースのアプローチまたは @AspectJ アノテーションスタイルのいずれかを使用して、カスタムアスペクトを記述するシンプルかつ強力な方法を提供します。これらのスタイルは両方とも、Spring AOP をウィービングに使用しながら、完全に型指定されたアドバイスと AspectJ ポイントカット言語の使用を提供します。

この章では、スキーマおよび @AspectJ ベースの AOP サポートについて説明します。下位レベルの AOP サポートについては、次の章で説明します

AOP は Spring Framework で次の目的で使用されます。

  • 宣言的なエンタープライズサービスを提供します。最も重要なそのようなサービスは、宣言的なトランザクション管理です。

  • ユーザーにカスタムアスペクトを実装させ、OOP と AOP の使用を補完します。

汎用の宣言型サービスまたはプーリングなどの事前にパッケージ化された宣言型ミドルウェアサービスのみに関心がある場合は、Spring AOP を直接操作する必要はなく、この章のほとんどをスキップできます。

5.1. AOP の概念

いくつかの中心的な AOP の概念と用語を定義することから始めましょう。これらの用語は、Spring 固有のものではありません。残念ながら、AOP の用語は特に直感的ではありません。ただし、Spring が独自の用語を使用すると、さらに混乱を招きます。

  • アスペクト: 複数のクラスにまたがる関心事のモジュール化。トランザクション管理は、エンタープライズ Java アプリケーションにおける横断的な関心事の良い例です。Spring AOP では、アスペクトは通常のクラス(スキーマベースのアプローチ)または @Aspect アノテーションが付けられた通常のクラス(@AspectJ スタイル)を使用して実装されます。

  • ジョインポイント: メソッドの実行や例外の処理など、プログラムの実行中のポイント。Spring AOP では、ジョインポイントは常にメソッドの実行を表します。

  • アドバイス: 特定のジョインポイントでアスペクトによって実行されるアクション。さまざまな種類のアドバイスには、"around"、"before"、"after" アドバイスが含まれます。(アドバイスの型については後で説明します)Spring を含む多くの AOP フレームワークは、アドバイスをインターセプターとしてモデル化し、ジョインポイント周辺でインターセプターのチェーンを維持します。

  • ポイントカット: ジョインポイントに一致する述語。アドバイスはポイントカット式に関連付けられ、ポイントカットに一致する任意のジョインポイントで実行されます(たとえば、特定の名前のメソッドの実行)。ポイントカット式と一致するジョインポイントの概念は AOP の中心であり、Spring はデフォルトで AspectJ ポイントカット式言語を使用します。

  • 導入: 型に代わって追加のメソッドまたはフィールドを宣言します。Spring AOP を使用すると、推奨オブジェクトに新しいインターフェース(および対応する実装)を導入できます。例: 導入を使用して、Bean に IsModified インターフェースを実装させ、キャッシングを簡素化できます。(概要は、AspectJ コミュニティでの型間宣言として知られています。)

  • 対象オブジェクト: 1 つ以上のアスペクトによってアドバイスされているオブジェクト。「推奨オブジェクト」とも呼ばれます。Spring AOP はランタイムプロキシを使用して実装されるため、このオブジェクトは常にプロキシオブジェクトです。

  • AOP プロキシ: アスペクト契約を実装するために AOP フレームワークによって作成されたオブジェクト(メソッドの実行などをアドバイス)。Spring Framework では、AOP プロキシは JDK 動的プロキシまたは CGLIB プロキシです。

  • ウィービング: アスペクトを他のアプリケーション型またはオブジェクトとリンクして、推奨オブジェクトを作成します。これは、コンパイル時(たとえば、AspectJ コンパイラーを使用)、ロード時、実行時に実行できます。Spring AOP は、他の純粋な Java AOP フレームワークと同様に、実行時にウィービングを実行します。

Spring AOP には、次の種類のアドバイスが含まれています。

  • Before アドバイス: ジョインポイントの前に実行されるが、例外がスローされない限り、実行フローがジョインポイントに進むことを防ぐ機能がないアドバイス。

  • After returning アドバイス: ジョインポイントが正常に完了した後に実行するアドバイス(たとえば、メソッドが例外をスローせずに戻る場合)。

  • After throwing アドバイス: 例外をスローしてメソッドが終了した場合に実行されるアドバイス。

  • After (finally) アドバイス: ジョインポイントが存在する方法(通常または例外的なリターン)に関係なく実行されるアドバイス。

  • Around アドバイス: メソッド呼び出しなどのジョインポイントを囲むアドバイス。これは最も強力なアドバイスです。Around アドバイスは、メソッド呼び出しの前後にカスタム動作を実行できます。また、ジョインポイントに進むか、独自の戻り値を返すか例外をスローすることにより、推奨されるメソッド実行をショートカットするかを選択する責任もあります。

Around アドバイスは、最も一般的な種類のアドバイスです。Spring AOP は、AspectJ と同様、あらゆる種類のアドバイスを提供するため、必要な動作を実装できる最も強力でないアドバイス型を使用することをお勧めします。例: メソッドの戻り値でキャッシュを更新するだけでよい場合は、around アドバイスよりも after returning アドバイスを実装する方が適切ですが、around アドバイスでも同じことができます。最も具体的なアドバイス型を使用すると、エラーが発生する可能性が低く、シンプルなプログラミングモデルが提供されます。例: 回避アドバイスに使用される JoinPoint で proceed() メソッドを呼び出す必要はないため、呼び出しに失敗することはありません。

すべてのアドバイスパラメーターは静的に型付けされているため、Object 配列ではなく、適切な型(たとえば、メソッド実行からの戻り値の型)のアドバイスパラメーターを操作できます。

ポイントカットと一致するジョインポイントの概念は、AOP の鍵であり、インターセプトのみを提供する古いテクノロジーと区別します。ポイントカットを使用すると、オブジェクト指向の階層とは無関係にアドバイスをターゲットにできます。例: 複数のオブジェクト(サービス層のすべてのビジネスオペレーションなど)にまたがる一連のメソッドに宣言型トランザクション管理を提供するアラウンドアドバイスを適用できます。

5.2. Spring AOP の機能とゴール

Spring AOP は、純粋な Java で実装されています。特別なコンパイルプロセスは必要ありません。Spring AOP はクラスローダー階層を制御する必要がないため、サーブレットコンテナーまたはアプリケーションサーバーでの使用に適しています。

Spring AOP は現在、メソッド実行のジョインポイント(Spring Bean でのメソッドの実行をアドバイスする)のみをサポートしています。フィールドインターセプトは実装されていませんが、コア Spring AOP API を壊すことなくフィールドインターセプトのサポートを追加できます。フィールドアクセスをアドバイスし、ジョインポイントを更新する必要がある場合は、AspectJ などの言語を検討してください。

Spring AOP の AOP へのアプローチは、他のほとんどの AOP フレームワークのアプローチとは異なります。目的は、最も完全な AOP 実装を提供することではありません(ただし、Spring AOP は非常に優れています)。むしろ、AOP 実装と Spring IoC を密接に統合して、エンタープライズアプリケーションの一般的な問題を解決することを目的としています。

たとえば、Spring Framework の AOP 機能は通常、Spring IoC コンテナーと組み合わせて使用されます。アスペクトは、通常の Bean 定義構文を使用して構成されます(ただし、これにより強力な「自動プロキシ」機能が可能になります)。これは、他の AOP 実装との決定的な違いです。Spring AOP では、非常にきめの細かいオブジェクト(通常はドメインオブジェクト)のアドバイスなど、いくつかのことを簡単または効率的に行うことはできません。このような場合には、AspectJ が最適です。ただし、私たちの経験では、Spring AOP は、AOP に対応しているエンタープライズ Java アプリケーションのほとんどの問題に対する優れたソリューションを提供します。

Spring AOP は、包括的な AOP ソリューションを提供するために AspectJ と競合することは決してありません。Spring AOP などのプロキシベースのフレームワークと AspectJ などの本格的なフレームワークはどちらも価値があり、競争ではなく補完的なものであると考えています。Spring は、Spring AOP と IoC を AspectJ とシームレスに統合し、一貫した Spring ベースのアプリケーションアーキテクチャ内で AOP のすべての使用を可能にします。この統合は、Spring AOP API または AOP Alliance API には影響しません。Spring AOP は下位互換性を維持しています。Spring AOP API の説明については、次の章を参照してください。

Spring Framework の中心的な教義の 1 つは、非侵襲性です。これは、フレームワーク固有のクラスとインターフェースをビジネスモデルまたはドメインモデルに強制的に導入するべきではないという考え方です。ただし、一部の場所では、Spring Framework は、Spring フレームワーク固有の依存関係をコードベースに導入するオプションを提供します。そのようなオプションを提供する理由は、特定のシナリオでは、そのようなメソッドで特定の機能の一部を読んだりコーディングしたりするのが簡単です。ただし、Spring Framework(ほとんど)は常に選択肢を提供します。特定のユースケースまたはシナリオに最適なオプションについて、十分な情報に基づいて判断することができます。

この章に関連するそのような選択の 1 つは、どの AOP フレームワーク(およびどの AOP スタイル)を選択するかです。AspectJ、Spring AOP、またはその両方を選択できます。また、@AspectJ アノテーションスタイルのアプローチまたは Spring XML 構成スタイルのアプローチのいずれかを選択できます。この章が最初に @AspectJ スタイルのアプローチを導入することを選択したという事実は、Spring チームが Spring XML 構成スタイルよりも @AspectJ アノテーションスタイルのアプローチを好むことを示すものと解釈すべきではありません。

各スタイルの「理由と理由」の詳細については、使用する AOP 宣言スタイルの選択を参照してください。

5.3. AOP プロキシ

Spring AOP は、デフォルトで AOP プロキシに標準 JDK 動的プロキシを使用します。これにより、任意のインターフェース(またはインターフェースのセット)をプロキシできます。

Spring AOP は CGLIB プロキシも使用できます。これは、インターフェースではなくクラスをプロキシするために必要です。デフォルトでは、ビジネスオブジェクトがインターフェースを実装しない場合、CGLIB が使用されます。クラスではなくインターフェースにプログラミングすることをお勧めするため、ビジネスクラスは通常 1 つ以上のビジネスインターフェースを実装します。インターフェース上で宣言されていないメソッドをアドバイスする必要がある場合や、プロキシオブジェクトをメソッドに具象型として渡す必要がある場合(まれに)に、CGLIB の使用を強制することができます。

Spring AOP はプロキシベースであるという事実を理解することが重要です。この実装の詳細が実際に何を意味するかを正確に調べるには、AOP プロキシについてを参照してください。

5.4. @AspectJ サポート

@AspectJ は、アスペクトをアノテーション付きの通常の Java クラスとして宣言するスタイルを指します。@AspectJ スタイルは、AspectJ 5 リリースの一部として AspectJ プロジェクト (英語) によって導入されました。Spring は、ポイントカットの解析とマッチングのために AspectJ が提供するライブラリを使用して、AspectJ 5 と同じアノテーションを解釈します。ただし、AOP ランタイムは依然として純粋な Spring AOP であり、AspectJ コンパイラーまたはウィーバーへの依存関係はありません。

AspectJ コンパイラーとウィーバーを使用すると、完全な AspectJ 言語の使用が可能になります。これについては、Spring アプリケーションでの AspectJ の使用で説明しています。

5.4.1. @AspectJ のサポートを有効にする

Spring 構成で @AspectJ アスペクトを使用するには、@AspectJ アスペクトに基づいて Spring AOP を構成するための Spring サポートと、それらのアスペクトによってアドバイスされているかどうかに基づいて自動プロキシ Bean を有効にする必要があります。自動プロキシとは、Spring が Bean が 1 つ以上のアスペクトからアドバイスを受けていると判断した場合、その Bean のプロキシを自動的に生成してメソッド呼び出しをインターセプトし、必要に応じてアドバイスが実行されるようにすることを意味します。

@AspectJ サポートは、XML または Java スタイルの構成で有効にできます。いずれの場合も、AspectJ の aspectjweaver.jar ライブラリがアプリケーションのクラスパス(バージョン 1.8 以降)にあることを確認する必要があります。このライブラリは、AspectJ ディストリビューションの lib ディレクトリまたは Maven Central リポジトリから入手できます。

Java 構成で @AspectJ サポートを有効にする

Java @Configuration で @AspectJ サポートを有効にするには、次の例に示すように、@EnableAspectJAutoProxy アノテーションを追加します。

Java
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
Kotlin
@Configuration
@EnableAspectJAutoProxy
class AppConfig
XML 設定で @AspectJ サポートを有効にする

XML ベースの構成で @AspectJ サポートを有効にするには、次の例に示すように、aop:aspectj-autoproxy 要素を使用します。

<aop:aspectj-autoproxy/>

これは、XML スキーマベースの構成で説明されているスキーマサポートを使用することを前提としています。aop 名前空間にタグをインポートする方法については、AOP スキーマを参照してください。

5.4.2. アスペクトを宣言する

@AspectJ サポートを有効にすると、@AspectJ アスペクト(@Aspect アノテーションを持つ)のクラスを使用してアプリケーションコンテキストで定義された Bean は、Spring によって自動的に検出され、Spring AOP の構成に使用されます。次の 2 つの例は、あまり有用ではないアスペクトに必要な最小限の定義を示しています。

2 つの例の最初は、@Aspect アノテーションを持つ Bean クラスを指すアプリケーションコンテキストの通常の Bean 定義を示しています。

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of the aspect here -->
</bean>

2 つの例の 2 番目は、NotVeryUsefulAspect クラス定義を示しています。これには、org.aspectj.lang.annotation.Aspect アノテーションが付けられています。

Java
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}
Kotlin
package org.xyz

import org.aspectj.lang.annotation.Aspect;

@Aspect
class NotVeryUsefulAspect

アスペクト(@Aspect アノテーションが付けられたクラス)は、他のクラスと同じようにメソッドとフィールドを持つことができます。また、ポイントカット、アドバイス、導入(型間)宣言を含めることもできます。

コンポーネントのスキャンによるアスペクトの自動検出
アスペクトクラスは、@Configuration クラスの @Bean メソッドを介して Spring XML 構成に通常の Bean として登録するか、他の Spring 管理の Bean と同じように、クラスパススキャンを介して Spring に自動検出させることができます。ただし、@Aspect アノテーションは、クラスパスでの自動検出には不十分であることに注意してください。そのためには、個別の @Component アノテーション(または、Spring のコンポーネントスキャナーのルールに従って適格となるカスタムステレオタイプアノテーション)を追加する必要があります。
アスペクトを他のアスペクトにアドバイスできますか?
Spring AOP では、アスペクト自体を他のアスペクトからのアドバイスの対象にすることはできません。クラスの @Aspect アノテーションは、それをアスペクトとしてマークするため、自動プロキシから除外します。

5.4.3. ポイントカットの宣言

ポイントカットは、関心のあるジョインポイントを決定するため、アドバイスの実行時期を制御できます。Spring AOP は Spring Bean のメソッド実行ジョインポイントのみをサポートするため、ポイントカットは Spring Bean でのメソッドの実行と一致すると考えることができます。ポイントカット宣言には 2 つの部分があります。名前とパラメーターを含むシグネチャーと、対象のメソッド実行を正確に決定するポイントカット式です。AOP の @AspectJ アノテーションスタイルでは、ポイントカットシグネチャーは通常のメソッド定義によって提供されます。ポイントカット式は、@Pointcut アノテーションを使用して示されます(ポイントカットシグネチャーとして機能するメソッドには、void 戻り型が必要です)。

例は、ポイントカット署名とポイントカット表現のこの区別を明確にできます。次の例では、transfer という名前のメソッドの実行に一致する anyOldTransfer という名前のポイントカットを定義しています。

Java
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
Kotlin
@Pointcut("execution(* transfer(..))") // the pointcut expression
private fun anyOldTransfer() {} // the pointcut signature

@Pointcut アノテーションの値を形成するポイントカット式は、通常の AspectJ ポイントカット式です。AspectJ のポイントカット言語の詳細については、AspectJ プログラミングガイド (英語) (および拡張機能については AspectJ 5 開発者向けノートブック (英語) )または AspectJ に関する書籍の 1 つ(Colyeret。al。による Eclipse AspectJ、または RamnivasLaddad による AspectJin Action など)を参照してください。

サポートされているポイントカット指定子

Spring AOP は、ポイントカット式で使用するために、次の AspectJ ポイントカット指定子(PCD)をサポートしています。

  • execution: マッチングメソッドの実行のジョインポイント。これは、Spring AOP で作業するときに使用する主要なポイントカット指定子です。

  • within: 特定の型内のジョインポイントへの一致を制限します(Spring AOP を使用する場合、一致する型内で宣言されたメソッドの実行)。

  • this: Bean 参照(Spring AOP プロキシ)が指定された型のインスタンスであるジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • target: ターゲットオブジェクト(プロキシ化されるアプリケーションオブジェクト)が指定された型のインスタンスであるジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • args: 引数が指定された型のインスタンスであるジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • @target: 実行中のオブジェクトのクラスに特定の型のアノテーションがあるジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • @args: 渡される実際の引数の実行時の型が指定された型のアノテーションを持っているジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • @within: 指定されたアノテーションを持つ型内のジョインポイントへのマッチングを制限します(Spring AOP を使用する場合、指定されたアノテーションを持つ型で宣言されたメソッドの実行)。

  • @annotation: ジョインポイントのサブジェクト(Spring AOP で実行されているメソッド)に特定のアノテーションが付いているジョインポイントにマッチングを制限します。

その他のポイントカット型

完全な AspectJ ポイントカット言語は、Spring でサポートされていない追加のポイントカット指定子をサポートします: callgetsetpreinitializationstaticinitializationinitializationhandleradviceexecutionwithincodecflowcflowbelowif@this@withincode。Spring AOP によって解釈されるポイントカット式でこれらのポイントカット指定子を使用すると、IllegalArgumentException がスローされます。

Spring AOP でサポートされるポイントカット指定子のセットは、今後のリリースで拡張され、より多くの AspectJ ポイントカット指定子をサポートする可能性があります。

Spring AOP はメソッド実行のジョインポイントのみにマッチングを制限するため、ポイントカット指定子に関する前述の説明では、AspectJ プログラミングガイドで見つけることができるよりも狭い定義を示しています。さらに、AspectJ 自体には型ベースのセマンティクスがあり、実行ジョインポイントでは、this と target の両方が同じオブジェクト(メソッドを実行するオブジェクト)を参照します。Spring AOP はプロキシベースのシステムであり、プロキシオブジェクト自体(this にバインドされている)とプロキシの背後のターゲットオブジェクト(target にバインドされている)を区別します。

Spring の AOP フレームワークのプロキシベースの性質により、ターゲットオブジェクト内の呼び出しは、定義上、インターセプトされません。JDK プロキシの場合、プロキシ上のパブリックインターフェースメソッド呼び出しのみをインターセプトできます。CGLIB を使用すると、プロキシでのパブリックおよび protected メソッド呼び出しがインターセプトされます(必要に応じて、パッケージ private メソッドも)。ただし、プロキシを介した一般的な相互作用は、常に公開署名を介して設計する必要があります。

ポイントカットの定義は通常、インターセプトされたメソッドと一致することに注意してください。プロキシを介した潜在的な非公開相互作用がある CGLIB プロキシシナリオであっても、ポイントカットが厳密に公開専用であることを意図している場合、それに応じて定義する必要があります。

インターセプトにターゲットクラス内のメソッド呼び出しまたはコンストラクターを含める必要がある場合は、Spring のプロキシベースの AOP フレームワークの代わりに、Spring 駆動のネイティブ AspectJ ウィービングの使用を検討してください。これは、異なる特性を備えた AOP 使用の異なるモードを構成するため、決定を下す前に、ウィービングに精通してください。

Spring AOP は、bean という名前の追加の PCD もサポートします。この PCD を使用すると、ジョインポイントの一致を特定の名前付き Spring Bean または名前付き Spring Bean のセット(ワイルドカードを使用する場合)に制限できます。bean PCD の形式は次のとおりです。

Java
bean(idOrNameOfBean)
Kotlin
bean(idOrNameOfBean)

idOrNameOfBean トークンは、任意の Spring Bean の名前にすることができます。* 文字を使用する制限されたワイルドカードサポートが提供されるため、Spring Bean の命名規則を確立する場合、bean PCD 式を記述して選択できます。他のポイントカット指定子の場合と同様に、bean PCD は、&& (および)、|| (または)、! (否定)演算子でも使用できます。

bean PCD は Spring AOP でのみサポートされ、ネイティブの AspectJ ウィービングではサポートされません。これは、AspectJ が定義する標準 PCD に対する Spring 固有の拡張であるため、@Aspect モデルで宣言されたアスペクトでは使用できません。

bean PCD は、型レベル(ウィービングベースの AOP が制限されている)だけでなく、インスタンスレベル(Spring Bean の名前の概念に基づいて構築)で動作します。インスタンスベースのポイントカット指定子は、Spring のプロキシベースの AOP フレームワークの特別な機能であり、Spring Bean ファクトリとの緊密な統合であり、特定の Bean を名前で識別するのが自然で簡単です。

ポイントカット式の組み合わせ

&&, ||! を使用して、ポイントカット式を組み合わせることができます。ポイントカット式を名前で参照することもできます。次の例は、3 つのポイントカット式を示しています。

Java
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} (1)

@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} (2)

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} (3)
1anyPublicOperation は、メソッド実行ジョインポイントが public メソッドの実行を表す場合に一致します。
2inTrading は、メソッドの実行が取引モジュール内にある場合に一致します。
3tradingOperation は、メソッドの実行が取引モジュールの public メソッドを表す場合に一致します。
Kotlin
@Pointcut("execution(public * *(..))")
private fun anyPublicOperation() {} (1)

@Pointcut("within(com.xyz.myapp.trading..*)")
private fun inTrading() {} (2)

@Pointcut("anyPublicOperation() && inTrading()")
private fun tradingOperation() {} (3)
1anyPublicOperation は、メソッド実行ジョインポイントが public メソッドの実行を表す場合に一致します。
2inTrading は、メソッドの実行が取引モジュール内にある場合に一致します。
3tradingOperation は、メソッドの実行が取引モジュールの public メソッドを表す場合に一致します。

前に示したように、より小さな名前のコンポーネントからより複雑なポイントカット式を構築することをお勧めします。名前でポイントカットを参照する場合、通常の Java 可視性ルールが適用されます(同じ型のプライベートポイントカット、階層内の保護されたポイントカット、どこでもパブリックポイントカットなどを表示できます)。可視性はポイントカットのマッチングには影響しません。

共通のポイントカット定義を共有する

エンタープライズアプリケーションを使用する場合、開発者はアプリケーションのモジュールと特定の操作セットをいくつかのアスペクトから参照することを望みます。この目的で一般的なポイントカット式をキャプチャーする CommonPointcuts アスペクトを定義することをお勧めします。このようなアスペクトは通常、次の例のようになります。

Java
package com.xyz.myapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CommonPointcuts {

    /**
     * A join point is in the web layer if the method is defined
     * in a type in the com.xyz.myapp.web package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.web..*)")
    public void inWebLayer() {}

    /**
     * A join point is in the service layer if the method is defined
     * in a type in the com.xyz.myapp.service package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.service..*)")
    public void inServiceLayer() {}

    /**
     * A join point is in the data access layer if the method is defined
     * in a type in the com.xyz.myapp.dao package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    public void inDataAccessLayer() {}

    /**
     * A business service is the execution of any method defined on a service
     * interface. This definition assumes that interfaces are placed in the
     * "service" package, and that implementation types are in sub-packages.
     *
     * If you group service interfaces by functional area (for example,
     * in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
     * the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
     * could be used instead.
     *
     * Alternatively, you can write the expression using the 'bean'
     * PCD, like so "bean(*Service)". (This assumes that you have
     * named your Spring service beans in a consistent fashion.)
     */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    public void businessService() {}

    /**
     * A data access operation is the execution of any method defined on a
     * dao interface. This definition assumes that interfaces are placed in the
     * "dao" package, and that implementation types are in sub-packages.
     */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}
Kotlin
package com.xyz.myapp

import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut

@Aspect
class CommonPointcuts {

    /**
    * A join point is in the web layer if the method is defined
    * in a type in the com.xyz.myapp.web package or any sub-package
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.web..*)")
    fun inWebLayer() {
    }

    /**
    * A join point is in the service layer if the method is defined
    * in a type in the com.xyz.myapp.service package or any sub-package
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.service..*)")
    fun inServiceLayer() {
    }

    /**
    * A join point is in the data access layer if the method is defined
    * in a type in the com.xyz.myapp.dao package or any sub-package
    * under that.
    */
    @Pointcut("within(com.xyz.myapp.dao..*)")
    fun inDataAccessLayer() {
    }

    /**
    * A business service is the execution of any method defined on a service
    * interface. This definition assumes that interfaces are placed in the
    * "service" package, and that implementation types are in sub-packages.
    *
    * If you group service interfaces by functional area (for example,
    * in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
    * the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
    * could be used instead.
    *
    * Alternatively, you can write the expression using the 'bean'
    * PCD, like so "bean(*Service)". (This assumes that you have
    * named your Spring service beans in a consistent fashion.)
    */
    @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
    fun businessService() {
    }

    /**
    * A data access operation is the execution of any method defined on a
    * dao interface. This definition assumes that interfaces are placed in the
    * "dao" package, and that implementation types are in sub-packages.
    */
    @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
    fun dataAccessOperation() {
    }

}

このようなアスペクトで定義されたポイントカットは、ポイントカット式が必要な場所であればどこでも参照できます。例: サービス層をトランザクション化するには、次のように記述できます。

<aop:config>
    <aop:advisor
        pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
        advice-ref="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<aop:config> および <aop:advisor> 要素については、スキーマベースの AOP サポートで説明しています。トランザクション要素については、トランザクション管理で説明しています。

サンプル

Spring AOP ユーザーは、execution ポイントカット指定子を最も頻繁に使用する可能性があります。実行式の形式は次のとおりです。

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                throws-pattern?)

返される型パターン(前のスニペットの ret-type-pattern)、名前パターン、パラメーターパターンを除くすべての部分はオプションです。戻り値の型パターンは、ジョインポイントが一致するためにメソッドの戻り値の型を決定します。* は、戻り値の型パターンとして最も頻繁に使用されます。すべての戻り値の型に一致します。メソッドが指定された型を返す場合のみ、完全修飾型名が一致します。名前パターンはメソッド名と一致します。* ワイルドカードを名前パターンのすべてまたは一部として使用できます。宣言型パターンを指定する場合は、末尾の . を含めて名前パターンコンポーネントに結合します。パラメーターパターンはやや複雑です。() はパラメーターを取らないメソッドと一致しますが、(..) は任意の数(ゼロ以上)のパラメーターと一致します。(*) パターンは、任意の型の 1 つのパラメーターを取るメソッドと一致します。(*,String) は、2 つのパラメーターを取るメソッドと一致します。最初のものは任意の型で、2 番目のものは String でなければなりません。詳細については、AspectJ プログラミングガイドの言語セマンティクス (英語) セクションを参照してください。

次の例は、いくつかの一般的なポイントカット式を示しています。

  • public メソッドの実行:

        execution(public * *(..))
  • set で始まる名前のメソッドの実行:

        execution(* set*(..))
  • AccountService インターフェースによって定義されたメソッドの実行:

        execution(* com.xyz.service.AccountService.*(..))
  • service パッケージで定義されたメソッドの実行:

        execution(* com.xyz.service.*.*(..))
  • サービスパッケージまたはそのサブパッケージのいずれかで定義されたメソッドの実行:

        execution(* com.xyz.service..*.*(..))
  • サービスパッケージ内の任意のジョインポイント(Spring AOP でのメソッド実行のみ):

        within(com.xyz.service.*)
  • サービスパッケージ内またはそのサブパッケージ内の任意のジョインポイント(Spring AOP のみでのメソッド実行):

        within(com.xyz.service..*)
  • プロキシが AccountService インターフェースを実装する任意のジョインポイント(Spring AOP でのメソッド実行のみ):

        this(com.xyz.service.AccountService)
    'this' は、より一般的にバインディング形式で使用されます。アドバイス本文でプロキシオブジェクトを使用可能にする方法については、アドバイスを宣言するセクションを参照してください。
  • ターゲットオブジェクトが AccountService インターフェースを実装する任意のジョインポイント(Spring AOP でのメソッド実行のみ):

        target(com.xyz.service.AccountService)
    「ターゲット」は、バインディングフォームでより一般的に使用されます。ターゲットオブジェクトをアドバイス本文で使用可能にする方法については、アドバイスを宣言するセクションを参照してください。
  • 単一のパラメーターを取り、実行時に渡される引数が Serializable であるジョインポイント(Spring AOP でのメソッド実行のみ):

        args(java.io.Serializable)
    'args' は、バインディング形式でより一般的に使用されます。アドバイス本文でメソッド引数を使用可能にする方法については、アドバイスを宣言するセクションを参照してください。

    この例で指定されたポイントカットは execution(* *(java.io.Serializable)) とは異なることに注意してください。実行時に渡される引数が Serializable の場合、args バージョンは一致し、メソッドシグネチャーが Serializable 型の単一のパラメーターを宣言する場合、実行バージョンは一致します。

  • ターゲットオブジェクトに @Transactional アノテーションがある任意のジョインポイント(Spring AOP でのみメソッドを実行):

        @target(org.springframework.transaction.annotation.Transactional)
    バインディング形式で "@target" を使用することもできます。アドバイスオブジェクトでアノテーションオブジェクトを使用可能にする方法については、アドバイスを宣言するセクションを参照してください。
  • ターゲットオブジェクトの宣言された型に @Transactional アノテーションがあるジョインポイント(Spring AOP でのみメソッドを実行):

        @within(org.springframework.transaction.annotation.Transactional)
    バインディング形式で "@within" を使用することもできます。アドバイスオブジェクトでアノテーションオブジェクトを使用可能にする方法については、アドバイスを宣言するセクションを参照してください。
  • 実行中のメソッドに @Transactional アノテーションがあるジョインポイント(Spring AOP でのメソッド実行のみ):

        @annotation(org.springframework.transaction.annotation.Transactional)
    バインディング形式で "@annotation" を使用することもできます。アドバイスオブジェクトでアノテーションオブジェクトを使用可能にする方法については、アドバイスを宣言するセクションを参照してください。
  • 単一のパラメーターを取り、渡された引数の実行時型に @Classified アノテーションがあるジョインポイント(Spring AOP でのメソッド実行のみ):

        @args(com.xyz.security.Classified)
    バインディング形式で "@args" を使用することもできます。アドバイス本体でアノテーションオブジェクトを使用可能にする方法については、アドバイスを宣言するセクションを参照してください。
  • tradeService という名前の Spring Bean 上の任意のジョインポイント(Spring AOP のみでのメソッド実行):

        bean(tradeService)
  • ワイルドカード表現 *Service に一致する名前を持つ Spring Bean のジョインポイント(Spring AOP のみでのメソッド実行):

        bean(*Service)
良いポイントカットを書く

コンパイル時に、AspectJ はマッチングパフォーマンスを最適化するためにポイントカットを処理します。コードを調べて、各ジョインポイントが(静的または動的に)特定のポイントカットと一致するかどうかを判断するのは、コストのかかるプロセスです。(動的一致とは、静的分析から完全に一致を判断することはできず、コードの実行時に実際の一致があるかどうかを判断するテストがコードに配置されることを意味します)。最初にポイントカット宣言に遭遇すると、AspectJ はそれを一致プロセスに最適な形式に書き換えます。これは何を意味するのでしょうか? 基本的に、ポイントカットは DNF(Disjunctive Normal Form)で書き直され、ポイントカットのコンポーネントは、評価が安価なコンポーネントが最初にチェックされるようにソートされます。つまり、さまざまなポイントカット指定子のパフォーマンスを理解する必要はなく、ポイントカット宣言で任意の順序で指定できます。

ただし、AspectJ は、指定された内容でのみ機能します。マッチングの最適なパフォーマンスを得るには、達成しようとしているものを検討し、定義内で可能な限り一致の検索スペースを狭める必要があります。既存の指定子は、当然、親切、スコーピング、コンテキストの 3 つのグループのいずれかに分類されます。

  • 種類指定子は、特定の種類のジョインポイント executiongetsetcallhandler を選択します。

  • スコープ指定子は、関心のあるジョインポイントのグループ(おそらく多くの種類)を選択します: within および withincode

  • コンテキスト指定子は、コンテキストに基づいて一致します(オプションでバインドします): thistarget@annotation

よく書かれたポイントカットには、少なくとも最初の 2 つの型(種類とスコープ)を含める必要があります。コンテキストの指示子を含めて、ジョインポイントのコンテキストに基づいて照合したり、アドバイスで使用するためにそのコンテキストをバインドしたりできます。親切なデジグネータのみ、またはコンテキストのデジグネータのみを指定すると機能しますが、余分な処理と分析のために、ウィービングのパフォーマンス(使用される時間とメモリ)に影響を与える可能性があります。スコーピング指定子は非常に高速に一致し、使用することで、AspectJ はさらに処理すべきではないジョインポイントのグループを非常に迅速に削除できます。適切なポイントカットには、可能であれば常にポイントカットを含める必要があります。

5.4.4. アドバイスを宣言する

アドバイスはポイントカット式に関連付けられており、ポイントカットに一致するメソッド実行の前、後、または前後に実行されます。ポイントカット式は、名前付きポイントカットへの単純な参照、または所定の場所で宣言されたポイントカット式のいずれかです。

Before アドバイス

@Before アノテーションを使用して、アスペクトのアドバイスの前に宣言できます。

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before

@Aspect
class BeforeExample {

    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}

インプレースポイントカット式を使用する場合、前の例を次の例のように書き換えることができます。

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before

@Aspect
class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    fun doAccessCheck() {
        // ...
    }
}
After Returning アドバイス

After returning アドバイスは、一致したメソッドの実行が正常に戻ったときに実行されます。@AfterReturning アノテーションを使用して宣言できます。

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning

@Aspect
class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doAccessCheck() {
        // ...
    }
}
複数のアドバイス宣言(およびその他のメンバー)を、すべて同じアスペクト内に含めることができます。これらの例では、それぞれの効果に焦点を当てるために、1 つのアドバイス宣言のみを示しています。

場合によっては、返された実際の値にアドバイス本文でアクセスする必要があります。次の例に示すように、戻り値をバインドする @AfterReturning の形式を使用して、そのアクセスを取得できます。

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning

@Aspect
class AfterReturningExample {

    @AfterReturning(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        returning = "retVal")
    fun doAccessCheck(retVal: Any) {
        // ...
    }
}

returning 属性で使用される名前は、advice メソッドのパラメーターの名前に対応している必要があります。メソッドの実行が戻ると、戻り値は対応する引数値としてアドバイスメソッドに渡されます。returning 句は、指定された型の値を返すメソッドの実行のみに一致を制限します(この場合、戻り値に一致する Object)。

after returning アドバイスを使用する場合、まったく異なる参照を返すことはできないことに注意してください。

After Throwing アドバイス

After throwing アドバイスは、一致したメソッドの実行が例外をスローして終了したときに実行されます。次の例に示すように、@AfterThrowing アノテーションを使用して宣言できます。

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing

@Aspect
class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doRecoveryActions() {
        // ...
    }
}

多くの場合、特定の型の例外がスローされたときにのみアドバイスを実行したい場合があります。また、アドバイス本体のスローされた例外へのアクセスも必要になることがよくあります。throwing 属性を使用して、一致を制限し(必要に応じて - そうでない場合は Throwable を例外型として使用)、スローされた例外をアドバイスパラメーターにバインドできます。次の例は、その方法を示しています。

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing(
        pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing

@Aspect
class AfterThrowingExample {

    @AfterThrowing(
        pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
        throwing = "ex")
    fun doRecoveryActions(ex: DataAccessException) {
        // ...
    }
}

throwing 属性で使用される名前は、advice メソッドのパラメーターの名前に対応している必要があります。メソッドの実行が例外をスローして終了すると、例外は対応する引数値としてアドバイスメソッドに渡されます。throwing 句は、指定された型(この場合は DataAccessException)の例外をスローするメソッドの実行のみに一致を制限します。

@AfterThrowing は、一般的な例外処理コールバックを示していないことに注意してください。具体的には、@AfterThrowing アドバイスメソッドは、ジョインポイント(ユーザーが宣言したターゲットメソッド)自体からのみ例外を受け取ることになっていますが、付随する @After/@AfterReturning メソッドからは受け取りません。

After (Finally) アドバイス

After (finally) アドバイスは、一致したメソッドの実行が終了すると実行されます。@After アノテーションを使用して宣言されます。After アドバイスは、通常の戻り条件と例外の戻り条件の両方を処理するように準備する必要があります。通常、リソースの解放などに使用されます。次の例は、after finally アドバイスの使用方法を示しています。

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After

@Aspect
class AfterFinallyExample {

    @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    fun doReleaseLock() {
        // ...
    }
}

AspectJ の @After アドバイスは、try-catch ステートメントの finally ブロックに類似した「after finally アドバイス」として定義されていることに注意してください。これは、成功した通常のリターンにのみ適用される @AfterReturning とは対照的に、ジョインポイント(ユーザーが宣言したターゲットメソッド)からスローされた結果、通常のリターン、例外に対して呼び出されます。

Around アドバイス

最後のアドバイスはアドバイスに関するものです。Around アドバイスは、一致したメソッドの実行の「周囲」で実行されます。これには、メソッドの実行前と実行後の両方で作業を行い、メソッドが実際に実行される時期、方法、実行を決定する機会があります。Around アドバイスは、メソッド実行の前後でスレッドセーフな方法で状態を共有する必要がある場合(タイマーの開始や停止など)によく使用されます。常に要件を満たす最も強力でない形式のアドバイスを使用します(つまり、アドバイスの前に実行する場合は、アラウンドアドバイスを使用しないでください)。

Around アドバイスは、@Around アノテーションを使用して宣言されます。アドバイスメソッドの最初のパラメーターは、型 ProceedingJoinPoint でなければなりません。アドバイスの本文内で、ProceedingJoinPoint で proceed() を呼び出すと、基になるメソッドが実行されます。proceed メソッドは Object[] を渡すこともできます。配列の値は、メソッドの実行が進むときにメソッドの引数として使用されます。

Object[] で呼び出されたときの proceed の動作は、AspectJ コンパイラーによってコンパイルされたアラウンドアドバイスの proceed の動作とは少し異なります。従来の AspectJ 言語を使用して記述されたアラウンドアドバイスの場合、proceed に渡される引数の数は、アラウンドアドバイスに渡される引数の数(基礎となるジョインポイントが取る引数の数ではなく)と一致する必要があります。指定された引数の位置は、値がバインドされたエンティティのジョインポイントで元の値に取って代わります(これが今意味をなさない場合でも心配不要です)。Spring が採用したアプローチはよりシンプルで、プロキシベースの実行専用のセマンティクスによりよく適合しています。Spring 用に記述された @AspectJ アスペクトをコンパイルし、AspectJ コンパイラーとウィーバーで proceed を引数とともに使用する場合にのみ、この違いに注意する必要があります。Spring AOP と AspectJ の両方で 100% と互換性のあるこのようなアスペクトを記述する方法があります。これについては、アドバイスパラメーターに関する次のセクションで説明します

次の例は、アラウンドアドバイスの使用方法を示しています。

Java
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }
}
Kotlin
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint

@Aspect
class AroundExample {

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
        // start stopwatch
        val retVal = pjp.proceed()
        // stop stopwatch
        return retVal
    }
}

around アドバイスによって返される値は、メソッドの呼び出し側から見た戻り値です。例: 単純なキャッシュアスペクトは、キャッシュに値があればキャッシュから値を返し、値がなければ proceed() を呼び出します。proceed は、around アドバイスの本文内で 1 回、何回も呼び出されるか、まったく呼び出されないことに注意してください。これらはすべて正当です。

アドバイスパラメーター

Spring は完全に型付けされたアドバイスを提供します。つまり、Object[] 配列を常に使用するのではなく、アドバイス署名で必要なパラメーターを宣言することを意味します(返り値とスローの例で前述)このセクションの後半で、アドバイス本体で引数やその他のコンテキスト値を使用できるようにする方法を確認します。最初に、アドバイスが現在アドバイスしている方法を知ることができる一般的なアドバイスを書く方法を見てみましょう。

現在の JoinPoint へのアクセス

どのアドバイスメソッドでも、最初のパラメーターとして org.aspectj.lang.JoinPoint 型のパラメーターを宣言することができます (JoinPoint のサブクラスである ProceedingJoinPoint 型の最初のパラメーターを宣言するには、around アドバイスが必要であることに注意してください)。JoinPoint インターフェースには、次のような便利なメソッドが用意されています。

  • getArgs(): メソッドの引数を返します。

  • getThis(): プロキシオブジェクトを返します。

  • getTarget(): ターゲットオブジェクトを返します。

  • getSignature(): アドバイスされているメソッドの説明を返します。

  • toString(): 推奨されている方法の有用な説明を出力します。

詳細については、javadoc (英語) を参照してください。

アドバイスにパラメーターを渡す

返された値または例外値をバインドする方法はすでに見てきました(返り値と after throwing アドバイスを使用して)。アドバイス本文で引数値を使用できるようにするには、args のバインディング形式を使用できます。args 式で型名の代わりにパラメーター名を使用する場合、アドバイスが呼び出されるときに、対応する引数の値がパラメーター値として渡されます。例により、これを明確にする必要があります。Account オブジェクトを最初のパラメーターとして使用する DAO 操作の実行をアドバイスし、アドバイス本体のアカウントにアクセスする必要があるとします。次のように書くことができます。

Java
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}
Kotlin
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
fun validateAccount(account: Account) {
    // ...
}

ポイントカット式の args(account,..) 部分には 2 つの目的があります。まず、メソッドが少なくとも 1 つのパラメーターを取り、そのパラメーターに渡される引数が Account のインスタンスであるメソッド実行のみに一致を制限します。次に、account パラメーターを介して、実際の Account オブジェクトをアドバイスで利用できるようにします。

これを記述する別の方法は、Account オブジェクト値がジョインポイントと一致したときに「提供」するポイントカットを宣言し、アドバイスから名前付きポイントカットを参照することです。これは次のようになります。

Java
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}
Kotlin
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
}

@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
    // ...
}

詳細については、AspectJ プログラミングガイドを参照してください。

プロキシオブジェクト(this)、ターゲットオブジェクト(target)、アノテーション(@within@target@annotation@args)は、すべて同様の方法でバインドできます。次の 2 つの例は、@Auditable アノテーションが付けられたメソッドの実行を照合し、監査コードを抽出する方法を示しています。

2 つの例の最初は、@Auditable アノテーションの定義を示しています。

Java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)

2 つの例の 2 番目は、@Auditable メソッドの実行に一致するアドバイスを示しています。

Java
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}
Kotlin
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
fun audit(auditable: Auditable) {
    val code = auditable.value()
    // ...
}
アドバイスパラメーターとジェネリクス

Spring AOP は、クラス宣言およびメソッドパラメーターで使用されるジェネリクスを処理できます。次のようなジェネリクス型があるとします。

Java
public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection<T> param);
}
Kotlin
interface Sample<T> {
    fun sampleGenericMethod(param: T)
    fun sampleGenericCollectionMethod(param: Collection<T>)
}

メソッド型のインターセプトを特定のパラメーター型に制限するには、アドバイスパラメーターをメソッドをインターセプトするパラメーター型に入力します。

Java
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}
Kotlin
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
    // Advice implementation
}

このアプローチは、ジェネリクスコレクションでは機能しません。次のようにポイントカットを定義することはできません。

Java
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
    // Advice implementation
}
Kotlin
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
    // Advice implementation
}

これを機能させるには、コレクションのすべての要素をインスペクションする必要がありますが、これは合理的ではありません。null 値の一般的な扱い方も決定できないためです。これに似た何かを実現するには、Collection<?> にパラメーターを入力し、要素の型を手動で確認する必要があります。

引数名の決定

アドバイス呼び出しのパラメーターバインドは、アドバイスおよびポイントカットメソッドシグネチャーで宣言されたパラメーター名にポイントカット式で使用される一致する名前に依存します。Java リフレクションではパラメーター名を使用できないため、Spring AOP は次の戦略を使用してパラメーター名を決定します。

  • パラメーター名がユーザーによって明示的に指定されている場合、指定されたパラメーター名が使用されます。アドバイスとポイントカットの両方のアノテーションには、アノテーション付きメソッドの引数名を指定するために使用できるオプションの argNames 属性があります。これらの引数名は実行時に利用可能です。次の例は、argNames 属性の使用方法を示しています。

Java
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
}
Kotlin
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(bean: Any, auditable: Auditable) {
    val code = auditable.value()
    // ... use code and bean
}

最初のパラメーターが JoinPointProceedingJoinPointJoinPoint.StaticPart 型の場合、argNames 属性の値からパラメーターの名前を省略できます。例: ジョインポイントオブジェクトを受け取るように前述のアドバイスを変更する場合、argNames 属性にそれを含める必要はありません。

Java
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code, bean, and jp
}
Kotlin
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
    val code = auditable.value()
    // ... use code, bean, and jp
}

JoinPointProceedingJoinPointJoinPoint.StaticPart 型の最初のパラメーターに与えられた特別な処理は、他のジョインポイントコンテキストを収集しないアドバイスインスタンスに特に便利です。このような状況では、argNames 属性を省略できます。例: 次のアドバイスでは、argNames 属性を宣言する必要はありません。

Java
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
    // ... use jp
}
Kotlin
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
fun audit(jp: JoinPoint) {
    // ... use jp
}
  • 'argNames' 属性の使用は少し不器用なので、'argNames' 属性が指定されていない場合、Spring AOP はクラスのデバッグ情報を調べて、ローカル変数テーブルからパラメーター名を判別しようとします。この情報は、クラスがデバッグ情報(少なくとも '-g:vars')でコンパイルされている限り存在します。このフラグをオンにしてコンパイルすると、(1) コードが少しわかりやすくなります(リバースエンジニア)、(2) クラスファイルのサイズが非常にわずかに大きくなり(通常は重要ではありません)、(3) 未使用のローカルを削除する最適化変数はコンパイラーによって適用されません。言い換えると、このフラグをオンにしてビルドすることにより、問題が発生することはありません。

    デバッグ情報がなくても @AspectJ アスペクトが AspectJ コンパイラー(ajc)によってコンパイルされている場合、コンパイラーは必要な情報を保持するため、argNames 属性を追加する必要はありません。
  • 必要なデバッグ情報なしでコードがコンパイルされた場合、Spring AOP はパラメーターへのバインド変数のペアを推測しようとします(たとえば、ポイントカット式でバインドされる変数が 1 つだけで、アドバイスメソッドがパラメーターを 1 つしか受け取らない場合、明らかです)。使用可能な情報を考慮して変数のバインドがあいまいな場合、AmbiguousBindingException がスローされます。

  • 上記のすべての戦略が失敗すると、IllegalArgumentException がスローされます。

引数付きで続行

Spring AOP と AspectJ で一貫して動作する引数を使用して proceed 呼び出しを記述する方法を説明することを以前に述べました。解決策は、アドバイス署名が各メソッドパラメーターを順番にバインドするようにすることです。次の例は、その方法を示しています。

Java
@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
        String accountHolderNamePattern) throws Throwable {
    String newPattern = preProcess(accountHolderNamePattern);
    return pjp.proceed(new Object[] {newPattern});
}
Kotlin
@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
                        accountHolderNamePattern: String): Any {
    val newPattern = preProcess(accountHolderNamePattern)
    return pjp.proceed(arrayOf<Any>(newPattern))
}

多くの場合、このバインディングを行います(前の例のように)。

アドバイスのオーダー

複数のアドバイスがすべて同じジョインポイントで実行されるとどうなるでしょうか? Spring AOP は、AspectJ と同じ優先順位ルールに従って、アドバイスの実行順序を決定します。最も優先順位の高いアドバイスが最初に「途中」で実行されます(つまり、2 つの before アドバイスが与えられた場合、最も優先順位の高いアドバイスが最初に実行されます)。ジョインポイントから「途中」では、最も優先順位の高いアドバイスが最後に実行されます(したがって、2 つの after アドバイスが与えられた場合、最も優先順位の高いものが 2 番目に実行されます)。

異なるアスペクトで定義された 2 つのアドバイスが両方とも同じジョインポイントで実行する必要がある場合、特に指定しない限り、実行順序は定義されていません。優先順位を指定することにより、実行の順序を制御できます。これは、アスペクトクラスで org.springframework.core.Ordered インターフェースを実装するか、@Order アノテーションを付けて通常の Spring の方法で行われます。2 つのアスペクトを考えると、Ordered.getOrder() から低い値(またはアノテーション値)を返すアスペクトの優先順位が高くなります。

特定のアスペクトの個別のアドバイス型はそれぞれ、概念的にはジョインポイントに直接適用することを目的としています。結果として、@AfterThrowing アドバイスメソッドは、付随する @After/@AfterReturning メソッドから例外を受け取ることは想定されていません。

Spring Framework 5.2.7 の時点で、同じ @Aspect クラスで定義され、同じジョインポイントで実行する必要があるアドバイスメソッドには、アドバイス型に基づいて、優先順位の高いものから低いものの順に優先順位が割り当てられます: @Around@Before@After@AfterReturning@AfterThrowing。ただし、@After アドバイスメソッドは、同じアスペクトの @AfterReturning または @AfterThrowing アドバイスメソッドの後に、AspectJ の @After の「after finally アドバイス」セマンティクスに従って効果的に呼び出されることに注意してください。

同じ @Aspect クラスで定義された同じ型のアドバイス(たとえば、2 つの @After アドバイスメソッド)の 2 つの部分が同じジョインポイントで実行される必要がある場合、順序は定義されていません(ソースコード宣言を取得する方法がないため) javac でコンパイルされたクラスのリフレクションを介してオーダーします)。このようなアドバイスメソッドを、各 @Aspect クラスのジョインポイントごとに 1 つのアドバイスメソッドにまとめるか、Ordered または @Order を介してアスペクトレベルでオーダーできる個別の @Aspect クラスにアドバイスをリファクタリングすることを検討してください。

5.4.5. 導入

イントロダクション(AspectJ では型間宣言として知られています)は、アスペクトがアドバイスされたオブジェクトが特定のインターフェースを実装することを宣言し、それらのオブジェクトに代わってそのインターフェースの実装を提供できるようにします。

@DeclareParents アノテーションを使って導入することができます。このアノテーションは、一致する型に新しい親があることを宣言するために使用されます(そのため名前が付けられています)。例: UsageTracked という名前のインターフェースと DefaultUsageTracked という名前のそのインターフェースの実装が与えられた場合、次のアスペクトは、サービスインターフェースのすべての実装者が UsageTracked インターフェースも実装することを宣言します(たとえば、JMX を介した統計用):

Java
@Aspect
public class UsageTracking {

    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;

    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }

}
Kotlin
@Aspect
class UsageTracking {

    companion object {
        @DeclareParents(value = "com.xzy.myapp.service.*+", defaultImpl = DefaultUsageTracked::class)
        lateinit var mixin: UsageTracked
    }

    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    fun recordUsage(usageTracked: UsageTracked) {
        usageTracked.incrementUseCount()
    }
}

実装されるインターフェースは、アノテーション付きフィールドの型によって決まります。@DeclareParents アノテーションの value 属性は、AspectJ 型のパターンです。一致する型の Bean は、UsageTracked インターフェースを実装します。前の例の前のアドバイスでは、サービス Bean を UsageTracked インターフェースの実装として直接使用できることに注意してください。Bean にプログラムでアクセスする場合、次のように記述します。

Java
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
Kotlin
val usageTracked = context.getBean("myService") as UsageTracked

5.4.6. アスペクトのインスタンス化モデル

これは高度なトピックです。AOP を始めたばかりの場合は、後まで安全にスキップできます。

デフォルトでは、アプリケーションコンテキスト内に各アスペクトの単一のインスタンスがあります。AspectJ はこれをシングルトンインスタンス化モデルと呼んでいます。代替ライフサイクルでアスペクトを定義することが可能です。Spring は、AspectJ の perthis および pertarget インスタンス化モデルをサポートしています。percflowpercflowbelowpertypewithin は現在サポートされていません。

@Aspect アノテーションで perthis 節を指定することにより、perthis アスペクトを宣言できます。次の例を考えてみましょう。

Java
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {

    private int someState;

    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    public void recordServiceUsage() {
        // ...
    }
}
Kotlin
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
class MyAspect {

    private val someState: Int = 0

    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    fun recordServiceUsage() {
        // ...
    }
}

上記の例では、perthis 句の効果として、ビジネスサービスを実行する一意のサービスオブジェクトごとに 1 つのアスペクトインスタンスが作成されます(各一意のオブジェクトは、ポイントカット式と一致するジョインポイントで this にバインドされます)。アスペクトインスタンスは、サービスオブジェクトでメソッドが初めて呼び出されたときに作成されます。サービスオブジェクトがスコープから外れると、アスペクトはスコープから外れます。アスペクトインスタンスが作成される前は、そのインスタンス内のアドバイスは実行されません。アスペクトインスタンスが作成されるとすぐに、その中で宣言されたアドバイスは、一致したジョインポイントで実行されますが、サービスオブジェクトがこのアスペクトに関連付けられている場合のみです。per 句の詳細については、AspectJ プログラミングガイドを参照してください。

pertarget インスタンス化モデルは perthis とまったく同じように機能しますが、一致するジョインポイントで一意のターゲットオブジェクトごとに 1 つのアスペクトインスタンスを作成します。

5.4.7. AOP の例

すべての構成要素がどのように機能するかを見てきたため、まとめて何か役に立つことをすることができます。

ビジネスサービスの実行は、同時実行性の課題のために失敗することがあります(たとえば、デッドロックの敗者)。操作が再試行された場合、次の試行で成功する可能性があります。このような条件で再試行することが適切なビジネスサービス(競合解決のためにユーザーに戻る必要のないべき等操作)の場合、クライアントが PessimisticLockingFailureException を認識しないように透過的に操作を再試行します。これは、サービスレイヤーの複数のサービスに明確に適用される要件であるため、アスペクトを介した実装に最適です。

操作を再試行するため、proceed を複数回呼び出せるように、around advice を使用する必要があります。次のリストは、基本的なアスペクトの実装を示しています。

Java
@Aspect
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}
Kotlin
@Aspect
class ConcurrentOperationExecutor : Ordered {

    private val DEFAULT_MAX_RETRIES = 2
    private var maxRetries = DEFAULT_MAX_RETRIES
    private var order = 1

    fun setMaxRetries(maxRetries: Int) {
        this.maxRetries = maxRetries
    }

    override fun getOrder(): Int {
        return this.order
    }

    fun setOrder(order: Int) {
        this.order = order
    }

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
        var numAttempts = 0
        var lockFailureException: PessimisticLockingFailureException
        do {
            numAttempts++
            try {
                return pjp.proceed()
            } catch (ex: PessimisticLockingFailureException) {
                lockFailureException = ex
            }

        } while (numAttempts <= this.maxRetries)
        throw lockFailureException
    }
}

アスペクトは Ordered インターフェースを実装するため、アスペクトの優先順位をトランザクションアドバイスより高く設定できることに注意してください(再試行するたびに新しいトランザクションが必要です)。maxRetries および order プロパティは両方とも Spring によって構成されます。主なアクションは、アドバイスを中心に doConcurrentOperation で発生します。現時点では、各 businessService() に再試行ロジックを適用していることに注意してください。続行しようとしますが、PessimisticLockingFailureException で失敗した場合は、すべての再試行を使い果たしていない限り、再試行します。

対応する Spring 構成は次のとおりです。

<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>

べき等操作のみを再試行するようにアスペクトを調整するには、次の Idempotent アノテーションを定義します。

Java
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation

その後、アノテーションを使用して、サービス操作の実装にアノテーションを付けることができます。べき等操作のみを再試行するアスペクトの変更には、次のように、@Idempotent 操作のみが一致するようにポイントカット式を改善することが含まれます。

Java
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}
Kotlin
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
    // ...
}

5.5. スキーマベースの AOP サポート

XML ベースのフォーマットを希望する場合、Spring は aop 名前空間タグを使用してアスペクトを定義するためのサポートも提供します。@AspectJ スタイルを使用する場合とまったく同じポイントカット式とアドバイスの種類がサポートされています。このセクションでは、その構文に焦点を当て、前のセクション(@AspectJ サポート)の説明を参照して、ポイントカット式の記述とアドバイスパラメーターのバインディングについて理解します。

このセクションで説明されている aop 名前空間タグを使用するには、XML スキーマベースの構成に従って、spring-aop スキーマをインポートする必要があります。aop 名前空間にタグをインポートする方法については、AOP スキーマを参照してください。

Spring 構成内では、すべてのアスペクトおよびアドバイザー要素を <aop:config> 要素内に配置する必要があります(アプリケーションコンテキスト構成内に複数の <aop:config> 要素を含めることができます)。<aop:config> 要素には、ポイントカット、アドバイザー、アスペクト要素を含めることができます(これらはこの順序で宣言する必要があることに注意してください)。

<aop:config> スタイルの構成では、Spring の自動プロキシメカニズムを多用していますBeanNameAutoProxyCreator または同様のものを使用して明示的な自動プロキシをすでに使用している場合、これにより課題(アドバイスが織り込まれていないなど)が発生する可能性があります。推奨される使用パターンは、<aop:config> スタイルのみまたは AutoProxyCreator スタイルのみのいずれかを使用し、混合しないことです。

5.5.1. アスペクトを宣言する

スキーマサポートを使用する場合、アスペクトは Spring アプリケーションコンテキストで Bean として定義された通常の Java オブジェクトです。状態と動作はオブジェクトのフィールドとメソッドにキャプチャーされ、ポイントカットとアドバイス情報は XML にキャプチャーされます。

次の例に示すように、<aop:aspect> 要素を使用してアスペクトを宣言し、ref 属性を使用してバッキング Bean を参照できます。

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>

もちろん、他の Spring Bean と同様に、アスペクト(この場合は aBean)をサポートする Bean を構成し、依存関係を注入できます。

5.5.2. ポイントカットの宣言

<aop:config> エレメント内で名前付きポイントカットを宣言して、ポイントカット定義を複数のアスペクトとアドバイザで共有できます。

サービス層でのビジネスサービスの実行を表すポイントカットは、次のように定義できます。

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

</aop:config>

ポイントカット式自体は、@AspectJ サポートで説明されているのと同じ AspectJ ポイントカット式言語を使用していることに注意してください。スキーマベースの宣言スタイルを使用する場合、ポイントカット式内の型(@Aspects)で定義された名前付きポイントカットを参照できます。上記のポイントカットを定義する別の方法は次のとおりです。

<aop:config>

    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.CommonPointcuts.businessService()"/>

</aop:config>

共通のポイントカット定義を共有するで説明されている CommonPointcuts アスペクトがあると仮定します。

次に、アスペクト内でポイントカットを宣言することは、次の例が示すように、トップレベルのポイントカットを宣言することに非常に似ています。

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        ...
    </aop:aspect>

</aop:config>

@AspectJ アスペクトとほぼ同じ方法で、スキーマベースの定義スタイルを使用して宣言されたポイントカットは、ジョインポイントコンテキストを収集できます。例: 次のポイントカットは、this オブジェクトをジョインポイントコンテキストとして収集し、アドバイスに渡します。

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) &amp;&amp; this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>

</aop:config>

次のように、一致する名前のパラメーターを含めることにより、収集されたジョインポイントコンテキストを受け取るようにアドバイスを宣言する必要があります。

Java
public void monitor(Object service) {
    // ...
}
Kotlin
fun monitor(service: Any) {
    // ...
}

ポイントカットの部分式を組み合わせる場合、&amp;&amp; は XML ドキュメント内で扱いにくいため、&amp;&amp;||! の代わりに andornot キーワードをそれぞれ使用できます。例: 前のポイントカットは、次のように書く方が適切です。

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>
</aop:config>

この方法で定義されたポイントカットは、XML id によって参照され、複合ポイントカットを形成するための名前付きポイントカットとして使用できないことに注意してください。スキーマベースの定義スタイルでの名前付きポイントカットのサポートは、@AspectJ スタイルで提供されるものよりも制限されています。

5.5.3. アドバイスを宣言する

スキーマベースの AOP サポートは、@AspectJ スタイルと同じ 5 種類のアドバイスを使用します。これらのセマンティクスはまったく同じです。

Before アドバイス

Before アドバイスは、一致したメソッドが実行される前に実行されます。次の例に示すように、<aop:before> 要素を使用して <aop:aspect> 内で宣言されます。

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>

ここで、dataAccessOperation は、トップ(<aop:config>)レベルで定義されたポイントカットの id です。代わりにポイントカットをインラインで定義するには、次のように pointcut-ref 属性を pointcut 属性に置き換えます。

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
        method="doAccessCheck"/>

    ...
</aop:aspect>

@AspectJ スタイルの説明で記述されていたように、名前付きポイントカットを使用すると、コードの可読性が大幅に向上します。

method 属性は、アドバイスの本文を提供するメソッド(doAccessCheck)を識別します。このメソッドは、アドバイスを含むアスペクト要素によって参照される Bean に対して定義する必要があります。データアクセス操作が実行される前に(ポイントカット式と一致するメソッド実行ジョインポイント)、アスペクト Bean の doAccessCheck メソッドが呼び出されます。

After Returning アドバイス

After returning アドバイスは、一致したメソッドの実行が正常に完了すると実行されます。これは、アドバイスの前と同じ方法で <aop:aspect> 内で宣言されます。次の例は、宣言方法を示しています。

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...
</aop:aspect>

@AspectJ スタイルと同様に、アドバイス本文内で戻り値を取得できます。これを行うには、次の例に示すように、returning 属性を使用して、戻り値が渡されるパラメーターの名前を指定します。

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>

    ...
</aop:aspect>

doAccessCheck メソッドは、retVal という名前のパラメーターを宣言する必要があります。このパラメーターの型は、@AfterReturning について説明したのと同じ方法でマッチングを制限します。例: 次のようにメソッドシグネチャーを宣言できます。

Java
public void doAccessCheck(Object retVal) {...
Kotlin
fun doAccessCheck(retVal: Any) {...
After Throwing アドバイス

After throwing アドバイスは、一致したメソッドの実行が例外をスローして終了したときに実行されます。次の例に示すように、after-throwing 要素を使用して <aop:aspect> 内で宣言されます。

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        method="doRecoveryActions"/>

    ...
</aop:aspect>

@AspectJ スタイルと同様に、スローされた例外をアドバイス本文内で取得できます。これを行うには、次の例に示すように、throwing 属性を使用して、例外を渡す必要があるパラメーターの名前を指定します。

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>

    ...
</aop:aspect>

doRecoveryActions メソッドは、dataAccessEx という名前のパラメーターを宣言する必要があります。このパラメーターの型は、@AfterThrowing について説明したのと同じ方法でマッチングを制限します。例: メソッドシグネチャーは次のように宣言できます。

Java
public void doRecoveryActions(DataAccessException dataAccessEx) {...
Kotlin
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
After (Finally) アドバイス

After (finally) アドバイスは、一致したメソッドの実行がどのように終了しても実行されます。次の例に示すように、after 要素を使用して宣言できます。

<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>

    ...
</aop:aspect>
Around アドバイス

最後のアドバイスは Around アドバイスに関するものです。Around アドバイスは、一致したメソッド実行の「周り」で実行されます。メソッドの実行前と実行後の両方で作業を行い、いつ、どのように、メソッドが実際に実行されるかどうかを判断する機会があります。Around アドバイスは、メソッド実行の前後の状態をスレッドセーフな方法で共有するためによく使用されます(たとえば、タイマーの開始と停止)。要件を満たす最も強力でない形式のアドバイスを常に使用してください。Before アドバイスが機能する場合は、Around アドバイスを使用しないでください。

aop:around 要素を使用してアドバイスを宣言できます。アドバイスメソッドの最初のパラメーターは、型 ProceedingJoinPoint でなければなりません。アドバイスの本文内で、ProceedingJoinPoint で proceed() を呼び出すと、基になるメソッドが実行されます。proceed メソッドは、Object[] で呼び出すこともできます。配列の値は、メソッドの実行が進むときにメソッドの引数として使用されます。Object[] で proceed を呼び出す際の注意については、Around アドバイスを参照してください。次の例は、XML でアドバイスを宣言する方法を示しています。

<aop:aspect id="aroundExample" ref="aBean">

    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>

    ...
</aop:aspect>

doBasicProfiling アドバイスの実装は、次の例に示すように、@AspectJ の例とまったく同じにすることができます(もちろん、アノテーションを除く)。

Java
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
}
Kotlin
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
    // start stopwatch
    val retVal = pjp.proceed()
    // stop stopwatch
    return pjp.proceed()
}
アドバイスパラメーター

スキーマベースの宣言スタイルは、@AspectJ サポートで説明したのと同じ方法で、アドバイスメソッドのパラメーターと名前でポイントカットパラメーターを照合することによって、完全に型付けされたアドバイスをサポートします。詳細は、アドバイスパラメーターを参照してください。アドバイスメソッド (前述の検出戦略に頼らない) の引数名を明示的に指定する場合は、アドバイス要素の arg-names 属性を使用します。この属性は、アドバイスアノテーション ( 引数名の決定の説明) の argNames 属性と同じ方法で処理されます。次の例では、XML で引数名を指定する方法を示します。

<aop:before
    pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
    method="audit"
    arg-names="auditable"/>

arg-names 属性は、パラメーター名のコンマ区切りリストを受け入れます。

次の XSD ベースのアプローチのやや複雑な例は、いくつかの厳密に型指定されたパラメーターと組み合わせて使用されるアドバイスを示しています。

Java
package x.y.service;

public interface PersonService {

    Person getPerson(String personName, int age);
}

public class DefaultPersonService implements PersonService {

    public Person getPerson(String name, int age) {
        return new Person(name, age);
    }
}
Kotlin
package x.y.service

interface PersonService {

    fun getPerson(personName: String, age: Int): Person
}

class DefaultPersonService : PersonService {

    fun getPerson(name: String, age: Int): Person {
        return Person(name, age)
    }
}

次はアスペクトです。profile(..) メソッドは、多くの厳密に型指定されたパラメーターを受け入れることに注意してください。最初のパラメーターは、メソッド呼び出しを進めるために使用されるジョインポイントです。このパラメーターの存在は、次の例が示すように、profile(..) が around アドバイスとして使用されることを示しています。

Java
package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class SimpleProfiler {

    public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
        StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
        try {
            clock.start(call.toShortString());
            return call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
    }
}
Kotlin
import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.util.StopWatch

class SimpleProfiler {

    fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any {
        val clock = StopWatch("Profiling for '$name' and '$age'")
        try {
            clock.start(call.toShortString())
            return call.proceed()
        } finally {
            clock.stop()
            println(clock.prettyPrint())
        }
    }
}

最後に、次の XML 構成の例は、特定のジョインポイントに対する前述のアドバイスの実行に影響します。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the object that will be proxied by Spring's AOP infrastructure -->
    <bean id="personService" class="x.y.service.DefaultPersonService"/>

    <!-- this is the actual advice itself -->
    <bean id="profiler" class="x.y.SimpleProfiler"/>

    <aop:config>
        <aop:aspect ref="profiler">

            <aop:pointcut id="theExecutionOfSomePersonServiceMethod"
                expression="execution(* x.y.service.PersonService.getPerson(String,int))
                and args(name, age)"/>

            <aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
                method="profile"/>

        </aop:aspect>
    </aop:config>

</beans>

次のドライバースクリプトを検討してください。

Java
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
        PersonService person = (PersonService) ctx.getBean("personService");
        person.getPerson("Pengo", 12);
    }
}
Kotlin
fun main() {
    val ctx = ClassPathXmlApplicationContext("x/y/plain.xml")
    val person = ctx.getBean("personService") as PersonService
    person.getPerson("Pengo", 12)
}

このような Boot クラスを使用すると、標準出力で次のような出力が得られます。

StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)
アドバイスのオーダー

複数のアドバイスを同じジョインポイント(実行メソッド)で実行する必要がある場合、順序付けルールはアドバイスのオーダーで説明されています。アスペクト間の優先順位は、<aop:aspect> 要素の order 属性を介して、またはアスペクトをサポートする Bean に @Order アノテーションを追加するか、Bean に Ordered インターフェースを実装させることによって決定されます。

同じ @Aspect クラスで定義されたアドバイスメソッドの優先順位規則とは対照的に、同じ <aop:aspect> 要素で定義された 2 つのアドバイスが両方とも同じジョインポイントで実行される必要がある場合、優先順位はアドバイス要素の順序によって決定されます。囲まれた <aop:aspect> 要素内で、最高から最低の優先順位で宣言されます。

例: 同じジョインポイントに適用される同じ <aop:aspect> 要素で定義された around アドバイスと before アドバイスが与えられた場合、around アドバイスが before アドバイスよりも優先されるようにするには、<aop:before> 要素の前に <aop:around> 要素を宣言する必要があります。

一般的な経験則として、同じジョインポイントに適用される同じ <aop:aspect> 要素で定義された複数のアドバイスがある場合、そのようなアドバイスメソッドを各 <aop:aspect> 要素のジョインポイントごとに 1 つのアドバイスメソッドにまとめるか、アドバイスの断片をアスペクトレベルで順序付けできる別の <aop:aspect> 要素にリファクタリングすることを検討してください。

5.5.4. 導入

はじめに(AspectJ で型間宣言として知られる)アスペクトは、アドバイスされたオブジェクトが特定のインターフェースを実装し、それらのオブジェクトに代わってそのインターフェースの実装を提供することを宣言させます。

aop:aspect 内で aop:declare-parents 要素を使用して、導入を行うことができます。aop:declare-parents エレメントを使用して、一致する型に新しい親があることを宣言できます(そのため名前があります)。例: UsageTracked という名前のインターフェースと DefaultUsageTracked という名前のインターフェースの実装を指定すると、次のアスペクトは、サービスインターフェースのすべての実装者も UsageTracked インターフェースを実装することを宣言します。(たとえば、JMX を通じて統計を公開するため。)

<aop:aspect id="usageTrackerAspect" ref="usageTracking">

    <aop:declare-parents
        types-matching="com.xzy.myapp.service.*+"
        implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
        default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>

    <aop:before
        pointcut="com.xyz.myapp.CommonPointcuts.businessService()
            and this(usageTracked)"
            method="recordUsage"/>

</aop:aspect>

usageTracking Bean を支援するクラスには、次のメソッドが含まれます。

Java
public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
}
Kotlin
fun recordUsage(usageTracked: UsageTracked) {
    usageTracked.incrementUseCount()
}

実装されるインターフェースは、implement-interface 属性によって決定されます。types-matching 属性の値は、AspectJ 型のパターンです。一致する型の Bean は、UsageTracked インターフェースを実装します。前の例の前のアドバイスでは、サービス Bean を UsageTracked インターフェースの実装として直接使用できることに注意してください。Bean にプログラムでアクセスするには、次のように記述できます。

Java
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
Kotlin
val usageTracked = context.getBean("myService") as UsageTracked

5.5.5. アスペクトのインスタンス化モデル

スキーマ定義のアスペクトでサポートされるインスタンス化モデルは、シングルトンモデルのみです。他のインスタンス化モデルは、将来のリリースでサポートされる可能性があります。

5.5.6. アドバイザー

「アドバイザー」の概念は、Spring で定義された AOP サポートに由来し、AspectJ に直接相当するものはありません。アドバイザーは、単一のアドバイスを持つ小さな自己完結型のアスペクトのようなものです。アドバイス自体は Bean で表され、Spring のアドバイス型で説明されているアドバイスインターフェースの 1 つを実装する必要があります。アドバイザーは、AspectJ ポイントカット式を利用できます。

Spring は、<aop:advisor> エレメントでアドバイザーの概念をサポートします。最も一般的には、Spring で独自のネームスペースをサポートしているトランザクションアドバイスと組み合わせて使用されます。次の例はアドバイザーを示しています。

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>

</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

前の例で使用した pointcut-ref 属性と同様に、pointcut 属性を使用して、ポイントカット式をインラインで定義することもできます。

アドバイスが順序付けに参加できるようにアドバイザーの優先順位を定義するには、order 属性を使用してアドバイザーの Ordered 値を定義します。

5.5.7. AOP スキーマの例

このセクションでは、AOP の例からの同時ロック失敗の再試行の例が、スキーマサポートで書き換えられたときの様子を示します。

ビジネスサービスの実行は、同時実行性の課題のために失敗することがあります(たとえば、デッドロックの敗者)。操作が再試行された場合、次の試行で成功する可能性があります。このような条件で再試行することが適切なビジネスサービス(競合解決のためにユーザーに戻る必要のないべき等操作)の場合、クライアントが PessimisticLockingFailureException を認識しないように透過的に操作を再試行します。これは、サービスレイヤーの複数のサービスに明確に適用される要件であるため、アスペクトを介した実装に最適です。

操作を再試行するため、proceed を複数回呼び出せるように、around advice を使用する必要があります。次のリストは、基本的なアスペクトの実装を示しています(これは、スキーマサポートを使用する通常の Java クラスです)。

Java
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}
Kotlin
class ConcurrentOperationExecutor : Ordered {

    private val DEFAULT_MAX_RETRIES = 2

    private var maxRetries = DEFAULT_MAX_RETRIES
    private var order = 1

    fun setMaxRetries(maxRetries: Int) {
        this.maxRetries = maxRetries
    }

    override fun getOrder(): Int {
        return this.order
    }

    fun setOrder(order: Int) {
        this.order = order
    }

    fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
        var numAttempts = 0
        var lockFailureException: PessimisticLockingFailureException
        do {
            numAttempts++
            try {
                return pjp.proceed()
            } catch (ex: PessimisticLockingFailureException) {
                lockFailureException = ex
            }

        } while (numAttempts <= this.maxRetries)
        throw lockFailureException
    }
}

アスペクトは Ordered インターフェースを実装するため、アスペクトの優先順位をトランザクションアドバイスより高く設定できることに注意してください(再試行するたびに新しいトランザクションが必要です)。maxRetries および order プロパティは両方とも Spring によって構成されます。主なアクションは、doConcurrentOperation アラウンドアドバイスメソッドで発生します。続行しようとします。PessimisticLockingFailureException で失敗した場合は、すべての再試行を使い果たしていない限り、再試行します。

このクラスは、@AspectJ の例で使用したものと同じですが、アノテーションが削除されています。

対応する Spring 構成は次のとおりです。

<aop:config>

    <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">

        <aop:pointcut id="idempotentOperation"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        <aop:around
            pointcut-ref="idempotentOperation"
            method="doConcurrentOperation"/>

    </aop:aspect>

</aop:config>

<bean id="concurrentOperationExecutor"
    class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
        <property name="maxRetries" value="3"/>
        <property name="order" value="100"/>
</bean>

当面は、すべてのビジネスサービスがべき等であると想定していることに注意してください。そうでない場合は、次の例に示すように、Idempotent アノテーションを導入し、アノテーションを使用してサービス操作の実装にアノテーションを付けることにより、アスペクトを洗練して genuine 等な操作のみを再試行できます。

Java
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}
Kotlin
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
    // marker annotation
}

べき等操作のみを再試行するアスペクトの変更には、次のように、@Idempotent 操作のみが一致するようにポイントカット式を改善することが含まれます。

<aop:pointcut id="idempotentOperation"
        expression="execution(* com.xyz.myapp.service.*.*(..)) and
        @annotation(com.xyz.myapp.service.Idempotent)"/>

5.6. 使用する AOP 宣言スタイルの選択

アスペクトが特定の要件を実装するための最良のアプローチであると判断したら、Spring AOP または AspectJ を使用するか、アスペクト言語(コード)スタイル、@AspectJ アノテーションスタイル、Spring XML スタイルの間でどのように決定しますか? これらの決定は、アプリケーション要件、開発ツール、AOP に対するチームの知識など、多くの要因の影響を受けます。

5.6.1. Spring AOP または Full AspectJ ?

動作できる最も単純なものを使用してください。Spring AOP は、AspectJ コンパイラー / ウィーバーを開発およびビルドプロセスに導入する必要がないため、フル AspectJ を使用するよりも簡単です。Spring Bean での操作の実行のみをアドバイスする必要がある場合は、Spring AOP が正しい選択です。Spring コンテナーで管理されていないオブジェクト(通常はドメインオブジェクトなど)を通知する必要がある場合は、AspectJ を使用する必要があります。また、単純なメソッドの実行以外のジョインポイントをアドバイスする場合は、AspectJ を使用する必要があります(たとえば、フィールドの取得またはジョインポイントの設定など)。

AspectJ を使用する場合、AspectJ 言語構文 (「コードスタイル」とも呼ばれる) または @AspectJ アノテーションスタイルを選択できます。明らかに、Java 5+ を使用しない場合は、コードスタイルを選択してください。アスペクトが設計で大きなロールを果たし、Eclipse 用の AspectJ 開発ツール (AJDT) (英語) プラグインを使用できる場合は、AspectJ 言語構文を使用することをお勧めします。この言語はアスペクトを書くために意図的に設計されているため、より簡潔で単純です。Eclipse を使用していない場合、またはアプリケーションで重要なロールを果たしていないアスペクトがいくつかしかない場合は、@AspectJ スタイルの使用、IDE での通常の Java コンパイルの使用、ビルドスクリプトへのアスペクト編成フェーズの追加を検討してください。

5.6.2. @AspectJ または Spring AOP の XML

Spring AOP を使用することを選択した場合、@AspectJ または XML スタイルを選択できます。考慮すべきさまざまなトレードオフがあります。

XML スタイルは、既存の Spring ユーザーに最も馴染みのあるものであり、正規の POJO によってサポートされています。エンタープライズサービスを構成するためのツールとして AOP を使用する場合、XML が適切な選択になります(適切なテストは、ポイントカット式を構成の一部と見なして、個別に変更する必要があるかどうかです)。XML スタイルを使用すると、システムにどのアスペクトが存在するかが構成から明らかに明確になります。

XML スタイルには 2 つの欠点があります。第一に、単一の場所で対処する要件の実装を完全にカプセル化しません。DRY の原則では、システム内のあらゆる知識の単一の明確な、権威ある表現が存在する必要があると述べています。XML スタイルを使用する場合、要件の実装方法に関する知識は、バッキング Bean クラスの宣言と構成ファイル内の XML に分割されます。@AspectJ スタイルを使用すると、この情報は単一のモジュール(アスペクト)にカプセル化されます。第二に、XML スタイルは @AspectJ スタイルよりも表現できる範囲がわずかに制限されています。「シングルトン」アスペクトのインスタンス化モデルのみがサポートされ、XML で宣言された名前付きポイントカットを結合することはできません。例: @AspectJ スタイルでは、次のように記述できます。

Java
@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}
Kotlin
@Pointcut("execution(* get*())")
fun propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
fun operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
fun accountPropertyAccess() {}

XML スタイルでは、最初の 2 つのポイントカットを宣言できます。

<aop:pointcut id="propertyAccess"
        expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
        expression="execution(org.xyz.Account+ *(..))"/>

XML アプローチの欠点は、これらの定義を組み合わせて accountPropertyAccess ポイントカットを定義できないことです。

@AspectJ スタイルは、追加のインスタンス化モデルとより豊富なポイントカット構成をサポートします。アスペクトをモジュラーユニットとして維持するという利点があります。また、@AspectJ のアスペクトを Spring AOP と AspectJ の両方で理解(および消費)できるという利点もあります。そのため、後から追加の要件を実装するために AspectJ の機能が必要になった場合、簡単に従来の AspectJ セットアップに移行できます。結局、Spring チームは、エンタープライズサービスの単純な構成よりもカスタムアスペクトに @AspectJ スタイルを優先します。

5.7. アスペクト型の混合

自動プロキシサポート、スキーマ定義の <aop:aspect> アスペクト、<aop:advisor> 宣言アドバイザー、さらには同じ構成の他のスタイルのプロキシとインターセプターを使用することにより、@AspectJ スタイルアスペクトを混在させることは完全に可能です。これらはすべて、同じ基盤となるサポートメカニズムを使用して実装され、問題なく共存できます。

5.8. プロキシメカニズム

Spring AOP は、JDK 動的プロキシまたは CGLIB を使用して、指定されたターゲットオブジェクトのプロキシを作成します。JDK 動的プロキシは JDK に組み込まれていますが、CGLIB は一般的なオープンソースクラス定義ライブラリです(spring-core に再パッケージ化されています)。

プロキシされるターゲットオブジェクトが少なくとも 1 つのインターフェースを実装する場合、JDK 動的プロキシが使用されます。ターゲット型によって実装されるすべてのインターフェースがプロキシされます。ターゲットオブジェクトがインターフェースを実装しない場合、CGLIB プロキシが作成されます。

CGLIB プロキシの使用を強制する場合(たとえば、インターフェースによって実装されているメソッドだけでなく、ターゲットオブジェクトに対して定義されているすべてのメソッドをプロキシする)、そうすることができます。ただし、次の課題を考慮する必要があります。

  • CGLIB では、final メソッドはランタイム生成サブクラスでオーバーライドできないため、アドバイスできません。

  • Spring 4.0 以降、CGLIB プロキシインスタンスは Objenesis を介して作成されるため、プロキシオブジェクトのコンストラクターが 2 回呼び出されることはなくなりました。JVM がコンストラクターのバイパスを許可しない場合にのみ、Spring の AOP サポートからの二重呼び出しと対応するデバッグログエントリが表示されることがあります。

CGLIB プロキシの使用を強制するには、次のように、<aop:config> 要素の proxy-target-class 属性の値を true に設定します。

<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>

@AspectJ 自動プロキシサポートを使用するときに CGLIB プロキシを強制するには、次のように <aop:aspectj-autoproxy> 要素の proxy-target-class 属性を true に設定します。

<aop:aspectj-autoproxy proxy-target-class="true"/>

複数の <aop:config/> セクションは、実行時に単一の統合された自動プロキシクリエーターにまとめられ、<aop:config/> セクションのいずれか(通常は異なる XML Bean 定義ファイルから)が指定した最も強力なプロキシ設定が適用されます。これは、<tx:annotation-driven/> および <aop:aspectj-autoproxy/> 要素にも適用されます。

明確にするために、<tx:annotation-driven/><aop:aspectj-autoproxy/><aop:config/> 要素で proxy-target-class="true" を使用すると、3 つすべてに CGLIB プロキシが強制的に使用されます

5.8.1. AOP プロキシについて

Spring AOP はプロキシベースです。独自のアスペクトを記述したり、Spring Framework で提供される Spring AOP ベースのアスペクトを使用する前に、最後のステートメントが実際に意味するセマンティクスを把握することが非常に重要です。

次のコードスニペットが示すように、まず、プレーンバニラ、プロキシ化されていない、特別なものは何もない、ストレートオブジェクト参照があるシナリオを考えます。

Java
public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}
Kotlin
class SimplePojo : Pojo {

    fun foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar()
    }

    fun bar() {
        // some logic...
    }
}

オブジェクト参照でメソッドを呼び出すと、次のイメージとリストに示すように、メソッドはそのオブジェクト参照で直接呼び出されます。

aop proxy plain pojo call
Java
public class Main {

    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}
Kotlin
fun main() {
    val pojo = SimplePojo()
    // this is a direct method call on the 'pojo' reference
    pojo.foo()
}

クライアントコードの参照がプロキシの場合、状況はわずかに変わります。次の図とコードスニペットを検討してください。

aop proxy call
Java
public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}
Kotlin
fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface(Pojo::class.java)
    factory.addAdvice(RetryAdvice())

    val pojo = factory.proxy as Pojo
    // this is a method call on the proxy!
    pojo.foo()
}

ここで理解しておくべき重要なことは、Main クラスの main(..) メソッド内のクライアントコードがプロキシへの参照を持っていることです。つまり、そのオブジェクト参照に対するメソッド呼び出しは、プロキシに対する呼び出しです。その結果、プロキシは、その特定のメソッド呼び出しに関連するすべてのインターセプター(アドバイス)に委譲できます。ただし、呼び出しが最終的にターゲットオブジェクト(この場合は SimplePojo 参照)に到達すると、this.bar() や this.foo() など、それ自体で行うメソッド呼び出しは、プロキシではなく this 参照に対して呼び出されます。これには重要な意味があります。つまり、自己呼び出しでは、メソッド呼び出しに関連するアドバイスが実行される可能性はありません。

では、これについてはどうすればいいのでしょうか? 最良の方法は (ここでは「最良」という言葉をゆるく使っています)、自己呼び出しが起こらないようにコードをリファクタリングすることです。これはあなたの多少の作業を必要としますが、これが最良であり、最も侵襲性の低いアプローチです。次のアプローチは非常に恐ろしいものですが、それを指摘することを躊躇しています。次の例が示すように、クラス内のロジックを Spring AOP に完全に結びつけることができます(私たちにとっては苦痛です)。

Java
public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}
Kotlin
class SimplePojo : Pojo {

    fun foo() {
        // this works, but... gah!
        (AopContext.currentProxy() as Pojo).bar()
    }

    fun bar() {
        // some logic...
    }
}

これにより、コードが Spring AOP に完全に結合され、AOP に直面して飛行する AOP コンテキストで使用されているという事実がクラス自体に認識されます。また、次の例に示すように、プロキシを作成するときに追加の構成が必要です。

Java
public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}
Kotlin
fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface(Pojo::class.java)
    factory.addAdvice(RetryAdvice())
    factory.isExposeProxy = true

    val pojo = factory.proxy as Pojo
    // this is a method call on the proxy!
    pojo.foo()
}

最後に、AspectJ はプロキシベースの AOP フレームワークではないため、この自己呼び出しの課題はありません。

5.9. @AspectJ プロキシのプログラムによる作成

<aop:config> または <aop:aspectj-autoproxy> のいずれかを使用して構成のアスペクトを宣言することに加えて、ターゲットオブジェクトをアドバイスするプロキシをプログラムで作成することもできます。Spring の AOP API の詳細については、次の章を参照してください。ここでは、@AspectJ アスペクトを使用してプロキシを自動的に作成する機能に焦点を当てたいと思います。

org.springframework.aop.aspectj.annotation.AspectJProxyFactory クラスを使用して、1 つ以上の @AspectJ アスペクトによって推奨されるターゲットオブジェクトのプロキシを作成できます。次の例に示すように、このクラスの基本的な使用箇所は非常に簡単です。

Java
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
Kotlin
// create a factory that can generate a proxy for the given target object
val factory = AspectJProxyFactory(targetObject)

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager::class.java)

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker)

// now get the proxy object...
val proxy = factory.getProxy<Any>()

詳細については、javadoc を参照してください。

5.10. Spring アプリケーションでの AspectJ の使用

この章でこれまで取り上げてきたものはすべて、純粋な Spring AOP です。このセクションでは、Spring AOP 単独で提供される機能を超えるニーズがある場合に、Spring AOP の代わりに、または Spring AOP に加えて AspectJ コンパイラーまたはウィーバーを使用する方法について説明します。

Spring には、ディストリビューションで spring-aspects.jar としてスタンドアロンで使用できる小さな AspectJ アスペクトライブラリが付属しています。アスペクトを使用するには、これをクラスパスに追加する必要があります。AspectJ を使用して Spring でドメインオブジェクトを依存性注入するおよび AspectJ の他の Spring アスペクトは、このライブラリの内容とその使用方法について説明しています。Spring IoC を使用した AspectJ アスペクトの構成は、AspectJ コンパイラーを使用して織り込まれた AspectJ アスペクトを依存性注入する方法について説明します。最後に、Spring Framework における AspectJ を使用したロードタイムウィービングは、AspectJ を使用する Spring アプリケーションのロード時ウィービングの概要を提供します。

5.10.1. AspectJ を使用して Spring でドメインオブジェクトを依存性注入する

Spring コンテナーは、アプリケーションコンテキストで定義された Bean をインスタンス化して構成します。適用する構成を含む Bean 定義の名前を指定して、Bean ファクトリに既存のオブジェクトの構成を依頼することもできます。spring-aspects.jar には、この機能を活用して任意のオブジェクトの依存性注入を可能にするアノテーション駆動型の側面が含まれています。このサポートは、コンテナーの制御外で作成されたオブジェクトに使用することを目的としています。ドメインオブジェクトは、多くの場合、new 演算子を使用してプログラムで作成されるか、データベースクエリの結果として ORM ツールによって作成されるため、このカテゴリに分類されます。

@Configurable アノテーションは、クラスを Spring 駆動の構成に適格としてマークします。最も単純な場合、次の例に示すように、純粋にマーカーアノテーションとして使用できます。

Java
package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
    // ...
}
Kotlin
package com.xyz.myapp.domain

import org.springframework.beans.factory.annotation.Configurable

@Configurable
class Account {
    // ...
}

この方法でマーカーインターフェースとして使用する場合、Spring は、完全修飾型名(com.xyz.myapp.domain.Account)と同じ名前の Bean 定義(通常はプロトタイプスコープ)を使用して、アノテーション付き型(この場合は Account)の新しいインスタンスを構成します。Bean のデフォルト名はその型の完全修飾名であるため、プロトタイプ定義を宣言する便利な方法は、次の例に示すように、id 属性を省略することです。

<bean class="com.xyz.myapp.domain.Account" scope="prototype">
    <property name="fundsTransferService" ref="fundsTransferService"/>
</bean>

使用するプロトタイプ Bean 定義の名前を明示的に指定する場合は、次の例に示すように、アノテーションで直接指定できます。

Java
package com.xyz.myapp.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
    // ...
}
Kotlin
package com.xyz.myapp.domain

import org.springframework.beans.factory.annotation.Configurable

@Configurable("account")
class Account {
    // ...
}

Spring は、account という名前の Bean 定義を検索し、それを定義として使用して新しい Account インスタンスを構成します。

オートワイヤーを使用して、専用の Bean 定義をまったく指定する必要がないようにすることもできます。Spring にオートワイヤーを適用させるには、@Configurable アノテーションの autowire プロパティを使用します。型ごとまたは名前ごとに、オートワイヤー用に @Configurable(autowire=Autowire.BY_TYPE) または @Configurable(autowire=Autowire.BY_NAME) を指定できます。別の方法として、フィールドまたはメソッドレベルで @Autowired または @Inject を介して @Configurable Bean に明示的なアノテーション駆動の依存性注入を指定することをお勧めします(詳細についてはアノテーションベースのコンテナー構成を参照)。

最後に、dependencyCheck 属性(たとえば、@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))を使用して、新しく作成および構成されたオブジェクトのオブジェクト参照の Spring 依存関係チェックを有効にできます。この属性が true に設定されている場合、Spring は、構成後にすべてのプロパティ(プリミティブまたはコレクションではない)が設定されていることを検証します。

アノテーションを単独で使用しても何も起こらないことに注意してください。アノテーションの存在に作用するのは、spring-aspects.jar の AnnotationBeanConfigurerAspect です。本質的に、アスペクトは、「 @Configurable でアノテーションが付けられた型の新しいオブジェクトの初期化から戻った後、アノテーションのプロパティに従って Spring を使用して新しく作成されたオブジェクトを構成する」と言われています。このコンテキストでは、「初期化」とは、新しくインスタンス化されたオブジェクト(たとえば、new 演算子でインスタンス化されたオブジェクト)および逆直列化中の Serializable オブジェクト(たとえば、readResolve() (標準 Javadoc) )を指します。

上の段落の重要なフレーズの 1 つは「本質的に」です。ほとんどの場合、「新しいオブジェクトの初期化から戻った後」の正確なセマンティクスは問題ありません。このコンテキストでは、「初期化後」とは、オブジェクトが構築された後に依存関係が注入されることを意味します。これは、依存関係がクラスのコンストラクター本体で使用できないことを意味します。依存関係をコンストラクター本体の実行前に注入して、コンストラクターの本体で使用できるようにする場合は、次のように、@Configurable 宣言でこれを定義する必要があります。

Java
@Configurable(preConstruction = true)
Kotlin
@Configurable(preConstruction = true)

AspectJ プログラミングガイド (英語) この付録 (英語) では、AspectJ のさまざまなポイントカット型の言語セマンティクスに関する詳細情報を見つけることができます。

これが機能するためには、アノテーション付きの型を AspectJ ウィーバーで織り込む必要があります。ビルド時の Ant または Maven タスクを使用してこれを行うことができます(たとえば、AspectJ 開発環境ガイド (英語) を参照)か、ロード時のウィービング(Spring Framework における AspectJ を使用したロードタイムウィービングを参照)。AnnotationBeanConfigurerAspect 自体は、Spring によって構成する必要があります(新しいオブジェクトの構成に使用される Bean ファクトリへの参照を取得するため)。Java ベースの構成を使用する場合、次のように、@EnableSpringConfigured を @Configuration クラスに追加できます。

Java
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
Kotlin
@Configuration
@EnableSpringConfigured
class AppConfig {
}

XML ベースの構成を好む場合、Spring context 名前空間は便利な context:spring-configured 要素を定義し、次のように使用できます。

<context:spring-configured/>

アスペクトが構成される前に作成された @Configurable オブジェクトのインスタンスは、デバッグログにメッセージが発行され、オブジェクトの構成は行われません。例は、Spring によって初期化されたときにドメインオブジェクトを作成する Spring 構成の Bean です。この場合、depends-on Bean 属性を使用して、Bean が構成の側面に依存することを手動で指定できます。次の例は、depends-on 属性の使用メソッドを示しています。

<bean id="myService"
        class="com.xzy.myapp.service.MyService"
        depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

    <!-- ... -->

</bean>
実行時にセマンティクスに本当に依存するつもりでない限り、Bean 構成機能アスペクトを介して @Configurable 処理をアクティブにしないでください。特に、通常の Spring Bean としてコンテナーに登録されている Bean クラスで @Configurable を使用しないようにしてください。これを行うと、コンテナーとアスペクトを一度ずつ通る二重の初期化が行われます。
@Configurable オブジェクトの単体テスト

@Configurable サポートのゴールの 1 つは、ハードコードされたルックアップに関連する困難なしに、ドメインオブジェクトの独立した単体テストを有効にすることです。@Configurable 型が AspectJ で編まれていない場合、単体テスト中にアノテーションは影響しません。テスト対象のオブジェクトにモックまたはスタブプロパティ参照を設定し、通常どおり続行できます。@Configurable 型が AspectJ によって織り込まれている場合、通常どおりコンテナーの外部で単体テストを実行できますが、Spring によって構成されていないことを示す @Configurable オブジェクトを作成するたびに警告メッセージが表示されます。

複数のアプリケーションコンテキストの操作

@Configurable サポートの実装に使用される AnnotationBeanConfigurerAspect は、AspectJ シングルトンアスペクトです。シングルトンアスペクトのスコープは、static メンバーのスコープと同じです。型を定義するクラスローダーごとに 1 つのアスペクトインスタンスがあります。つまり、同じクラスローダー階層内で複数のアプリケーションコンテキストを定義する場合、@EnableSpringConfigured Bean を定義する場所と、クラスパス上の spring-aspects.jar を配置する場所を考慮する必要があります。

一般的なビジネスサービスを定義する共有親アプリケーションコンテキスト、それらのサービスをサポートするために必要なすべて、および各サーブレット(そのサーブレットに固有の定義を含む)ごとに 1 つの子アプリケーションコンテキストを持つ典型的な Spring Web アプリケーション構成を考えてください。これらのコンテキストはすべて同じクラスローダー階層内に共存するため、AnnotationBeanConfigurerAspect はそのうちの 1 つのみへの参照を保持できます。この場合、共有(親)アプリケーションコンテキストで @EnableSpringConfigured Bean を定義することをお勧めします。これは、ドメインオブジェクトに注入する可能性が高いサービスを定義します。結果として、@Configurable メカニズムを使用して、子(サーブレット固有)コンテキストで定義された Bean への参照を使用してドメインオブジェクトを構成することはできません(これはおそらくしたいことではありません)。

同じコンテナー内に複数の Web アプリケーションをデプロイする場合、各 Web アプリケーションが独自のクラスローダーを使用して(たとえば、spring-aspects.jar を 'WEB-INF/lib' に配置することにより) spring-aspects.jar に型をロードするようにします。spring-aspects.jar がコンテナー全体のクラスパスにのみ追加される(したがって、共有親クラスローダーによってロードされる)場合、すべての Web アプリケーションは同じアスペクトインスタンスを共有します(おそらく、これは望んでいないものです)。

5.10.2. AspectJ の他の Spring アスペクト

@Configurable アスペクトに加えて、spring-aspects.jar には AspectJ アスペクトが含まれており、これを使用して @Transactional アノテーションが付けられた型とメソッドの Spring のトランザクション管理を駆動できます。これは主に、Spring Framework のトランザクションサポートを Spring コンテナーの外部で使用したいユーザーを対象としています。

@Transactional アノテーションを解釈するアスペクトは AnnotationTransactionAspect です。このアスペクトを使用する場合、クラスが実装するインターフェース(存在する場合)ではなく、実装クラス(またはそのクラス内のメソッド、あるいはその両方)にアノテーションを付ける必要があります。AspectJ は、インターフェースのアノテーションが継承されないという Java のルールに従います。

クラスの @Transactional アノテーションは、クラス内のすべてのパブリック操作の実行に対するデフォルトのトランザクションセマンティクスを指定します。

クラス内のメソッドの @Transactional アノテーションは、クラスアノテーション(存在する場合)によって指定されたデフォルトのトランザクションセマンティクスをオーバーライドします。プライベートメソッドを含む、任意の可視性のメソッドにアノテーションを付けることができます。非 public メソッドに直接アノテーションを付けることは、そのようなメソッドの実行のためにトランザクション境界を取得する唯一の方法です。

Spring Framework 4.2 以降、spring-aspects は、標準の javax.transaction.Transactional アノテーションとまったく同じ機能を提供する同様の側面を提供します。詳細については、JtaAnnotationTransactionAspect を確認してください。

Spring 構成およびトランザクション管理サポートを使用したいが、アノテーションを使用したくない(または使用できない)AspectJ プログラマー向けに、spring-aspects.jar には、独自のポイントカット定義を提供するために拡張できる abstract アスペクトも含まれています。詳細については、AbstractBeanConfigurerAspect および AbstractTransactionAspect のアスペクトのソースを参照してください。例として、次の抜粋は、完全修飾クラス名に一致するプロトタイプ Bean 定義を使用して、ドメインモデルで定義されたオブジェクトのすべてのインスタンスを構成するアスペクトを作成する方法を示しています。

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

    public DomainObjectConfiguration() {
        setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
    }

    // the creation of a new bean (any object in the domain model)
    protected pointcut beanCreation(Object beanInstance) :
        initialization(new(..)) &&
        CommonPointcuts.inDomainModel() &&
        this(beanInstance);
}

5.10.3. Spring IoC を使用した AspectJ アスペクトの構成

Spring アプリケーションで AspectJ アスペクトを使用する場合、そのようなアスペクトを Spring で構成できることを望み、期待することは当然です。AspectJ ランタイム自体がアスペクトの作成を担当し、Spring を介して AspectJ で作成されたアスペクトを構成する手段は、アスペクトで使用される AspectJ インスタンス化モデル(per-xxx 句)に依存します。

AspectJ アスペクトの大部分はシングルトンアスペクトです。これらのアスペクトの構成は簡単です。通常どおりアスペクト型を参照し、factory-method="aspectOf" Bean 属性を含む Bean 定義を作成できます。これにより、Spring は、インスタンス自体を作成しようとするのではなく、AspectJ に要求してアスペクトインスタンスを取得します。次の例は、factory-method="aspectOf" 属性の使用方法を示しています。

<bean id="profiler" class="com.xyz.profiler.Profiler"
        factory-method="aspectOf"> (1)

    <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
1factory-method="aspectOf" 属性に注意してください

非シングルトンのアスペクトは構成が困難です。ただし、プロトタイプ Bean 定義を作成し、spring-aspects.jar の @Configurable サポートを使用して、AspectJ ランタイムによって Bean が作成されると、アスペクトインスタンスを構成することにより、これを行うことができます。

AspectJ で織り込む @AspectJ アスペクト(ドメインモデル型のロード時ウィービングなど)や Spring AOP で使用する他の @AspectJ アスペクトがあり、これらのアスペクトはすべて Spring で構成されている場合、Spring AOP @AspectJ 自動プロキシサポートに、構成で定義された @AspectJ アスペクトの正確なサブセットを自動プロキシに使用するよう指示する必要があります。これを行うには、<aop:aspectj-autoproxy/> 宣言内で 1 つ以上の <include/> エレメントを使用します。各 <include/> エレメントは名前パターンを指定し、Spring AOP 自動プロキシ構成には、少なくとも 1 つのパターンと一致する名前を持つ Bean のみが使用されます。次の例は、<include/> 要素の使用メソッドを示しています。

<aop:aspectj-autoproxy>
    <aop:include name="thisBean"/>
    <aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
<aop:aspectj-autoproxy/> 要素の名前に惑わされないでください。これを使用すると、Spring AOP プロキシが作成されます。ここでは @AspectJ スタイルのアスペクト宣言が使用されていますが、AspectJ ランタイムは関係していません。

5.10.4. Spring Framework における AspectJ を使用したロードタイムウィービング

ロードタイムウィービング(LTW)は、Java 仮想マシン(JVM)にロードされるときに、AspectJ アスペクトをアプリケーションのクラスファイルにウィービングするプロセスを指します。このセクションの焦点は、Spring Framework の特定のコンテキストで LTW を構成および使用することです。このセクションは、LTW の一般的な導入ではありません。LTW の詳細および AspectJ のみで Spring を使用しない LTW の構成の詳細については、AspectJ 開発環境ガイドの LTW セクション (英語) を参照してください。

Spring Framework が AspectJ LTW にもたらす価値は、ウィービングプロセスをよりきめ細かく制御できることです。「バニラ」AspectJ LTW は、JVM の起動時に VM 引数を指定することによりオンに切り替えられる Java(5+)エージェントを使用することにより影響を受けます。JVM 全体の設定であり、状況によっては問題ない場合もありますが、多くの場合少し粗すぎます。Spring 対応の LTW を使用すると、ClassLoader 単位で LTW を切り替えることができます。これは、よりきめ細かく、「単一 JVM 複数アプリケーション」環境(一般的なアプリケーションサーバーなど)でより意味があります。環境)。

さらに、特定の環境では、このサポートにより、-javaagent:path/to/aspectjweaver.jar または(このセクションで後述する) -javaagent:path/to/spring-instrument.jar を追加するために必要なアプリケーションサーバーの起動スクリプトを変更することなく、ロード時のウィービングが可能になります。開発者は、起動スクリプトなどのデプロイ構成を通常担当する管理者に依存する代わりに、アプリケーションコンテキストを構成してロード時のウィービングを有効にします。

紹介が終わったため、まず Spring を使用する AspectJ LTW の簡単な例を見てみましょう。次に、例で紹介した要素に関する詳細を説明します。完全な例については、Petclinic サンプルアプリケーション [GitHub] (英語) を参照してください。

最初の例

システムのパフォーマンスの問題の原因の診断を担当しているアプリケーション開発者であると仮定します。プロファイリングツールをブレークアウトするのではなく、パフォーマンスメトリクスをすばやく取得できる単純なプロファイリングアスペクトに切り替えます。その後、すぐにその特定の領域に詳細なプロファイリングツールを適用できます。

ここに示す例では、XML 構成を使用しています。Java 構成で @AspectJ を構成して使用することもできます。具体的には、@EnableLoadTimeWeaving アノテーションを <context:load-time-weaver/> の代替として使用できます(詳細については以下を参照)。

次の例はプロファイリングのアスペクトを示していますが、これは派手ではありません。@AspectJ スタイルのアスペクト宣言を使用する時間ベースのプロファイラーです。

Java
package foo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;

@Aspect
public class ProfilingAspect {

    @Around("methodsToBeProfiled()")
    public Object profile(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch sw = new StopWatch(getClass().getSimpleName());
        try {
            sw.start(pjp.getSignature().getName());
            return pjp.proceed();
        } finally {
            sw.stop();
            System.out.println(sw.prettyPrint());
        }
    }

    @Pointcut("execution(public * foo..*.*(..))")
    public void methodsToBeProfiled(){}
}
Kotlin
package foo

import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Pointcut
import org.springframework.util.StopWatch
import org.springframework.core.annotation.Order

@Aspect
class ProfilingAspect {

    @Around("methodsToBeProfiled()")
    fun profile(pjp: ProceedingJoinPoint): Any {
        val sw = StopWatch(javaClass.simpleName)
        try {
            sw.start(pjp.getSignature().getName())
            return pjp.proceed()
        } finally {
            sw.stop()
            println(sw.prettyPrint())
        }
    }

    @Pointcut("execution(public * foo..*.*(..))")
    fun methodsToBeProfiled() {
    }
}

また、ProfilingAspect をクラスに織り込むことを AspectJ ウィーバーに通知するために、META-INF/aop.xml ファイルを作成する必要があります。このファイル規則、つまり、META-INF/aop.xml と呼ばれる Java クラスパス上のファイルの存在は、標準の AspectJ です。次の例は、aop.xml ファイルを示しています。

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

    <weaver>
        <!-- only weave classes in our application-specific packages -->
        <include within="foo.*"/>
    </weaver>

    <aspects>
        <!-- weave in just this aspect -->
        <aspect name="foo.ProfilingAspect"/>
    </aspects>

</aspectj>

これで、構成の Spring 固有の部分に進むことができます。LoadTimeWeaver を構成する必要があります(後で説明します)。このロード時ウィーバーは、1 つ以上の META-INF/aop.xml ファイルのアスペクト構成をアプリケーションのクラスに織り込む重要なコンポーネントです。良い点は、次の例に示すように、多くの構成を必要としないことです(指定できるオプションがいくつかありますが、これらについては後で詳しく説明します)。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- a service object; we will be profiling its methods -->
    <bean id="entitlementCalculationService"
            class="foo.StubEntitlementCalculationService"/>

    <!-- this switches on the load-time weaving -->
    <context:load-time-weaver/>
</beans>

必要なすべてのアーティファクト(アスペクト、META-INF/aop.xml ファイル、Spring 構成)が配置されたため、LTP の実際の動作を示すために、main(..) メソッドを使用して次のドライバークラスを作成できます。

Java
package foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Main {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);

        EntitlementCalculationService entitlementCalculationService =
                (EntitlementCalculationService) ctx.getBean("entitlementCalculationService");

        // the profiling aspect is 'woven' around this method execution
        entitlementCalculationService.calculateEntitlement();
    }
}
Kotlin
package foo

import org.springframework.context.support.ClassPathXmlApplicationContext

fun main() {
    val ctx = ClassPathXmlApplicationContext("beans.xml")

    val entitlementCalculationService = ctx.getBean("entitlementCalculationService") as EntitlementCalculationService

    // the profiling aspect is 'woven' around this method execution
    entitlementCalculationService.calculateEntitlement()
}

最後にやることがあります。このセクションの導入では、Spring を使用して ClassLoader ごとに LTW を選択的にオンにできると述べていますが、これは事実です。ただし、この例では、Java エージェント(Spring で提供)を使用して LTW をオンにします。次のコマンドを使用して、前述の Main クラスを実行します。

java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main

-javaagent は、エージェントが JVM 上で実行されるプログラムをインスツルメント (標準 Javadoc) できるように指定および有効化する (標準 Javadoc) ためのフラグです。Spring Framework には、このようなエージェント InstrumentationSavingAgent が付属しています。これは、前の例で -javaagent 引数の値として提供された spring-instrument.jar にパッケージ化されています。

Main プログラムの実行からの出力は、次の例のようになります。(Thread.sleep(..) ステートメントを calculateEntitlement() 実装に導入して、プロファイラーが実際に 0 ミリ秒以外をキャプチャーするようにしました(01234 ミリ秒は AOP によってもたらされるオーバーヘッドではありません)。以下のリストは、プロファイラーを実行したときに得られる出力を示しています。

Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms     %     Task name
------ ----- ----------------------------
01234  100%  calculateEntitlement

この LTW は本格的な AspectJ を使用して行われるため、Spring Bean のアドバイスのみに限定されません。Main プログラムの次のわずかなバリエーションでも同じ結果が得られます。

Java
package foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Main {

    public static void main(String[] args) {
        new ClassPathXmlApplicationContext("beans.xml", Main.class);

        EntitlementCalculationService entitlementCalculationService =
                new StubEntitlementCalculationService();

        // the profiling aspect will be 'woven' around this method execution
        entitlementCalculationService.calculateEntitlement();
    }
}
Kotlin
package foo

import org.springframework.context.support.ClassPathXmlApplicationContext

fun main(args: Array<String>) {
    ClassPathXmlApplicationContext("beans.xml")

    val entitlementCalculationService = StubEntitlementCalculationService()

    // the profiling aspect will be 'woven' around this method execution
    entitlementCalculationService.calculateEntitlement()
}

上記のプログラムで、Spring コンテナーをブートストラップし、Spring のコンテキストの完全に外側で StubEntitlementCalculationService の新しいインスタンスを作成する方法に注目してください。プロファイリングのアドバイスはまだ織り込まれています。

確かに、この例は単純です。ただし、Spring の LTW サポートの基本は前の例ですべて紹介されており、このセクションの残りの部分では、構成と使用方法の各ビットの背後にある「理由」について詳しく説明します。

この例で使用される ProfilingAspect は基本的なものですが、非常に便利です。これは、開発者が開発中に使用できる開発時のアスペクトの良い例であり、UAT または本番にデプロイされるアプリケーションのビルドから簡単に除外できます。
アスペクト

LTW で使用するアスペクトは、AspectJ アスペクトでなければなりません。AspectJ 言語自体で記述することも、@AspectJ スタイルでアスペクトを記述することもできます。アスペクトは有効な AspectJ と Spring AOP アスペクトの両方です。さらに、コンパイルされたアスペクトクラスは、クラスパスで利用可能である必要があります。

"META-INF/aop.xml"

AspectJ LTW インフラストラクチャは、Java クラスパス上にある 1 つ以上の META-INF/aop.xml ファイルを使用して構成されます(直接または、より一般的には jar ファイル内)。

このファイルの構造と内容は、AspectJ リファレンスドキュメント (英語) の LTW 部分で詳しく説明されています。aop.xml ファイルは 100% AspectJ であるため、ここではこれ以上説明しません。

必要なライブラリ (JARS)

AspectJ LTW の Spring Framework のサポートを使用するには、少なくとも次のライブラリが必要です。

  • spring-aop.jar

  • aspectjweaver.jar

  • spring-instrument.jar

Spring の設定

Spring の LTW サポートの重要なコンポーネントは、LoadTimeWeaver インターフェース(org.springframework.instrument.classloading パッケージ内)と、Spring ディストリビューションに同梱される多数の実装です。LoadTimeWeaver は、実行時に 1 つ以上の java.lang.instrument.ClassFileTransformers を ClassLoader に追加するロールを果たします。これにより、あらゆる種類の興味深いアプリケーションへの扉が開かれます。

未知でランタイムクラスファイルの変換を考えている場合は、続行する前に java.lang.instrument パッケージの javadoc API ドキュメントを参照してください。このドキュメントは包括的なものではありませんが、少なくとも、主要なインターフェースとクラスを確認できます(このセクションを読む際に参照してください)。

特定の ApplicationContext 用に LoadTimeWeaver を構成するのは、1 行追加するのと同じくらい簡単です。(Spring コンテナーとして ApplicationContext を使用する必要があることはほぼ確実であることに注意してください。通常、LTP サポートは BeanFactoryPostProcessors を使用するため、BeanFactory では十分ではありません。)

Spring Framework の LTW サポートを有効にするには、LoadTimeWeaver を構成する必要があります。これは、通常、次のように @EnableLoadTimeWeaving アノテーションを使用して行われます。

Java
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
Kotlin
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}

または、XML ベースの構成が必要な場合は、<context:load-time-weaver/> 要素を使用します。要素は context 名前空間で定義されていることに注意してください。次の例は、<context:load-time-weaver/> の使用方法を示しています。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:load-time-weaver/>

</beans>

上記の構成は、LoadTimeWeaver や AspectJWeavingEnabler など、多数の LTW 固有のインフラストラクチャ Bean を自動的に定義および登録します。デフォルトの LoadTimeWeaver は DefaultContextLoadTimeWeaver クラスで、自動的に検出された LoadTimeWeaver を装飾しようとします。「自動検出」される LoadTimeWeaver の正確な型は、ランタイム環境によって異なります。次の表は、さまざまな LoadTimeWeaver 実装をまとめたものです。

表 13: DefaultContextLoadTimeWeaver LoadTimeWeavers
ランタイム環境 LoadTimeWeaver の実装

Apache Tomcat (英語) で実行

TomcatLoadTimeWeaver

GlassFish (英語) で実行 (EAR デプロイに限定)

GlassFishLoadTimeWeaver

Red Hat の JBoss AS (英語) または WildFly (英語) で実行

JBossLoadTimeWeaver

IBM の WebSphere (英語) で実行

WebSphereLoadTimeWeaver

Oracle の WebLogic [Oracle] (英語) で実行

WebLogicLoadTimeWeaver

Spring InstrumentationSavingAgent で開始した JVM (java -javaagent:path/to/spring-instrument.jar)

InstrumentationLoadTimeWeaver

フォールバック、基礎となる ClassLoader が一般的な規則に従うことを期待 (すなわち、addTransformer およびオプションで getThrowawayClassLoader メソッド)

ReflectiveLoadTimeWeaver

この表には、DefaultContextLoadTimeWeaver の使用時に自動検出される LoadTimeWeavers のみがリストされていることに注意してください。使用する LoadTimeWeaver 実装を正確に指定できます。

Java 構成で特定の LoadTimeWeaver を指定するには、LoadTimeWeavingConfigurer インターフェースを実装し、getLoadTimeWeaver() メソッドをオーバーライドします。次の例では、ReflectiveLoadTimeWeaver を指定しています。

Java
@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new ReflectiveLoadTimeWeaver();
    }
}
Kotlin
@Configuration
@EnableLoadTimeWeaving
class AppConfig : LoadTimeWeavingConfigurer {

    override fun getLoadTimeWeaver(): LoadTimeWeaver {
        return ReflectiveLoadTimeWeaver()
    }
}

XML ベースの構成を使用する場合、<context:load-time-weaver/> 要素の weaver-class 属性の値として完全修飾クラス名を指定できます。繰り返しますが、次の例では ReflectiveLoadTimeWeaver を指定しています。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:load-time-weaver
            weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

</beans>

構成によって定義および登録された LoadTimeWeaver は、既知の名前 loadTimeWeaver を使用して Spring コンテナーから後で取得できます。LoadTimeWeaver は、Spring の LTW インフラストラクチャが 1 つ以上の ClassFileTransformers を追加するためのメカニズムとしてのみ存在することに注意してください。LTW を実行する実際の ClassFileTransformer は、ClassPreProcessorAgentAdapter (org.aspectj.weaver.loadtime パッケージから)クラスです。詳細については、ClassPreProcessorAgentAdapter クラスのクラスレベルの javadoc を参照してください。ウィービングが実際にどのように行われるかの詳細は、このドキュメントの範囲外です。

議論の余地がある設定の最後の属性が 1 つあります。aspectjWeaving 属性(XML を使用する場合は aspectj-weaving)です。この属性は、LTW を有効にするかどうかを制御します。3 つの可能な値のいずれかを受け入れます。属性が存在しない場合、デフォルト値は autodetect です。次の表は、3 つの可能な値をまとめたものです。

表 14: AspectJ ウィービング属性値
アノテーション値 XML 値 説明

ENABLED

on

AspectJ ウィービングはオンであり、必要に応じてロード時にアスペクトが織り込まれます。

DISABLED

off

LTW はオフです。ロード時にアスペクトは織り込まれません。

AUTODETECT

autodetect

Spring LTW インフラストラクチャが少なくとも 1 つの META-INF/aop.xml ファイルを検出できる場合、AspectJ ウィービングはオンになっています。それ以外の場合はオフです。これがデフォルト値です。

環境固有の構成

この最後のセクションには、アプリケーションサーバーや Web コンテナーなどの環境で Spring の LTW サポートを使用するときに必要な追加の設定と構成が含まれています。

Tomcat、JBoss、WebSphere、WebLogic

Tomcat、JBoss/WildFly、IBM WebSphere Application Server および Oracle WebLogic Server はすべて、ローカルインスツルメンテーションが可能な一般的なアプリ ClassLoader を提供します。Spring のネイティブ LTW は、これらの ClassLoader 実装を活用して AspectJ ウィービングを提供する場合があります。前述のように、ロード時のウィービングを有効にできます。具体的には、-javaagent:path/to/spring-instrument.jar を追加するために JVM 起動スクリプトを変更する必要はありません。

JBoss では、アプリケーションが実際に起動する前にクラスをロードしないように、アプリケーションサーバーのスキャンを無効にする必要がある場合があることに注意してください。簡単な回避策は、次の内容の WEB-INF/jboss-scanning.xml という名前のファイルをアーティファクトに追加することです。

<scanning xmlns="urn:jboss:scanning:1.0"/>
汎用 Java アプリケーション

特定の LoadTimeWeaver 実装でサポートされていない環境でクラスインスツルメンテーションが必要な場合、JVM エージェントが一般的なソリューションです。そのような場合、Spring は InstrumentationLoadTimeWeaver を提供し、一般的な @EnableLoadTimeWeaving および <context:load-time-weaver/> セットアップによって自動検出される Spring 固有の(ただし非常に一般的な)JVM エージェント spring-instrument.jar を必要とします。

これを使用するには、次の JVM オプションを指定して、Spring エージェントで仮想マシンを起動する必要があります。

-javaagent:/path/to/spring-instrument.jar

これには、JVM 起動スクリプトの変更が必要であり、アプリケーションサーバー環境でこれを使用できない場合があることに注意してください(サーバーと操作ポリシーによって異なります)。ただし、スタンドアロン Spring Boot アプリケーションなど、JVM ごとに 1 つのアプリケーションデプロイの場合、通常、いずれの場合でも JVM セットアップ全体を制御します。

5.11. その他のリソース

AspectJ の詳細については、AspectJ の Web サイト (英語) を参照してください。

Eclipse AspectJ by Adrian Colyer et。al。(Addison-Wesley、2005)は、AspectJ 言語の包括的な導入とリファレンスを提供します。

AspectJ in Action、第 2 版 Ramnivas Laddad(Manning、2009)が強く推奨されています。この本の焦点は AspectJ にありますが、一般的な AOP テーマの多くが(ある程度深く)調査されています。

6. Spring AOP API

前の章では、Spring が @AspectJ およびスキーマベースのアスペクト定義を使用した AOP をサポートすることについて説明しました。この章では、下位レベルの Spring AOP API について説明します。一般的なアプリケーションでは、前章で説明したように、AspectJ ポイントカットで Spring AOP を使用することをお勧めします。

6.1. Spring のポイントカット API

このセクションでは、Spring が重要なポイントカットの概念をどのように処理するかについて説明します。

6.1.1. 概念

Spring のポイントカットモデルは、アドバイス型に関係なくポイントカットの再利用を可能にします。同じポイントカットで異なるアドバイスをターゲットにすることができます。

org.springframework.aop.Pointcut インターフェースは、特定のクラスおよびメソッドへのアドバイスを対象とするために使用される主要インターフェースです。完全なインターフェースは次のとおりです。

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();
}

Pointcut インターフェースを 2 つの部分に分割すると、クラスとメソッドのマッチング部分ときめの細かい構成操作(別のメソッドマッチャーとの「結合」の実行など)を再利用できます。

ClassFilter インターフェースは、ポイントカットを特定のターゲットクラスのセットに制限するために使用されます。matches() メソッドが常に true を返す場合、すべてのターゲットクラスが一致します。次のリストは、ClassFilter インターフェース定義を示しています。

public interface ClassFilter {

    boolean matches(Class clazz);
}

通常、MethodMatcher インターフェースの方が重要です。完全なインターフェースは次のとおりです。

public interface MethodMatcher {

    boolean matches(Method m, Class<?> targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class<?> targetClass, Object... args);
}

matches(Method, Class) メソッドは、このポイントカットがターゲットクラスの特定のメソッドと一致するかどうかをテストするために使用されます。この評価は、AOP プロキシが作成されるときに実行でき、すべてのメソッド呼び出しでのテストの必要性を回避できます。2 つの引数を持つ matches メソッドが指定されたメソッドに対して true を返し、MethodMatcher に対する isRuntime() メソッドが true を返す場合、3 つの引数の一致メソッドがすべてのメソッド呼び出しで呼び出されます。これにより、ポイントカットは、ターゲットアドバイスが開始する直前にメソッド呼び出しに渡された引数を確認できます。

ほとんどの MethodMatcher 実装は静的です。つまり、isRuntime() メソッドは false を返します。この場合、引数が 3 つの matches メソッドは呼び出されません。

可能であれば、ポイントカットを静的にし、AOP フレームワークが AOP プロキシの作成時にポイントカット評価の結果をキャッシュできるようにします。

6.1.2. ポイントカットの操作

Spring は、ポイントカットの操作(特に、ユニオンとインターセクション)をサポートします。

Union は、どちらかのポイントカットが一致するメソッドを意味します。交差とは、両方のポイントカットが一致する方法を意味します。通常、Union はより便利です。org.springframework.aop.support.Pointcuts クラスの静的メソッドを使用するか、同じパッケージの ComposablePointcut クラスを使用して、ポイントカットを作成できます。ただし、AspectJ ポイントカット式の使用は、通常、より簡単なアプローチです。

6.1.3. AspectJ 式ポイントカット

2.0 以降、Spring が使用する最も重要な型のポイントカットは org.springframework.aop.aspectj.AspectJExpressionPointcut です。これは、AspectJ が提供するライブラリを使用して AspectJ ポイントカット式文字列を解析するポイントカットです。

サポートされている AspectJ ポイントカットプリミティブの説明については、前の章を参照してください。

6.1.4. 便利なポイントカットの実装

Spring はいくつかの便利なポイントカット実装を提供します。それらのいくつかを直接使用できます。その他は、アプリケーション固有のポイントカットでサブクラス化されることを目的としています。

静的ポイントカット

静的ポイントカットはメソッドとターゲットクラスに基づいており、メソッドの引数を考慮することはできません。ほとんどの場合、静的ポイントカットで十分であり、最適です。Spring は、メソッドが最初に呼び出されたときに、静的ポイントカットを一度だけ評価できます。その後、各メソッド呼び出しでポイントカットを再度評価する必要はありません。

このセクションの残りの部分では、Spring に含まれる静的ポイントカット実装のいくつかについて説明します。

正規表現のポイントカット

静的ポイントカットを指定する明白な方法の 1 つは、正規表現です。Spring 以外のいくつかの AOP フレームワークがこれを可能にします。org.springframework.aop.support.JdkRegexpMethodPointcut は、JDK の正規表現サポートを使用する汎用正規表現ポイントカットです。

JdkRegexpMethodPointcut クラスを使用すると、パターン文字列のリストを提供できます。これらのいずれかが一致する場合、ポイントカットは true に評価されます。(結果として、結果として生じるポイントカットは、指定されたパターンの結合になります。)

次の例は、JdkRegexpMethodPointcut の使用方法を示しています。

<bean id="settersAndAbsquatulatePointcut"
        class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

Spring は、RegexpMethodPointcutAdvisor という名前の便利なクラスを提供します。これにより、Advice を参照することもできます(Advice は、アドバイスの前、インターセプト、スローなど)。バックグラウンドでは、Spring は JdkRegexpMethodPointcut を使用しています。RegexpMethodPointcutAdvisor を使用すると、次の例に示すように、1 つの Bean がポイントカットとアドバイスの両方をカプセル化するため、接続が簡単になります。

<bean id="settersAndAbsquatulateAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref bean="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

RegexpMethodPointcutAdvisor は、任意の Advice 型で使用できます。

属性駆動型のポイントカット

静的なポイントカットの重要な型は、メタデータ駆動型のポイントカットです。これは、メタデータ属性の値(通常、ソースレベルのメタデータ)を使用します。

動的ポイントカット

動的ポイントカットは、静的ポイントカットよりも評価にコストがかかります。メソッドの引数と静的情報を考慮します。これは、すべてのメソッド呼び出しで評価する必要があり、引数が異なるため結果をキャッシュできないことを意味します。

主な例は、control flow ポイントカットです。

制御フローポイントカット

Spring 制御フローポイントカットは、概念的には AspectJ cflow ポイントカットに似ていますが、強力ではありません。(現在、ポイントカットが別のポイントカットと一致するジョインポイントで実行されるように指定する方法はありません)制御フローポイントカットは、現在の呼び出しスタックと一致します。例: com.mycompany.web パッケージのメソッドまたは SomeCaller クラスによってジョインポイントが呼び出された場合に発生する可能性があります。制御フローポイントカットは、org.springframework.aop.support.ControlFlowPointcut クラスを使用して指定されます。

制御フローのポイントカットは、他の動的なポイントカットよりも、実行時に評価するのに非常に費用がかかります。Java 1.4 では、コストは他の動的ポイントカットの約 5 倍です。

6.1.5. ポイントカットスーパークラス

Spring は、独自のポイントカットを実装するのに役立つ便利なポイントカットスーパークラスを提供します。

静的ポイントカットは最も有用であるため、おそらく StaticMethodMatcherPointcut をサブクラス化する必要があります。これには、抽象メソッドを 1 つだけ実装する必要があります(ただし、他のメソッドをオーバーライドして動作をカスタマイズできます)。次の例は、StaticMethodMatcherPointcut をサブクラス化する方法を示しています。

Java
class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }
}
Kotlin
class TestStaticPointcut : StaticMethodMatcherPointcut() {

    override fun matches(method: Method, targetClass: Class<*>): Boolean {
        // return true if custom criteria match
    }
}

動的ポイントカット用のスーパークラスもあります。任意のアドバイス型でカスタムポイントカットを使用できます。

6.1.6. カスタムポイントカット

Spring AOP のポイントカットは(AspectJ のような)言語機能ではなく Java クラスであるため、静的または動的にかかわらず、カスタムポイントカットを宣言できます。Spring のカスタムポイントカットは、任意に複雑にすることができます。ただし、可能であれば、AspectJ ポイントカット式言語の使用をお勧めします。

Spring の以降のバージョンは、JAC が提供する「セマンティックポイントカット」のサポートを提供する可能性があります。たとえば、「ターゲットオブジェクトのインスタンス変数を変更するすべてのメソッド」です。

6.2. Spring のアドバイス API

これで、Spring AOP がアドバイスを処理する方法を調べることができます。

6.2.1. アドバイスのライフサイクル

各アドバイスは Spring Bean です。アドバイスインスタンスは、すべてのアドバイスオブジェクトで共有することも、アドバイスオブジェクトごとに一意にすることもできます。これは、クラスごとまたはインスタンスごとのアドバイスに対応しています。

クラスごとのアドバイスが最も頻繁に使用されます。トランザクションアドバイザーなどの一般的なアドバイスに適しています。これらは、プロキシされるオブジェクトの状態に依存したり、新しい状態を追加したりしません。それらは単にメソッドと引数に基づいて動作します。

インスタンスごとのアドバイスは、ミックスインをサポートするための導入に適しています。この場合、アドバイスはプロキシされるオブジェクトに状態を追加します。

同じ AOP プロキシで共有アドバイスとインスタンスごとのアドバイスを組み合わせて使用できます。

6.2.2. Spring のアドバイス型

Spring はいくつかのアドバイス型を提供し、任意のアドバイス型をサポートするために拡張可能です。このセクションでは、基本概念と標準アドバイス型について説明します。

インターセプト Around アドバイス

Spring で最も基本的なアドバイス型は、アドバイスをインターセプトすることです。

Spring は、メソッドインターセプトを使用するアラウンドアドバイス用の AOP Alliance インターフェースに準拠しています。MethodInterceptor を実装し、アドバイスを実装するクラスは、次のインターフェースも実装する必要があります。

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

invoke() メソッドへの MethodInvocation 引数は、呼び出されるメソッド、ターゲットジョインポイント、AOP プロキシ、メソッドへの引数を公開します。invoke() メソッドは、呼び出しの結果、つまりジョインポイントの戻り値を返す必要があります。

次の例は、簡単な MethodInterceptor 実装を示しています。

Java
public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}
Kotlin
class DebugInterceptor : MethodInterceptor {

    override fun invoke(invocation: MethodInvocation): Any {
        println("Before: invocation=[$invocation]")
        val rval = invocation.proceed()
        println("Invocation returned")
        return rval
    }
}

MethodInvocation の proceed() メソッドの呼び出しに注意してください。これはインターセプターチェーンを下ってジョインポイントに向かって進みます。ほとんどのインターセプターはこのメソッドを呼び出し、その戻り値を返します。ただし、MethodInterceptor は、他のアラウンドアドバイスと同様に、proceed メソッドを呼び出すのではなく、異なる値を返すか、例外をスローできます。ただし、正当な理由がない限り、これを行いたくありません。

MethodInterceptor 実装は、他の AOP Alliance 準拠の AOP 実装との相互運用性を提供します。このセクションの残りの部分で説明する他のアドバイス型は、一般的な AOP の概念を実装していますが、Spring 固有の方法です。最も具体的なアドバイス型を使用することには利点がありますが、別の AOP フレームワークでアスペクトを実行する可能性がある場合は、MethodInterceptor をアドバイスに使用してください。ポイントカットは現在フレームワーク間で相互運用可能ではなく、AOP Alliance は現在ポイントカットインターフェースを定義していないことに注意してください。
Before アドバイス

より単純なアドバイス型は、事前アドバイスです。これは、メソッドに入る前にのみ呼び出されるため、MethodInvocation オブジェクトは必要ありません。

before アドバイスの主な利点は、proceed() メソッドを呼び出す必要がないことです。インターセプターチェーンを不注意に進めることができない機能があります。

次のリストは、MethodBeforeAdvice インターフェースを示しています。

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}

(Spring の API 設計では、アドバイスの前にフィールドを許可しますが、通常のオブジェクトはフィールドインターセプトに適用され、Spring がそれを実装することはほとんどありません)

戻り値の型は void であることに注意してください。Before アドバイスは、ジョインポイントの実行前にカスタム動作を挿入できますが、戻り値を変更することはできません。before アドバイスが例外をスローすると、インターセプターチェーンのそれ以上の実行を停止します。例外はインターセプターチェーンに伝搬します。チェックされていない場合、または呼び出されたメソッドの署名上にある場合は、直接クライアントに渡されます。それ以外の場合は、AOP プロキシによって未チェックの例外にラップされます。

次の例は、すべてのメソッド呼び出しをカウントする Spring の事前アドバイスを示しています。

Java
public class CountingBeforeAdvice implements MethodBeforeAdvice {

    private int count;

    public void before(Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}
Kotlin
class CountingBeforeAdvice : MethodBeforeAdvice {

    var count: Int = 0

    override fun before(m: Method, args: Array<Any>, target: Any?) {
        ++count
    }
}
Before アドバイスは、任意のポイントカットで使用できます。
Throws アドバイス

Throws アドバイスは、ジョインポイントが例外をスローした場合、ジョインポイントの復帰後に呼び出されます。Spring は、型付きスローのアドバイスを提供します。これは、org.springframework.aop.ThrowsAdvice インターフェースにメソッドが含まれていないことを意味することに注意してください。これは、指定されたオブジェクトが 1 つ以上の型付きスローアドバイスメソッドを実装することを識別するタグインターフェースです。これらは次の形式である必要があります。

afterThrowing([Method, args, target], subclassOfThrowable)

最後の引数のみが必要です。メソッドシグネチャーには、アドバイスメソッドがメソッドと引数に関心があるかどうかに応じて、1 つまたは 4 つの引数があります。次の 2 つのリストは、スローアドバイスの例であるクラスを示しています。

RemoteException がスローされた場合(サブクラスからも含む)、次のアドバイスが呼び出されます。

Java
public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}
Kotlin
class RemoteThrowsAdvice : ThrowsAdvice {

    fun afterThrowing(ex: RemoteException) {
        // Do something with remote exception
    }
}

前のアドバイスとは異なり、次の例では 4 つの引数を宣言しているため、呼び出されたメソッド、メソッド引数、ターゲットオブジェクトにアクセスできます。ServletException がスローされると、次のアドバイスが呼び出されます。

Java
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
Kotlin
class ServletThrowsAdviceWithArguments : ThrowsAdvice {

    fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
        // Do something with all arguments
    }
}

最後の例は、RemoteException と ServletException の両方を処理する単一のクラスでこれら 2 つのメソッドを使用する方法を示しています。1 つのクラスに任意の数の throws advice メソッドを組み合わせることができます。次のリストは、最後の例を示しています。

Java
public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
Kotlin
class CombinedThrowsAdvice : ThrowsAdvice {

    fun afterThrowing(ex: RemoteException) {
        // Do something with remote exception
    }

    fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
        // Do something with all arguments
    }
}
throws-advice メソッドが例外自体をスローする場合、元の例外をオーバーライドします(つまり、ユーザーにスローされる例外を変更します)。オーバーライドの例外は通常、RuntimeException であり、メソッドシグネチャーと互換性があります。ただし、throws-advice メソッドがチェック済み例外をスローする場合は、ターゲットメソッドの宣言された例外と一致する必要があるため、特定のターゲットメソッドシグネチャーとある程度結びついています。ターゲットメソッドのシグネチャーと互換性のない、宣言されていないチェック例外をスローしないでください!
Throws アドバイスは、任意のポイントカットで使用できます。
After Returning アドバイス

Spring の after returning アドバイスは、次のリストに示す org.springframework.aop.AfterReturningAdvice インターフェースを実装する必要があります。

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}

after returning アドバイスは、戻り値(変更できない)、呼び出されたメソッド、メソッドの引数、ターゲットにアクセスできます。

次の after returning アドバイスは、例外をスローしていないすべての成功したメソッド呼び出しをカウントします。

Java
public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}
Kotlin
class CountingAfterReturningAdvice : AfterReturningAdvice {

    var count: Int = 0
        private set

    override fun afterReturning(returnValue: Any?, m: Method, args: Array<Any>, target: Any?) {
        ++count
    }
}

このアドバイスは実行パスを変更しません。例外をスローすると、戻り値の代わりにインターセプターチェーンがスローされます。

After returning アドバイスは、任意のポイントカットで使用できます。
導入アドバイス

Spring は、導入アドバイスを特別な種類のインターセプトアドバイスとして扱います。

はじめに、次のインターフェースを実装する IntroductionAdvisor および IntroductionInterceptor が必要です。

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

AOP Alliance MethodInterceptor インターフェースから継承された invoke() メソッドは、導入を実装する必要があります。つまり、呼び出されたメソッドが導入されたインターフェース上にある場合、導入インターセプターはメソッド呼び出しの処理を担当します。proceed() を呼び出すことはできません。

導入アドバイスは、メソッドではなくクラスレベルでのみ適用されるため、ポイントカットでは使用できません。導入アドバイスは、次の方法がある IntroductionAdvisor でのみ使用できます。

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}

MethodMatcher はなく、導入アドバイスに関連付けられた Pointcut はありません。クラスフィルタリングのみが論理的です。

getInterfaces() メソッドは、このアドバイザーによって導入されたインターフェースを返します。

validateInterfaces() メソッドは、構成された IntroductionInterceptor によって導入されたインターフェースを実装できるかどうかを確認するために内部的に使用されます。

Spring Test スイートの例を検討し、1 つまたは複数のオブジェクトに次のインターフェースを導入するとします。

Java
public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}
Kotlin
interface Lockable {
    fun lock()
    fun unlock()
    fun locked(): Boolean
}

これはミックスインを示しています。アドバイスされたオブジェクトを Lockable にキャストできるようにしたいため、その型が何であっても、ロックおよびロック解除メソッドを呼び出します。lock() メソッドを呼び出す場合、すべての setter メソッドが LockedException をスローするようにします。オブジェクトを知らなくても不変にする機能を提供するアスペクトを追加できます: AOP の良い例です。

まず、重量物を持ち上げる IntroductionInterceptor が必要です。この場合、org.springframework.aop.support.DelegatingIntroductionInterceptor コンビニエンスクラスを継承します。IntroductionInterceptor を直接実装できますが、ほとんどの場合、DelegatingIntroductionInterceptor を使用するのが最適です。

DelegatingIntroductionInterceptor は、導入されたインターフェースの実際の実装への導入を委譲するように設計されており、そうするためにインターセプトの使用を隠しています。コンストラクター引数を使用して、任意のオブジェクトにデリゲートを設定できます。デフォルトのデリゲート(引数なしのコンストラクターが使用される場合)は this です。次の例では、デリゲートは DelegatingIntroductionInterceptor の LockMixin サブクラスです。デリゲート(デフォルトでは、それ自体)が与えられると、DelegatingIntroductionInterceptor インスタンスは、デリゲート(IntroductionInterceptor 以外)によって実装されるすべてのインターフェースを探し、それらのいずれかに対する導入をサポートします。LockMixin などのサブクラスは、suppressInterface(Class intf) メソッドを呼び出して、公開すべきでないインターフェースを抑制することができます。ただし、IntroductionInterceptor がサポートするインターフェースの数に関係なく、使用される IntroductionAdvisor は実際に公開されるインターフェースを制御します。導入されたインターフェースは、ターゲットによる同じインターフェースの実装を隠します。

LockMixin は DelegatingIntroductionInterceptor を継承し、Lockable 自体を実装します。スーパークラスは、Lockable を導入用にサポートできることを自動的に選択するため、指定する必要はありません。この方法で任意の数のインターフェースを導入できます。

locked インスタンス変数の使用に注意してください。これにより、ターゲットオブジェクトに保持されている状態に追加の状態が効果的に追加されます。

次の例は、LockMixin クラスの例を示しています。

Java
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
            throw new LockedException();
        }
        return super.invoke(invocation);
    }

}
Kotlin
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {

    private var locked: Boolean = false

    fun lock() {
        this.locked = true
    }

    fun unlock() {
        this.locked = false
    }

    fun locked(): Boolean {
        return this.locked
    }

    override fun invoke(invocation: MethodInvocation): Any? {
        if (locked() && invocation.method.name.indexOf("set") == 0) {
            throw LockedException()
        }
        return super.invoke(invocation)
    }

}

多くの場合、invoke() メソッドをオーバーライドする必要はありません。通常、DelegatingIntroductionInterceptor 実装(メソッドが導入された場合は delegate メソッドを呼び出し、それ以外の場合はジョインポイントに向かって進みます)で十分です。この場合、チェックを追加する必要があります。ロックモードの場合、setter メソッドを呼び出すことはできません。

必要な導入部は、別個の LockMixin インスタンスを保持し、導入されたインターフェースを指定するだけです(この場合は Lockable のみ)。より複雑な例では、導入インターセプター(プロトタイプとして定義される)への参照を使用できます。この場合、LockMixin に関連する構成はないため、new を使用して構成を作成します。次の例は、LockMixinAdvisor クラスを示しています。

Java
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}
Kotlin
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)

このアドバイザは、構成を必要としないため、非常に簡単に適用できます。(ただし、IntroductionAdvisor を使用せずに IntroductionInterceptor を使用することはできません)導入では通常どおり、アドバイザーはステートフルであるため、インスタンスごとである必要があります。アドバイスされたオブジェクトごとに LockMixinAdvisor の異なるインスタンス、LockMixin が必要です。アドバイザーは、アドバイスされたオブジェクトの状態の一部を構成します。

他のアドバイザと同様に、Advised.addAdvisor() メソッドを使用するか、XML 構成で(推奨される方法)を使用して、このアドバイザをプログラムで適用できます。「自動プロキシ作成者」を含む、以下で説明するすべてのプロキシ作成の選択は、導入とステートフルミックスインを正しく処理します。

6.3. Spring のアドバイザー API

Spring では、アドバイザはポイントカット式に関連付けられた単一のアドバイスオブジェクトのみを含むアスペクトです。

導入の特別な場合を除いて、どんなアドバイザーもどんなアドバイスでも使うことができます。org.springframework.aop.support.DefaultPointcutAdvisor は、最も一般的に使用されるアドバイザークラスです。MethodInterceptorBeforeAdviceThrowsAdvice で使用できます。

同じ AOP プロキシ内の Spring でアドバイザーとアドバイスの型を混在させることができます。例: 1 つのプロキシ設定でアドバイスの前後でインターセプトを使用し、アドバイスをスローし、アドバイスの前に使用できます。Spring は、必要なインターセプターチェーンを自動的に作成します。

6.4. ProxyFactoryBean を使用して AOP プロキシを作成する

ビジネスオブジェクトに Spring IoC コンテナー(ApplicationContext または BeanFactory)を使用している場合(そしてそうあるべきです! )、Spring の AOP FactoryBean 実装のいずれかを使用する必要があります。(ファクトリ Bean は間接化のレイヤーを導入し、異なる型のオブジェクトを作成できることを思い出してください。)

Spring AOP サポートは、カバーにあるファクトリ Bean も使用します。

Spring で AOP プロキシを作成する基本的な方法は、org.springframework.aop.framework.ProxyFactoryBean を使用することです。これにより、ポイントカット、適用されるアドバイス、それらの順序を完全に制御できます。ただし、このような制御が必要ない場合は、より簡単なオプションがあります。

6.4.1. 基本

ProxyFactoryBean は、他の Spring FactoryBean 実装と同様に、間接的なレベルを導入します。foo という名前の ProxyFactoryBean を定義すると、foo を参照するオブジェクトには ProxyFactoryBean インスタンス自体は表示されませんが、ProxyFactoryBean の getObject() メソッドの実装によって作成されたオブジェクトは表示されます。このメソッドは、ターゲットオブジェクトをラップする AOP プロキシを作成します。

ProxyFactoryBean または別の IoC 対応クラスを使用して AOP プロキシを作成する最も重要な利点の 1 つは、アドバイスとポイントカットも IoC で管理できることです。これは強力な機能であり、他の AOP フレームワークでは達成が難しい特定のアプローチを可能にします。例: アドバイス自体が(任意の AOP フレームワークで利用できるはずのターゲットを除く)アプリケーションオブジェクトを参照し、Dependency Injection によって提供されるすべてのプラガビリティの恩恵を受ける場合があります。

6.4.2. JavaBean のプロパティ

Spring で提供されるほとんどの FactoryBean 実装と共通して、ProxyFactoryBean クラス自体は JavaBean です。そのプロパティは次の目的で使用されます。

一部の主要なプロパティは、org.springframework.aop.framework.ProxyConfig (Spring のすべての AOP プロキシファクトリのスーパークラス)から継承されます。これらの主要なプロパティには次のものが含まれます。

  • proxyTargetClasstrue は、ターゲットクラスのインターフェースではなく、ターゲットクラスをプロキシする場合。このプロパティ値が true に設定されている場合、CGLIB プロキシが作成されます(ただし、JDK および CGLIB ベースのプロキシも参照してください)。

  • optimize: CGLIB を介して作成されたプロキシに積極的な最適化を適用するかどうかを制御します。関連する AOP プロキシが最適化を処理する方法を完全に理解していない限り、この設定を気軽に使用しないでください。これは現在、CGLIB プロキシにのみ使用されます。JDK 動的プロキシでは効果がありません。

  • frozen: プロキシ構成が frozen の場合、構成の変更は許可されなくなりました。これは、わずかな最適化としても、プロキシの作成後に呼び出し元が(Advised インターフェースを介して)プロキシを操作できないようにする場合にも役立ちます。このプロパティのデフォルト値は false であるため、変更(アドバイスの追加など)が許可されます。

  • exposeProxy: ターゲットがアクセスできるように、現在のプロキシを ThreadLocal で公開するかどうかを決定します。ターゲットがプロキシを取得する必要があり、exposeProxy プロパティが true に設定されている場合、ターゲットは AopContext.currentProxy() メソッドを使用できます。

ProxyFactoryBean に固有のその他のプロパティには、次のものがあります。

  • proxyInterfacesString インターフェース名の配列。これが提供されない場合、ターゲットクラスの CGLIB プロキシが使用されます(ただし、JDK および CGLIB ベースのプロキシも参照してください)。

  • interceptorNamesAdvisor の String 配列、インターセプター、適用する他のアドバイス名。先着順でオーダーは重要です。つまり、リスト内の最初のインターセプターが呼び出しをインターセプトできる最初のインターセプターです。

    名前は、祖先ファクトリからの Bean 名を含む、現在のファクトリの Bean 名です。ここで Bean 参照についてメンションすることはできません。そのようにすると、ProxyFactoryBean はアドバイスのシングルトン設定を無視することになります。

    インターセプター名にアスタリスク(*)を追加できます。これにより、適用されるアスタリスクの前の部分で始まる名前を持つすべてのアドバイザ Bean が適用されます。この機能の使用例は「グローバル」アドバイザの使用にあります。

  • シングルトン: getObject() メソッドが呼び出される頻度に関係なく、ファクトリが単一のオブジェクトを返すかどうか。いくつかの FactoryBean 実装がそのような方法を提供します。デフォルト値は true です。ステートフルアドバイス(たとえば、ステートフルミックスイン)を使用する場合は、false のシングルトン値とともにプロトタイプアドバイスを使用します。

6.4.3. JDK および CGLIB ベースのプロキシ

このセクションは、ProxyFactoryBean が特定のターゲットオブジェクト(プロキシされる)に対して JDK ベースのプロキシまたは CGLIB ベースのプロキシを作成する方法の決定的なドキュメントとして機能します。

JDK または CGLIB ベースのプロキシの作成に関する ProxyFactoryBean の動作は、Spring のバージョン 1.2.x および 2.0 の間で変更されました。ProxyFactoryBean は、TransactionProxyFactoryBean クラスのインターフェースと同様に、インターフェースの自動検出に関して同様のセマンティクスを示します。

プロキシされるターゲットオブジェクトのクラス(以降、単にターゲットクラスと呼びます)がインターフェースを実装しない場合、CGLIB ベースのプロキシが作成されます。これは最も簡単なシナリオです。JDK プロキシはインターフェースベースであり、インターフェースがないということは、JDK プロキシが不可能であることを意味するためです。ターゲット Bean をプラグインし、interceptorNames プロパティを設定してインターセプターのリストを指定できます。ProxyFactoryBean の proxyTargetClass プロパティが false に設定されている場合でも、CGLIB ベースのプロキシが作成されることに注意してください。(そうすることは意味がなく、Bean 定義から削除するのが最善です。これは、せいぜい冗長であり、最悪の場合混乱を招くからです。)

ターゲットクラスが 1 つ(または複数)のインターフェースを実装する場合、作成されるプロキシの型は ProxyFactoryBean の構成に依存します。

ProxyFactoryBean の proxyTargetClass プロパティが true に設定されている場合、CGLIB ベースのプロキシが作成されます。これは理にかなっており、最小限の驚きの原則に沿っています。ProxyFactoryBean の proxyInterfaces プロパティが 1 つ以上の完全修飾インターフェース名に設定されている場合でも、proxyTargetClass プロパティが true に設定されているという事実により、CGLIB ベースのプロキシが有効になります。

ProxyFactoryBean の proxyInterfaces プロパティが 1 つ以上の完全修飾インターフェース名に設定されている場合、JDK ベースのプロキシが作成されます。作成されたプロキシは、proxyInterfaces プロパティで指定されたすべてのインターフェースを実装します。ターゲットクラスがたまたま proxyInterfaces プロパティで指定されたインターフェースよりもはるかに多くのインターフェースを実装している場合、すべて良好ですが、それらの追加インターフェースは返されたプロキシによって実装されません。

ProxyFactoryBean の proxyInterfaces プロパティが設定されていないが、ターゲットクラスが 1 つ(または複数)のインターフェースを実装している場合、ProxyFactoryBean はターゲットクラスが実際に少なくとも 1 つのインターフェースと JDK ベースのプロキシを実装するという事実を自動検出します。創造された。実際にプロキシされるインターフェースは、ターゲットクラスが実装するすべてのインターフェースです。実際、これは、ターゲットクラスが proxyInterfaces プロパティに実装するすべてのインターフェースのリストを提供することと同じです。ただし、作業が大幅に少なくなり、誤植が発生しにくくなります。

6.4.4. プロキシインターフェース

ProxyFactoryBean の実際の簡単な例を考えてみましょう。この例には以下が含まれます。

  • プロキシされるターゲット Bean。これは、この例の personTarget Bean 定義です。

  • アドバイスを提供するために使用される Advisor および Interceptor

  • ターゲットオブジェクト(personTarget Bean)、プロキシするインターフェース、適用するアドバイスを指定する AOP プロキシ Bean 定義。

次のリストに例を示します。

<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name" value="Tony"/>
    <property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person"
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>

    <property name="target" ref="personTarget"/>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

interceptorNames プロパティは String のリストを取得することに注意してください。String は、現在のファクトリのインターセプターまたはアドバイザーの Bean 名を保持しています。アドバイザ、インターセプタを使用して、戻る前、後、アドバイスオブジェクトをスローできます。アドバイザーの順序は重要です。

リストに Bean 参照が含まれていない理由を疑問に思うかもしれません。これは、ProxyFactoryBean のシングルトンプロパティが false に設定されている場合、独立したプロキシインスタンスを返すことができる必要があるためです。いずれかのアドバイザ自体がプロトタイプである場合、独立したインスタンスを返す必要があるため、ファクトリからプロトタイプのインスタンスを取得できる必要があります。参照を保持するだけでは不十分です。

前述の person Bean 定義は、次のように Person 実装の代わりに使用できます。

Java
Person person = (Person) factory.getBean("person");
Kotlin
val person = factory.getBean("person") as Person;

同じ IoC コンテキスト内の他の Bean は、通常の Java オブジェクトと同様に、強く型付けされた依存関係を表現できます。次の例は、その方法を示しています。

<bean id="personUser" class="com.mycompany.PersonUser">
    <property name="person"><ref bean="person"/></property>
</bean>

この例の PersonUser クラスは、型 Person のプロパティを公開します。懸念される限り、AOP プロキシは「実際の」人の実装の代わりに透過的に使用できます。ただし、そのクラスは動的プロキシクラスになります。Advised インターフェースにキャストすることは可能です(後で説明します)。

匿名の内部 Bean を使用して、ターゲットとプロキシの区別を隠すことができます。ProxyFactoryBean 定義のみが異なります。アドバイスは完全を期すためにのみ含まれています。次の例は、匿名の内部 Bean の使用方法を示しています。

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
    <!-- Use inner bean, not local reference to target -->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

匿名の内部 Bean を使用すると、型 Person のオブジェクトが 1 つしかないという利点があります。これは、アプリケーションコンテキストのユーザーが非推奨オブジェクトへの参照を取得できないようにする場合、または Spring IoC オートワイヤーのあいまいさを回避する必要がある場合に役立ちます。また、ほぼ間違いなく、ProxyFactoryBean 定義が自己完結しているという利点もあります。ただし、ファクトリから推奨されていないターゲットを取得できることが実際に有利な場合があります(たとえば、特定のテストシナリオ)。

6.4.5. プロキシクラス

1 つ以上のインターフェースではなく、クラスをプロキシする必要がある場合はどうなるでしょうか?

前の例では、Person インターフェースがなかったと想像してください。ビジネスインターフェースを実装していない Person というクラスをアドバイスする必要がありました。この場合、動的プロキシではなく CGLIB プロキシを使用するように Spring を構成できます。これを行うには、前述の ProxyFactoryBean の proxyTargetClass プロパティを true に設定します。クラスではなくインターフェースにプログラミングするのが最善ですが、インターフェースを実装しないクラスにアドバイスする機能は、レガシーコードを使用する場合に役立ちます。(一般的に、Spring は規範的ではありません。優れたプラクティスを簡単に適用できますが、特定のアプローチを強制することは避けます。)

必要に応じて、インターフェースがある場合でも、CGLIB の使用を強制できます。

CGLIB プロキシは、実行時にターゲットクラスのサブクラスを生成することにより機能します。Spring は、この生成されたサブクラスを構成して、メソッド呼び出しを元のターゲットに委譲します。サブクラスは、アドバイスに織り込むデコレーターパターンを実装するために使用されます。

CGLIB プロキシは通常、ユーザーに対して透過的である必要があります。ただし、考慮すべき課題がいくつかあります。

  • Final メソッドはオーバーライドできないため、アドバイスできません。

  • クラスパスに CGLIB を追加する必要はありません。Spring 3.2 の時点で、CGLIB は再パッケージ化され、spring-core JAR に含まれています。言い換えると、CGLIB ベースの AOP は、JDK 動的プロキシと同様に「そのまま」機能します。

CGLIB プロキシと動的プロキシのパフォーマンスの違いはほとんどありません。この場合、パフォーマンスを決定的に考慮することはできません。

6.4.6. 「グローバル」アドバイザの使用

インターセプター名にアスタリスクを追加すると、アスタリスクの前の部分に一致する Bean 名を持つすべてのアドバイザーがアドバイザーチェーンに追加されます。これは、「グローバル」アドバイザの標準セットを追加する必要がある場合に役立ちます。次の例では、2 つのグローバルアドバイザーを定義しています。

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>

6.5. 簡潔なプロキシ定義

特に、トランザクションプロキシを定義する場合、多くの同様のプロキシ定義になる可能性があります。親と子の Bean 定義を、内部の Bean 定義とともに使用すると、プロキシ定義がよりクリーンで簡潔になります。

まず、次のように、プロキシの親、テンプレート、Bean 定義を作成します。

<bean id="txProxyTemplate" abstract="true"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

これはインスタンス化されないため、実際には不完全な場合があります。次に、作成する必要がある各プロキシは子 Bean 定義であり、ターゲットはそれ自体で決して使用されないため、プロキシのターゲットを内部 Bean 定義としてラップします。次の例は、そのような子 Bean を示しています。

<bean id="myService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MyServiceImpl">
        </bean>
    </property>
</bean>

親テンプレートのプロパティをオーバーライドできます。次の例では、トランザクション伝播設定をオーバーライドします。

<bean id="mySpecialService" parent="txProxyTemplate">
    <property name="target">
        <bean class="org.springframework.samples.MySpecialServiceImpl">
        </bean>
    </property>
    <property name="transactionAttributes">
        <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

親 Bean の例では、前述のように abstract 属性を true に設定することで、親 Bean 定義を抽象として明示的にマークしたため、実際にはインスタンス化されないことに注意してください。アプリケーションコンテキスト (ただし、単純な Bean ファクトリではない) は、デフォルトですべてのシングルトンを事前にインスタンス化します。(少なくともシングルトン Bean については) テンプレートとしてのみ使用する予定の (親) Bean 定義があり、この定義でクラスが指定されている場合は、必ず abstract 属性を次のように設定する必要があることが重要です。true。それ以外の場合、アプリケーションコンテキストは実際に事前インスタンス化を試みます。

6.6. ProxyFactory を使用したプログラムによる AOP プロキシの作成

Spring を使用すると、プログラムで AOP プロキシを簡単に作成できます。これにより、Spring IoC に依存せずに Spring AOP を使用できます。

ターゲットオブジェクトによって実装されたインターフェースは自動的にプロキシされます。次のリストは、1 つのインターセプターと 1 つのアドバイザーを使用した、ターゲットオブジェクトのプロキシの作成を示しています。

Java
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
Kotlin
val factory = ProxyFactory(myBusinessInterfaceImpl)
factory.addAdvice(myMethodInterceptor)
factory.addAdvisor(myAdvisor)
val tb = factory.proxy as MyBusinessInterface

最初のステップは、型 org.springframework.aop.framework.ProxyFactory のオブジェクトを作成することです。前の例のように、ターゲットオブジェクトを使用してこれを作成するか、代替コンストラクターでプロキシするインターフェースを指定できます。

アドバイス(インターセプターを特別な種類のアドバイスとして使用)、アドバイザー、その両方を追加し、ProxyFactory の存続期間中に操作できます。IntroductionInterceptionAroundAdvisor を追加すると、プロキシに追加のインターフェースを実装させることができます。

また、ProxyFactory (AdvisedSupport から継承)には、before や throws アドバイスなど、他のアドバイス型を追加できる便利なメソッドがあります。AdvisedSupport は、ProxyFactory と ProxyFactoryBean の両方のスーパークラスです。

AOP プロキシ作成を IoC フレームワークと統合することは、ほとんどのアプリケーションのベストプラクティスです。通常は、AOP を使用して Java コードから設定を外部化することをお勧めします。

6.7. 推奨オブジェクトの操作

ただし、AOP プロキシを作成する場合、org.springframework.aop.framework.Advised インターフェースを使用して操作できます。AOP プロキシは、他のどのインターフェースを実装していても、このインターフェースにキャストできます。このインターフェースには次のメソッドが含まれます。

Java
Advisor[] getAdvisors();

void addAdvice(Advice advice) throws AopConfigException;

void addAdvice(int pos, Advice advice) throws AopConfigException;

void addAdvisor(Advisor advisor) throws AopConfigException;

void addAdvisor(int pos, Advisor advisor) throws AopConfigException;

int indexOf(Advisor advisor);

boolean removeAdvisor(Advisor advisor) throws AopConfigException;

void removeAdvisor(int index) throws AopConfigException;

boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;

boolean isFrozen();
Kotlin
fun getAdvisors(): Array<Advisor>

@Throws(AopConfigException::class)
fun addAdvice(advice: Advice)

@Throws(AopConfigException::class)
fun addAdvice(pos: Int, advice: Advice)

@Throws(AopConfigException::class)
fun addAdvisor(advisor: Advisor)

@Throws(AopConfigException::class)
fun addAdvisor(pos: Int, advisor: Advisor)

fun indexOf(advisor: Advisor): Int

@Throws(AopConfigException::class)
fun removeAdvisor(advisor: Advisor): Boolean

@Throws(AopConfigException::class)
fun removeAdvisor(index: Int)

@Throws(AopConfigException::class)
fun replaceAdvisor(a: Advisor, b: Advisor): Boolean

fun isFrozen(): Boolean

getAdvisors() メソッドは、アドバイザー、インターセプター、ファクトリに追加された他のアドバイス型ごとに Advisor を返します。Advisor を追加した場合、このインデックスで返される advisor は追加したオブジェクトです。インターセプターまたは他のアドバイス型を追加した場合、Spring は、常に true を返すポイントカットでアドバイザーにこれをラップしました。MethodInterceptor を追加した場合、このインデックスに返されるアドバイザは、MethodInterceptor を返す DefaultPointcutAdvisor と、すべてのクラスおよびメソッドに一致するポイントカットです。

addAdvisor() メソッドを使用して、Advisor を追加できます。通常、ポイントカットとアドバイスを保持するアドバイザーは汎用の DefaultPointcutAdvisor です。これは、アドバイスやポイントカットで使用できます(ただし、導入用ではありません)。

デフォルトでは、プロキシが作成された後でもアドバイザまたはインターセプタを追加または削除できます。唯一の制限は、ファクトリからの既存のプロキシにはインターフェースの変更が表示されないため、導入アドバイザーを追加または削除できないことです。(この問題を回避するために、ファクトリから新しいプロキシを取得できます。)

次の例は、AOP プロキシを Advised インターフェースにキャストし、そのアドバイスを調べて操作することを示しています。

Java
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());

// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
Kotlin
val advised = myObject as Advised
val advisors = advised.advisors
val oldAdvisorCount = advisors.size
println("$oldAdvisorCount advisors")

// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(DebugInterceptor())

// Add selective advice using a pointcut
advised.addAdvisor(DefaultPointcutAdvisor(mySpecialPointcut, myAdvice))

assertEquals("Added two advisors", oldAdvisorCount + 2, advised.advisors.size)
当然のことながら、正当的な使用例がありますが、本番中のビジネスオブジェクトに関するアドバイスを変更することが望ましいかどうかは疑問です。ただし、開発(テストなど)では非常に役立ちます。インターセプターまたはその他のアドバイスの形式でテストコードを追加し、テストするメソッド呼び出しの内部に入れることが非常に便利であることがわかっています。(例: アドバイスをそのメソッド用に作成されたトランザクション内に入れることができます。トランザクションをロールバックするようにマークする前に、おそらく SQL を実行してデータベースが正しく更新されたことを確認します。)

プロキシの作成方法に応じて、通常は frozen フラグを設定できます。その場合、Advised isFrozen() メソッドは true を返し、追加または削除によってアドバイスを変更しようとすると、AopConfigException になります。アドバイスされたオブジェクトの状態をフリーズする機能は、場合によっては便利です(たとえば、コードの呼び出しによってセキュリティインターセプターが削除されるのを防ぐため)。

6.8. 「自動プロキシ」機能を使用する

これまで、ProxyFactoryBean または同様のファクトリ Bean を使用して、AOP プロキシの明示的な作成を検討してきました。

Spring では、選択した Bean 定義を自動的にプロキシできる「自動プロキシ」Bean 定義も使用できます。これは、Spring の「Bean ポストプロセッサー」インフラストラクチャ上に構築されており、コンテナーのロード時に Bean 定義を変更できます。

このモデルでは、XML Bean 定義ファイルに特別な Bean 定義を設定して、自動プロキシインフラストラクチャを構成します。これにより、自動プロキシに適格なターゲットを宣言できます。ProxyFactoryBean を使用する必要はありません。

これを行うには 2 つの方法があります。

  • 現在のコンテキストの特定の Bean を参照する自動プロキシクリエーターを使用します。

  • 個別に考慮するに値する自動プロキシ作成の特殊なケース: ソースレベルのメタデータ属性によって駆動される自動プロキシ作成。

6.8.1. 自動プロキシ Bean 定義

このセクションでは、org.springframework.aop.framework.autoproxy パッケージが提供する自動プロキシ作成者について説明します。

BeanNameAutoProxyCreator

BeanNameAutoProxyCreator クラスは、リテラル値またはワイルドカードに一致する名前を持つ Bean の AOP プロキシを自動的に作成する BeanPostProcessor です。次の例は、BeanNameAutoProxyCreator Bean を作成する方法を示しています。

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="jdk*,onlyJdk"/>
    <property name="interceptorNames">
        <list>
            <value>myInterceptor</value>
        </list>
    </property>
</bean>

ProxyFactoryBean と同様に、プロトタイプアドバイザーの正しい動作を可能にするために、インターセプターのリストではなく interceptorNames プロパティがあります。「インターセプター」という名前は、アドバイザーまたは任意のアドバイス型です。

一般的な自動プロキシの場合と同様に、BeanNameAutoProxyCreator を使用する主なポイントは、最小限の構成で同じ構成を複数のオブジェクトに一貫して適用することです。これは、宣言型トランザクションを複数のオブジェクトに適用するための一般的な選択肢です。

上記の例の jdkMyBean および onlyJdk など、名前が一致する Bean 定義は、ターゲットクラスを持つ単純な古い Bean 定義です。AOP プロキシは、BeanNameAutoProxyCreator によって自動的に作成されます。同じアドバイスが、一致するすべての Bean に適用されます。(前の例のインターセプターではなく)advisor が使用される場合、ポイントカットは異なる Bean に異なる方法で適用される場合があることに注意してください。

DefaultAdvisorAutoProxyCreator

より一般的で非常に強力な自動プロキシ作成者は DefaultAdvisorAutoProxyCreator です。これは、自動プロキシアドバイザーの Bean 定義に特定の Bean 名を含める必要なく、現在のコンテキストで適格なアドバイザーを自動的に適用します。BeanNameAutoProxyCreator と同じ一貫性のある構成と重複の回避というメリットがあります。

このメカニズムの使用には以下が含まれます。

  • DefaultAdvisorAutoProxyCreator Bean 定義の指定。

  • 同じまたは関連するコンテキストで任意の数のアドバイザーを指定します。これらはインターセプターやその他のアドバイスではなく、アドバイザーでなければならないことに注意してください。Bean の候補の定義に対する各アドバイスの適格性をチェックするために、評価するためのポイントカットが必要なので、これが必要です。

DefaultAdvisorAutoProxyCreator は、各アドバイザーに含まれるポイントカットを自動的に評価し、各ビジネスオブジェクト(例の businessObject1 や businessObject2 など)に適用するアドバイス(ある場合)を確認します。

これは、任意の数のアドバイザーを各ビジネスオブジェクトに自動的に適用できることを意味します。アドバイザーのいずれのポイントカットもビジネスオブジェクトのメソッドと一致しない場合、オブジェクトはプロキシされません。Bean 定義は新しいビジネスオブジェクトに追加されるため、必要に応じて自動的にプロキシされます。

一般に、自動プロキシには、呼び出し元または依存関係が非推奨オブジェクトを取得できないようにするという利点があります。この ApplicationContext で getBean("businessObject1") を呼び出すと、ターゲットビジネスオブジェクトではなく、AOP プロキシが返されます。(前述の「内部 Bean」イディオムもこの利点を提供します。)

次の例では、DefaultAdvisorAutoProxyCreator Bean と、このセクションで説明する他の要素を作成します。

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
    <property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>

<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>

<bean id="businessObject1" class="com.mycompany.BusinessObject1">
    <!-- Properties omitted -->
</bean>

<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>

DefaultAdvisorAutoProxyCreator は、多くのビジネスオブジェクトに同じアドバイスを一貫して適用する場合に非常に便利です。インフラストラクチャの定義が整ったら、特定のプロキシ構成を含めずに新しいビジネスオブジェクトを追加できます。また、構成の変更を最小限に抑えて、追加のアスペクト(トレースやパフォーマンス監視のアスペクトなど)を簡単にドロップできます。

DefaultAdvisorAutoProxyCreator は、フィルタリング(同じファクトリで複数の異なる構成の AdvisorAutoProxyCreators の使用を許可する特定のアドバイザーのみが評価されるように命名規則を使用することによる)および順序付けのサポートを提供します。アドバイザは、org.springframework.core.Ordered インターフェースを実装して、これが課題である場合に正しい順序を確保できます。前の例で使用した TransactionAttributeSourceAdvisor には、構成可能な順序値があります。デフォルト設定は順不同です。

6.9. TargetSource 実装の使用

Spring は、org.springframework.aop.TargetSource インターフェースで表現される TargetSource の概念を提供します。このインターフェースは、ジョインポイントを実装する「ターゲットオブジェクト」を返すロールを果たします。TargetSource 実装は、AOP プロキシがメソッド呼び出しを処理するたびにターゲットインスタンスを要求されます。

Spring AOP を使用する開発者は、通常 TargetSource 実装を直接操作する必要はありませんが、これはプーリング、ホットスワップ可能、その他の高度なターゲットをサポートする強力な手段を提供します。例: プールを使用してインスタンスを管理することにより、プーリング TargetSource は呼び出しごとに異なるターゲットインスタンスを返すことができます。

TargetSource を指定しない場合、デフォルトの実装がローカルオブジェクトのラップに使用されます。(予想どおり)呼び出しごとに同じターゲットが返されます。

このセクションの残りの部分では、Spring で提供される標準ターゲットソースとその使用方法について説明します。

カスタムターゲットソースを使用する場合、通常、ターゲットはシングルトン Bean 定義ではなくプロトタイプである必要があります。これにより、Spring は必要に応じて新しいターゲットインスタンスを作成できます。

6.9.1. ホットスワップ可能なターゲットソース

org.springframework.aop.target.HotSwappableTargetSource は、AOP プロキシのターゲットを切り替えながら、呼び出し元がそれへの参照を保持できるようにするために存在します。

ターゲットソースのターゲットの変更はすぐに有効になります。HotSwappableTargetSource はスレッドセーフです。

次の例に示すように、HotSwappableTargetSource で swap() メソッドを使用してターゲットを変更できます。

Java
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
Kotlin
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val oldTarget = swapper.swap(newTarget)

次の例は、必要な XML 定義を示しています。

<bean id="initialTarget" class="mycompany.OldTarget"/>

<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
    <constructor-arg ref="initialTarget"/>
</bean>

<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="swapper"/>
</bean>

上記の swap() 呼び出しは、交換可能な Bean のターゲットを変更します。その Bean への参照を保持しているクライアントは、変更を認識しませんが、すぐに新しいターゲットにヒットし始めます。

この例ではアドバイスを追加しませんが(TargetSource を使用するためにアドバイスを追加する必要はありません)、TargetSource は任意のアドバイスと組み合わせて使用できます。

6.9.2. ターゲットソースのプーリング

プーリングターゲットソースを使用すると、ステートレスセッション EJB と同様のプログラミングモデルが提供され、同一インスタンスのプールが維持され、メソッド呼び出しがプール内のオブジェクトを解放します。

Spring プーリングと SLSB プーリングの重要な違いは、Spring プーリングを任意の POJO に適用できることです。一般的な Spring と同様に、このサービスは非侵襲的な方法で適用できます。

Spring は、かなり効率的なプーリング実装を提供する Commons Pool 2.2 のサポートを提供します。この機能を使用するには、アプリケーションのクラスパスに commons-pool Jar が必要です。org.springframework.aop.target.AbstractPoolingTargetSource をサブクラス化して、他のプーリング API をサポートすることもできます。

Commons Pool 1.5+ もサポートされていますが、Spring Framework 4.2 で非推奨になりました。

次のリストは、構成の例を示しています。

<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
        scope="prototype">
    ... properties omitted
</bean>

<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
    <property name="maxSize" value="25"/>
</bean>

<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="targetSource" ref="poolTargetSource"/>
    <property name="interceptorNames" value="myInterceptor"/>
</bean>

ターゲットオブジェクト(前の例では businessObjectTarget)はプロトタイプでなければならないことに注意してください。これにより、PoolingTargetSource 実装はターゲットの新しいインスタンスを作成し、必要に応じてプールを拡大できます。AbstractPoolingTargetSource の javadoc およびそのプロパティに関する情報については、使用する具象サブクラスを参照してください。maxSize は最も基本的なものであり、常に存在することが保証されています。

この場合、myInterceptor は同じ IoC コンテキストで定義する必要があるインターセプターの名前です。ただし、プーリングを使用するインターセプターを指定する必要はありません。プーリングのみを行い、その他のアドバイスは必要ない場合は、interceptorNames プロパティを設定しないでください。

プールされたオブジェクトを org.springframework.aop.target.PoolingConfig インターフェースにキャストできるように Spring を構成することができます。これにより、導入を通じてプールの構成と現在のサイズに関する情報が公開されます。次のようなアドバイザを定義する必要があります。

<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="poolTargetSource"/>
    <property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>

このアドバイザは、AbstractPoolingTargetSource クラスでコンビニエンスメソッドを呼び出すことによって取得されるため、MethodInvokingFactoryBean が使用されます。このアドバイザーの名前(ここでは poolConfigAdvisor)は、プールされたオブジェクトを公開する ProxyFactoryBean のインターセプター名のリストに含まれている必要があります。

キャストは次のように定義されます。

Java
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
Kotlin
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
通常、ステートレスサービスオブジェクトのプーリングは必要ありません。ほとんどのステートレスオブジェクトは自然にスレッドセーフであり、リソースがキャッシュされるとインスタンスプーリングに問題があるため、デフォルトの選択であるとは考えていません。

自動プロキシを使用すると、よりシンプルなプーリングが可能になります。自動プロキシ作成者が使用する TargetSource 実装を設定できます。

6.9.3. プロトタイプターゲットソース

「プロトタイプ」ターゲットソースのセットアップは、プーリング TargetSource のセットアップに似ています。この場合、メソッド呼び出しのたびにターゲットの新しいインスタンスが作成されます。新しいオブジェクトを作成するコストは最新の JVM では高くありませんが、新しいオブジェクトを接続する(IoC 依存関係を満たす)コストはより高くなる可能性があります。非常に正当な理由がない限り、このアプローチを使用しないでください。

これを行うには、前述の poolTargetSource 定義を次のように変更できます(わかりやすくするために名前も変更しました)。

<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
    <property name="targetBeanName" ref="businessObjectTarget"/>
</bean>

唯一のプロパティは、ターゲット Bean の名前です。TargetSource 実装では、一貫性のある命名を保証するために継承が使用されます。プーリングターゲットソースと同様に、ターゲット Bean はプロトタイプ Bean 定義でなければなりません。

6.9.4. ThreadLocal ターゲットソース

ThreadLocal ターゲットソースは、受信リクエストごとに(つまり、スレッドごとに)オブジェクトを作成する必要がある場合に役立ちます。ThreadLocal の概念は、スレッドとともにリソースを透過的に格納する JDK 全体の機能を提供します。ThreadLocalTargetSource のセットアップは、次の例が示すように、他の型のターゲットソースで説明したものとほとんど同じです。

<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
    <property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal インスタンスは、マルチスレッド環境およびマルチクラスローダー環境で誤って使用すると、重大な課題(メモリリークが発生する可能性があります)を伴います。常に他のクラスで threadlocal をラップすることを検討し、ThreadLocal 自体を直接使用しないでください(ラッパークラスを除く)。また、スレッドにローカルなリソースを正しく設定および設定解除することを忘れないでください(後者は単に ThreadLocal.set(null) の呼び出しを含む)。設定を解除しないと、課題のある動作になる可能性があるため、いずれの場合も設定解除を行う必要があります。Spring の ThreadLocal サポートはこれをあなたに代わって行い、他の適切な処理コードなしで ThreadLocal インスタンスを使用することを常に考慮すべきです。

6.10. 新しいアドバイス型の定義

Spring AOP は拡張可能に設計されています。インターセプト実装戦略は現在内部的に使用されていますが、アドバイス、ビフォア、スローアドバイス、after returning アドバイスの周囲のインターセプトに加えて、任意のアドバイス型をサポートすることが可能です。

org.springframework.aop.framework.adapter パッケージは、コアフレームワークを変更せずに新しいカスタムアドバイス型のサポートを追加できる SPI パッケージです。カスタム Advice 型の唯一の制約は、org.aopalliance.aop.Advice マーカーインターフェースを実装する必要があるということです。

詳細については、org.springframework.aop.framework.adapter javadoc を参照してください。

7. null セーフ

Java ではその型システムで null 安全性を表現できませんが、Spring Framework では org.springframework.lang パッケージで次のアノテーションを提供して、API とフィールドの null 可能性を宣言できるようになりました。

  • @Nullable (Javadoc) : 特定のパラメーター、戻り値、フィールドが null になる可能性があることを示すアノテーション。

  • @NonNull (Javadoc) : 特定のパラメーター、戻り値、フィールドを null にできないことを示すアノテーション(それぞれ、@NonNullApi および @NonNullFields が適用されるパラメーター / 戻り値およびフィールドには不要)。

  • @NonNullApi (Javadoc) : パラメーターおよび戻り値のデフォルトのセマンティクスとして非 null を宣言するパッケージレベルでのアノテーション。

  • @NonNullFields (Javadoc) : フィールドのデフォルトのセマンティクスとして null 以外を宣言するパッケージレベルでのアノテーション。

Spring Framework 自体はこれらのアノテーションを活用しますが、任意の Spring ベースの Java プロジェクトで使用して、null セーフ API およびオプションで null セーフフィールドを宣言することもできます。ジェネリクス型の引数、可変引数、配列要素の NULL 可能性はまだサポートされていませんが、今後のリリースに含まれるはずです。最新情報については、SPR-15942 (英語) を参照してください。Nullability 宣言は、マイナーリリースを含む Spring Framework リリース間で微調整されることが期待されています。メソッド本体内で使用される型の Nullability は、この機能の範囲外です。

Reactor や Spring Data などの他の一般的なライブラリは、同様の nullability 配置を使用する null セーフ API を提供し、Spring アプリケーション開発者に一貫した全体的なエクスペリエンスを提供します。

7.1. ユースケース

Spring Framework API nullability の明示的な宣言を提供することに加えて、これらのアノテーションは IDE(IDEA や Eclipse など)によって使用され、実行時の NullPointerException を回避するために null 安全に関連する有用な警告を提供できます。

Kotlin はネイティブで null-safety (英語) をサポートしているため、Kotlin プロジェクトで Spring API を null-safe にするためにも使用されます。詳細は Kotlin サポートドキュメントで入手できます。

7.2. JSR-305 メタアノテーション

Spring アノテーションは、JSR 305 (英語) アノテーション(休止しているが広く普及している JSR)でメタアノテーションが付けられています。JSR-305 メタアノテーションにより、IDEA や Kotlin などのツールベンダーは、Spring アノテーションのサポートをハードコードすることなく、一般的な方法で null-safety サポートを提供できます。

Spring null-safe API を利用するために、プロジェクトのクラスパスに JSR-305 依存関係を追加する必要も推奨もありません。コンパイルの警告を回避するために、コードベースで null-safety アノテーションを使用する Spring ベースのライブラリなどのプロジェクトのみが、com.google.code.findbugs:jsr305:3.0.2 を compileOnly Gradle 構成または Maven provided スコープに追加する必要があります。

8. データバッファとコーデック

Java NIO は ByteBuffer を提供しますが、多くのライブラリは、特にバッファの再利用や直接バッファの使用がパフォーマンスに有益であるネットワーク操作のために、独自のバイトバッファ API を構築します。たとえば、Netty には ByteBuf 階層があり、Undertow は XNIO を使用し、Jetty は解放されるコールバックとともにプールされたバイトバッファーを使用します。spring-core モジュールは、次のようにさまざまなバイトバッファ API を操作するための一連の抽象化を提供します。

  • DataBufferFactory は、データバッファーの作成を抽象化します。

  • DataBuffer は、プールできるバイトバッファを表します。

  • DataBufferUtils は、データバッファ用のユーティリティメソッドを提供します。

  • コーデックは、ストリームデータバッファーストリームをより高いレベルのオブジェクトにデコードまたはエンコードします。

8.1. DataBufferFactory

DataBufferFactory は、次の 2 つの方法のいずれかでデータバッファを作成するために使用されます。

  1. DataBuffer の実装は要求に応じて拡大および縮小できますが、既知の場合は事前に容量を指定するオプションで、新しいデータバッファーを割り当てます。

  2. 既存の byte[] または java.nio.ByteBuffer をラップします。これにより、指定されたデータが DataBuffer 実装で装飾され、割り当ては行われません。

WebFlux アプリケーションは DataBufferFactory を直接作成せず、代わりにクライアント側の ServerHttpResponse または ClientHttpRequest を介してそれにアクセスすることに注意してください。ファクトリの型は、基盤となるクライアントまたはサーバーによって異なります。Reactor Netty の場合は NettyDataBufferFactory、その他の場合は DefaultDataBufferFactory

8.2. DataBuffer

DataBuffer インターフェースは java.nio.ByteBuffer と同様の操作を提供しますが、Netty ByteBuf に触発されたいくつかの追加の利点ももたらします。以下は、利点の一部のリストです。

  • 独立した位置での読み取りと書き込み、つまり読み取りと書き込みを交互に行うために flip() を呼び出す必要はありません。

  • java.lang.StringBuilder と同様に、要求に応じて容量が拡張されました。

  • プールされたバッファーと PooledDataBuffer を介した参照カウント。

  • バッファを java.nio.ByteBufferInputStreamOutputStream として表示します。

  • 特定のバイトのインデックスまたは最後のインデックスを決定します。

8.3. PooledDataBuffer

ByteBuffer (標準 Javadoc) の Javadoc に従って、バイトバッファーは直接または非直接にできます。ダイレクトバッファは Java ヒープの外側に存在する場合があり、ネイティブ I/O 操作のためにコピーする必要がなくなります。これにより、直接バッファはソケットを介してデータを送受信するのに特に役立ちますが、作成および解放するのに費用がかかるため、バッファをプールするという考えにつながります。

PooledDataBuffer は DataBuffer の拡張であり、バイトバッファプーリングに不可欠な参照カウントを支援します。どのように機能しますか? PooledDataBuffer が割り当てられると、参照カウントは 1 になります。retain() を呼び出すとカウントが増加し、release() を呼び出すとカウントが減少します。カウントが 0 を超えている限り、バッファは解放されないことが保証されます。カウントが 0 に減少すると、プールされたバッファを解放できます。実際には、バッファの予約メモリがメモリプールに戻される可能性があります。

PooledDataBuffer を直接操作する代わりに、ほとんどの場合、PooledDataBuffer のインスタンスである場合にのみ DataBuffer にリリースまたは保持を適用する DataBufferUtils の便利なメソッドを使用することをお勧めします。

8.4. DataBufferUtils

DataBufferUtils は、データバッファを操作するためのユーティリティメソッドをいくつか提供します。

  • データバッファのストリームを、おそらくゼロコピーで単一のバッファに結合します。基になるバイトバッファー API でサポートされている場合は、複合バッファー経由。

  • InputStream または NIO Channel を Flux<DataBuffer> に、またはその逆に Publisher<DataBuffer> を OutputStream または NIO Channel に変えます。

  • バッファーが PooledDataBuffer のインスタンスである場合、DataBuffer を解放または保持するメソッド。

  • 特定のバイトカウントまでバイトストリームからスキップまたは取得します。

8.5. コーデック

org.springframework.core.codec パッケージは、次の戦略インターフェースを提供します。

  •  Publisher<T> をデータバッファのストリームにエンコードする Encoder

  • Decoder は、Publisher<DataBuffer> をより高いレベルのオブジェクトのストリームにデコードします。

spring-core モジュールは、byte[]ByteBufferDataBufferResourceString エンコーダーおよびデコーダーの実装を提供します。spring-web モジュールは、Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers、その他のエンコーダーとデコーダーを追加します。WebFlux セクションのコーデックを参照してください。

8.6. DataBuffer を使用する

データバッファーを使用する場合、バッファーがプールされる可能性があるため、バッファーが解放されるように特に注意する必要があります。コーデックを使用してその仕組みを説明しますが、概念はより一般的に適用されます。データバッファを管理するためにコーデックが内部的に行う必要があるものを見てみましょう。

Decoder は、より高いレベルのオブジェクトを作成する前に、入力データバッファーを最後に読み取るため、次のように解放する必要があります。

  1. Decoder が単に各入力バッファーを読み取り、すぐにそれを解放する準備ができている場合、DataBufferUtils.release(dataBuffer) を介して解放できます。

  2. Decoder が Flux または Mono 演算子(flatMapreduce など)を使用してデータ項目を内部でプリフェッチおよびキャッシュする場合、または filterskip などの演算子を使用して項目を除外する場合は、doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release) をコンポジションに追加する必要があります。チェーンは、このようなバッファーが破棄される前に解放されるようにします。これは、エラーまたはキャンセルシグナルの結果として発生する可能性もあります。

  3. Decoder が他の方法で 1 つ以上のデータバッファーを保持している場合、完全に読み取られたとき、キャッシュされたデータバッファーが読み取られて解放される前にエラーまたはキャンセルシグナルが発生した場合、確実に解放する必要があります

DataBufferUtils#join は、データバッファストリームを単一のデータバッファに集約する安全で効率的な方法を提供することに注意してください。同様に、skipUntilByteCount と takeUntilByteCount は、デコーダーが使用する追加の安全な方法です。

Encoder は、他の人が読み取る(および解放する)必要があるデータバッファを割り当てます。Encoder にはあまり関係がありません。ただし、Encoder は、バッファーにデータを取り込む際に直列化エラーが発生した場合、データバッファーを解放するように注意する必要があります。例:

Java
DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
    // serialize and populate buffer..
    release = false;
}
finally {
    if (release) {
        DataBufferUtils.release(buffer);
    }
}
return buffer;
Kotlin
val buffer = factory.allocateBuffer()
var release = true
try {
    // serialize and populate buffer..
    release = false
} finally {
    if (release) {
        DataBufferUtils.release(buffer)
    }
}
return buffer

Encoder のコンシューマーは、受信したデータバッファーを解放する責任があります。WebFlux アプリケーションでは、Encoder の出力を使用して、HTTP サーバーレスポンスまたはクライアント HTTP リクエストに書き込みます。この場合、データバッファーの解放は、サーバーレスポンスまたはクライアントへのコード書き込みの責任です。リクエスト。

Netty で実行する場合、バッファリークのトラブルシューティング [GitHub] (英語) 用のデバッグオプションがあることに注意してください。

9. ログ

Spring Framework 5.0 以降、Spring には、spring-jcl モジュールに実装された独自の Commons Logging ブリッジが付属しています。実装は、クラスパスに Log4j 2.x API と SLF4J 1.7 API が存在するかどうかを確認し、最初に見つかったものをロギング実装として使用し、Log4j 2.x も SLF4J も使用できない場合は Java プラットフォームのコアロギング機能(JUL または java.util.logging とも呼ばれます)にフォールバックします。

Log4j 2.x または Logback(または別の SLF4J プロバイダー)を追加のブリッジなしでクラスパスに配置し、フレームワークを選択に自動適応させます。詳細については、Spring Boot ロギングリファレンスドキュメントを参照してください。

Spring の Commons Logging バリアントは、コアフレームワークおよび拡張機能でインフラストラクチャロギングの目的でのみ使用することを目的としています。

アプリケーションコード内のログのニーズについては、Log4j 2.x、SLF4J、JUL を直接使用することをお勧めします。

Log 実装は、次の例のように org.apache.commons.logging.LogFactory を介して取得できます。

Java
public class MyBean {
    private final Log log = LogFactory.getLog(getClass());
    // ...
}
Kotlin
class MyBean {
  private val log = LogFactory.getLog(javaClass)
  // ...
}

10. 付録

10.1. XML スキーマ

付録のこのパートには、コアコンテナーに関連する XML スキーマがリストされています。

10.1.1. util スキーマ

名前が示すように、util タグは、コレクションの構成、定数の参照など、一般的なユーティリティ構成の課題を処理します。util スキーマでタグを使用するには、Spring XML 構成ファイルの先頭に次のプリアンブルが必要です(スニペットのテキストは正しいスキーマを参照するため、util 名前空間のタグを使用できます)。

<?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:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">

        <!-- bean definitions here -->

</beans>
<util:constant/> を使用する

以下の Bean 定義を考慮してください。

<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>

上記の構成では、Spring FactoryBean 実装(FieldRetrievingFactoryBean)を使用して、Bean の isolation プロパティの値を java.sql.Connection.TRANSACTION_SERIALIZABLE 定数の値に設定します。これはすべてうまくいきますが、冗長であり、(不必要に)Spring の内部接続機能をエンドユーザーに公開します。

次の XML スキーマベースのバージョンはより簡潔で、開発者の意図を明確に表し(「この定数値を挿入する」)、読みやすくなっています。

<bean id="..." class="...">
    <property name="isolation">
        <util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
    </property>
</bean>
フィールド値からの Bean プロパティまたはコンストラクター引数の設定

FieldRetrievingFactoryBean (Javadoc) は、static または非静的フィールド値を取得する FactoryBean です。通常、public static final 定数を取得するために使用され、その後、別の Bean のプロパティ値またはコンストラクター引数を設定するために使用できます。

次の例は、staticField (Javadoc) プロパティを使用して、static フィールドがどのように公開されるかを示しています。

<bean id="myField"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
    <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>

次の例に示すように、static フィールドが Bean 名として指定されている便利な使用形態もあります。

<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
        class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>

これは、Bean の id が何であるかはもはや選択の余地がないことを意味します(したがって、それを参照する他の Bean もこの長い名前を使用しなければなりません)が、この形式は定義が非常に簡潔であり、次の例が示すように、Bean の参照に対して id を指定する必要がないため、内側の Bean として使用するのに非常に便利です。

<bean id="..." class="...">
    <property name="isolation">
        <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
                class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    </property>
</bean>

FieldRetrievingFactoryBean (Javadoc) クラスの API ドキュメントに従って、別の Bean の非静的 (インスタンス) フィールドにアクセスすることもできます。

Spring では、プロパティ値またはコンストラクター引数として列挙値を Bean に注入するのは簡単です。実際には、Spring 内部(または FieldRetrievingFactoryBean などのクラスについて)について何もする必要も、何も知る必要もありません。次の列挙例は、列挙値を簡単に挿入できることを示しています。

Java
package javax.persistence;

public enum PersistenceContextType {

    TRANSACTION,
    EXTENDED
}
Kotlin
package javax.persistence

enum class PersistenceContextType {

    TRANSACTION,
    EXTENDED
}

次に、型 PersistenceContextType の以下の setter と、対応する Bean 定義を検討します。

Java
package example;

public class Client {

    private PersistenceContextType persistenceContextType;

    public void setPersistenceContextType(PersistenceContextType type) {
        this.persistenceContextType = type;
    }
}
Kotlin
package example

class Client {

    lateinit var persistenceContextType: PersistenceContextType
}
<bean class="example.Client">
    <property name="persistenceContextType" value="TRANSACTION"/>
</bean>
<util:property-path/> を使用する

次の例を考えてみましょう。

<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<bean id="testBean.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>

前述の構成では、Spring FactoryBean 実装(PropertyPathFactoryBean)を使用して、testBean Bean の age プロパティに等しい値を持つ testBean.age と呼ばれる Bean(型 int)を作成します。

ここで、<util:property-path/> 要素を追加する次の例を検討してください。

<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<util:property-path id="name" path="testBean.age"/>

<property-path/> 要素の path 属性の値は、beanName.beanProperty の形式に従います。この場合、testBean という名前の Bean の age プロパティを取得します。その age プロパティの値は 10 です。

<util:property-path/> を使用して Bean プロパティまたはコンストラクター引数を設定する

PropertyPathFactoryBean は、指定されたターゲットオブジェクトのプロパティパスを評価する FactoryBean です。ターゲットオブジェクトは、直接または Bean 名で指定できます。その後、この値を別の Bean 定義でプロパティ値またはコンストラクター引数として使用できます。

次の例は、名前によって別の Bean に対して使用されるパスを示しています。

<!-- target bean to be referenced by name -->
<bean id="person" class="org.springframework.beans.TestBean" scope="prototype">
    <property name="age" value="10"/>
    <property name="spouse">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="11"/>
        </bean>
    </property>
</bean>

<!-- results in 11, which is the value of property 'spouse.age' of bean 'person' -->
<bean id="theAge"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    <property name="targetBeanName" value="person"/>
    <property name="propertyPath" value="spouse.age"/>
</bean>

次の例では、パスが内部 Bean に対して評価されます。

<!-- results in 12, which is the value of property 'age' of the inner bean -->
<bean id="theAge"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    <property name="targetObject">
        <bean class="org.springframework.beans.TestBean">
            <property name="age" value="12"/>
        </bean>
    </property>
    <property name="propertyPath" value="age"/>
</bean>

ショートカットフォームもあります。Bean 名はプロパティパスです。次の例は、ショートカットフォームを示しています。

<!-- results in 10, which is the value of property 'age' of bean 'person' -->
<bean id="person.age"
        class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>

この形式は、Bean の名前に選択肢がないことを意味します。それへの参照も、パスである同じ id を使用する必要があります。内部 Bean として使用する場合、次の例に示すように、それを参照する必要はまったくありません。

<bean id="..." class="...">
    <property name="age">
        <bean id="person.age"
                class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
    </property>
</bean>

実際の定義で結果型を明確に設定できます。これはほとんどのユースケースでは必要ありませんが、役に立つ場合があります。この機能の詳細については、javadoc を参照してください。

<util:properties/> を使用する

次の例を考えてみましょう。

<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="location" value="classpath:com/foo/jdbc-production.properties"/>
</bean>

上記の構成では、Spring FactoryBean 実装(PropertiesFactoryBean)を使用して、指定された Resource ロケーションからロードされた値を使用して java.util.Properties インスタンスをインスタンス化します。

次の例では、util:properties 要素を使用して、より簡潔な表現を作成しています。

<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
<util:list/> を使用する

次の例を考えてみましょう。

<!-- creates a java.util.List instance with values loaded from the supplied 'sourceList' -->
<bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean">
    <property name="sourceList">
        <list>
            <value>[email protected] (英語)  </value>
            <value>[email protected] (英語)  </value>
            <value>[email protected] (英語)  </value>
            <value>[email protected] (英語)  </value>
        </list>
    </property>
</bean>

上記の構成では、Spring FactoryBean 実装(ListFactoryBean)を使用して java.util.List インスタンスを作成し、指定された sourceList から取得した値で初期化します。

次の例では、<util:list/> 要素を使用して、より簡潔な表現を作成しています。

<!-- creates a java.util.List instance with the supplied values -->
<util:list id="emails">
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
</util:list>

また、<util:list/> エレメントの list-class 属性を使用して、インスタンス化および移植される List の正確な型を明示的に制御できます。例: java.util.LinkedList をインスタンス化する必要がある場合、次の構成を使用できます。

<util:list id="emails" list-class="java.util.LinkedList">
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
    <value>d'[email protected] (英語)  </value>
</util:list>

list-class 属性が指定されていない場合、コンテナーは List 実装を選択します。

<util:map/> を使用する

次の例を考えてみましょう。

<!-- creates a java.util.Map instance with values loaded from the supplied 'sourceMap' -->
<bean id="emails" class="org.springframework.beans.factory.config.MapFactoryBean">
    <property name="sourceMap">
        <map>
            <entry key="pechorin" value="[email protected] (英語)  "/>
            <entry key="raskolnikov" value="[email protected] (英語)  "/>
            <entry key="stavrogin" value="[email protected] (英語)  "/>
            <entry key="porfiry" value="[email protected] (英語)  "/>
        </map>
    </property>
</bean>

上記の構成では、Spring FactoryBean 実装(MapFactoryBean)を使用して、提供された 'sourceMap' から取得したキーと値のペアで初期化された java.util.Map インスタンスを作成します。

次の例では、<util:map/> 要素を使用して、より簡潔な表現を作成しています。

<!-- creates a java.util.Map instance with the supplied key-value pairs -->
<util:map id="emails">
    <entry key="pechorin" value="[email protected] (英語)  "/>
    <entry key="raskolnikov" value="[email protected] (英語)  "/>
    <entry key="stavrogin" value="[email protected] (英語)  "/>
    <entry key="porfiry" value="[email protected] (英語)  "/>
</util:map>

また、<util:map/> エレメントの 'map-class' 属性を使用して、インスタンス化および移植される Map の正確な型を明示的に制御できます。例: java.util.TreeMap をインスタンス化する必要がある場合、次の構成を使用できます。

<util:map id="emails" map-class="java.util.TreeMap">
    <entry key="pechorin" value="[email protected] (英語)  "/>
    <entry key="raskolnikov" value="[email protected] (英語)  "/>
    <entry key="stavrogin" value="[email protected] (英語)  "/>
    <entry key="porfiry" value="[email protected] (英語)  "/>
</util:map>

'map-class' 属性が指定されていない場合、コンテナーは Map 実装を選択します。

<util:set/> を使用する

次の例を考えてみましょう。

<!-- creates a java.util.Set instance with values loaded from the supplied 'sourceSet' -->
<bean id="emails" class="org.springframework.beans.factory.config.SetFactoryBean">
    <property name="sourceSet">
        <set>
            <value>[email protected] (英語)  </value>
            <value>[email protected] (英語)  </value>
            <value>[email protected] (英語)  </value>
            <value>[email protected] (英語)  </value>
        </set>
    </property>
</bean>

上記の構成では、Spring FactoryBean 実装(SetFactoryBean)を使用して、提供された sourceSet から取得した値で初期化された java.util.Set インスタンスを作成します。

次の例では、<util:set/> 要素を使用して、より簡潔な表現を作成しています。

<!-- creates a java.util.Set instance with the supplied values -->
<util:set id="emails">
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
</util:set>

また、<util:set/> エレメントの set-class 属性を使用して、インスタンス化および移植される Set の正確な型を明示的に制御できます。例: java.util.TreeSet をインスタンス化する必要がある場合、次の構成を使用できます。

<util:set id="emails" set-class="java.util.TreeSet">
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
    <value>[email protected] (英語)  </value>
</util:set>

set-class 属性が指定されていない場合、コンテナーは Set 実装を選択します。

10.1.2. aop スキーマ

aop タグは、Spring 独自のプロキシベースの AOP フレームワークや、AspectJ AOP フレームワークとの Spring の統合など、Spring でのすべての AOP の構成を処理します。これらのタグは、Spring によるアスペクト指向プログラミングというタイトルの章で包括的にカバーされています。

完全を期すために、aop スキーマのタグを使用するには、Spring XML 構成ファイルの先頭に次のプリアンブルが必要です(スニペットのテキストは正しいスキーマを参照するため、aop 名前空間のタグはあなたに利用可能):

<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- bean definitions here -->

</beans>

10.1.3. context スキーマ

context タグは、接続機能に関連する ApplicationContext 構成を処理します。つまり、通常、エンドユーザーにとって重要な Bean ではなく、BeanfactoryPostProcessors などの Spring で多くの「うなり」をする Bean です。次のスニペットは、context 名前空間の要素が利用できるように正しいスキーマを参照しています。

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- bean definitions here -->

</beans>
<property-placeholder/> を使用する

この要素は、${…​} プレースホルダーの置換をアクティブにします。これは、指定されたプロパティファイルに対して解決されます(Spring リソースの場所として)。この要素は、PropertySourcesPlaceholderConfigurer をセットアップする便利なメカニズムです。特定の PropertySourcesPlaceholderConfigurer セットアップをさらに制御する必要がある場合は、自分で Bean として明示的に定義できます。

<annotation-config/> を使用する

この要素は、Spring インフラストラクチャをアクティブにして、Bean クラスのアノテーションを検出します。

  • Spring の @Configuration モデル

  • @Autowired/@Inject@Value@Lookup

  • JSR-250 の @Resource@PostConstruct@PreDestroy (使用可能な場合)

  • JAX-WS の @WebServiceRef および EJB3 の @EJB (使用可能な場合)

  • JPA の @PersistenceContext および @PersistenceUnit (使用可能な場合)

  • Spring の @EventListener

または、これらのアノテーションに対して個々の BeanPostProcessors を明示的にアクティブにすることを選択できます。

この要素は、Spring の @Transactional アノテーションの処理をアクティブにしません。そのために <tx:annotation-driven/> 要素を使用できます。同様に、Spring のキャッシュアノテーションも明示的に有効にする必要があります。
<component-scan/> を使用する

この要素の詳細については、アノテーションベースのコンテナー設定に関するセクションを参照してください

<load-time-weaver/> を使用する

この要素については、Spring Framework の AspectJ を使用したロード時ウィービングのセクションで詳しく説明します。

<spring-configured/> を使用する
<mbean-export/> を使用する

この要素の詳細については、アノテーションベースの MBean エクスポートの構成に関するセクションを参照してください。

10.1.4. Beans スキーマ

最後になりましたが、beans スキーマには要素があります。これらの要素は、フレームワークの very 明期から Spring にありました。beans スキーマのさまざまな要素の例は、依存関係と構成が詳細に (そして実際、その全体で)非常に包括的にカバーされているため、ここでは示していません。

<bean/> XML 定義にゼロ以上のキーと値のペアを追加できることに注意してください。この追加のメタデータを使用して何が行われるかは、完全に独自のカスタムロジック次第です(したがって、XML スキーマオーサリングというタイトルの付録に従って独自のカスタム要素を記述する場合にのみ通常使用されます)。

次の例は、周囲の <bean/> のコンテキストで <meta/> 要素を示しています(それを解釈するためのロジックがなければ、メタデータは事実上役に立たないことに注意してください)。

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

    <bean id="foo" class="x.y.Foo">
        <meta key="cacheName" value="foo"/> (1)
        <property name="name" value="Rick"/>
    </bean>

</beans>
1 これは meta 要素の例です

上記の例の場合、Bean 定義を消費し、提供されたメタデータを使用するキャッシュインフラストラクチャを設定するロジックがあると想定できます。

10.2. XML スキーマオーサリング

バージョン 2.0 以降、Spring は、Bean を定義および構成するための基本的な Spring XML 形式にスキーマベースの拡張機能を追加するメカニズムを備えています。このセクションでは、独自のカスタム XML Bean 定義パーサーを作成し、そのようなパーサーを Spring IoC コンテナーに統合する方法について説明します。

スキーマ対応の XML エディターを使用する構成ファイルの作成を容易にするために、Spring の拡張可能な XML 構成メカニズムは XML スキーマに基づいています。標準の Spring ディストリビューションに付属する Spring の現在の XML 構成拡張機能に慣れていない場合は、最初に XML スキーマの前のセクションを読む必要があります。

新しい XML 構成拡張機能を作成するには:

  1. 作成者カスタム要素を記述する XML スキーマ。

  2. コードカスタム NamespaceHandler 実装。

  3. コード 1 つ以上の BeanDefinitionParser 実装(これは実際の作業が行われる場所です)。

  4. Spring を使用して、新しい成果物を登録

統一された例として、型 SimpleDateFormat のオブジェクト(java.text パッケージから)を構成できる XML 拡張機能(カスタム XML 要素)を作成します。完了したら、次のように型 SimpleDateFormat の Bean 定義を定義できます。

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

(この付録の後の方で、さらに詳細な例を示します。この最初の簡単な例の目的は、カスタム拡張機能を作成する基本的な手順を説明することです)

10.2.1. スキーマの作成

Spring の IoC コンテナーで使用する XML 構成拡張機能の作成は、拡張機能を記述する XML スキーマの作成から始まります。この例では、次のスキーマを使用して SimpleDateFormat オブジェクトを構成します。

<!-- myns.xsd (inside package org/springframework/samples/xml) -->

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:beans="http://www.springframework.org/schema/beans"
        targetNamespace="http://www.mycompany.example/schema/myns"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">

    <xsd:import namespace="http://www.springframework.org/schema/beans"/>

    <xsd:element name="dateformat">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType"> (1)
                    <xsd:attribute name="lenient" type="xsd:boolean"/>
                    <xsd:attribute name="pattern" type="xsd:string" use="required"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>
1 示された行には、識別可能なすべてのタグの拡張ベースが含まれています(コンテナー内で Bean 識別子として使用できる id 属性があることを意味します)。Spring 提供の beans 名前空間をインポートしたため、この属性を使用できます。

上記のスキーマを使用すると、次の例に示すように、<myns:dateformat/> 要素を使用して、XML アプリケーションコンテキストファイルで SimpleDateFormat オブジェクトを直接構成できます。

<myns:dateformat id="dateFormat"
    pattern="yyyy-MM-dd HH:mm"
    lenient="true"/>

インフラストラクチャクラスを作成した後、前述の XML スニペットは、基本的に次の XML スニペットと同じであることに注意してください。

<bean id="dateFormat" class="java.text.SimpleDateFormat">
    <constructor-arg value="yyyy-HH-dd HH:mm"/>
    <property name="lenient" value="true"/>
</bean>

上記の 2 つのスニペットの 2 番目は、コンテナーに Bean(型 SimpleDateFormat の名前 dateFormat で識別される)を作成し、いくつかのプロパティを設定します。

構成フォーマットを作成するためのスキーマベースのアプローチにより、スキーマ対応の XML エディターを備えた IDE との緊密な統合が可能になります。適切に作成されたスキーマを使用することにより、自動補完を使用して、ユーザーが列挙で定義されたいくつかの構成オプションから選択できるようにすることができます。

10.2.2. NamespaceHandler のコーディング

スキーマに加えて、Spring が構成ファイルの解析中に遭遇するこの特定の名前空間のすべての要素を解析するために、NamespaceHandler が必要です。この例では、NamespaceHandler は myns:dateformat 要素の解析を処理する必要があります。

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

  • init()NamespaceHandler の初期化を許可し、ハンドラーが使用される前に Spring によって呼び出されます。

  • BeanDefinition parse(Element, ParserContext): Spring が最上位要素(Bean 定義または別のネームスペース内にネストされていない)に遭遇したときに呼び出されます。このメソッド自体は、Bean 定義を登録するか、Bean 定義を返すか、その両方を行うことができます。

  • BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext): Spring が別の名前空間の属性またはネストされた要素に遭遇したときに呼び出されます。1 つ以上の Bean 定義の装飾は、(たとえば) Spring がサポートするスコープで使用されます。まず、装飾を使用しない単純な例を強調し、その後、もう少し高度な例で装飾を示します。

名前空間全体に対して独自の NamespaceHandler をコーディングできます(したがって、名前空間内のすべての要素を解析するコードを提供できます)が、Spring XML 構成ファイルの各最上位 XML 要素が単一の Bean になる場合がよくあります。定義(この例では、単一の <myns:dateformat/> 要素が単一の SimpleDateFormat Bean 定義になります)。Spring は、このシナリオをサポートする多くの便利なクラスを備えています。次の例では、NamespaceHandlerSupport クラスを使用します。

Java
package org.springframework.samples.xml;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class MyNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
    }
}
Kotlin
package org.springframework.samples.xml

import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class MyNamespaceHandler : NamespaceHandlerSupport {

    override fun init() {
        registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser())
    }
}

このクラスには実際には多くの構文解析ロジックがないことに気付くかもしれません。実際、NamespaceHandlerSupport クラスには委譲の概念が組み込まれています。任意の数の BeanDefinitionParser インスタンスの登録をサポートします。BeanDefinitionParser インスタンスは、ネームスペースの要素を解析する必要があるときに委譲されます。このように関心事を明確に分離することにより、NamespaceHandler は、ネームスペース内のすべてのカスタム要素の解析のオーケストレーションを処理しながら、BeanDefinitionParsers に委譲して XML 解析の面倒な作業を行うことができます。これは、次のステップでわかるように、各 BeanDefinitionParser には単一のカスタム要素を解析するためのロジックのみが含まれることを意味します。

10.2.3. BeanDefinitionParser を使用する

NamespaceHandler が特定の Bean 定義パーサー(この場合は dateformat)にマップされた型の XML エレメントを検出すると、BeanDefinitionParser が使用されます。言い換えれば、BeanDefinitionParser は、スキーマで定義された 1 つの異なる最上位 XML 要素を解析するロールを果たします。パーサーでは、XML 要素(およびそのサブ要素)にアクセスできるため、次の例に示すように、カスタム XML コンテンツを解析できます。

Java
package org.springframework.samples.xml;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

import java.text.SimpleDateFormat;

public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { (1)

    protected Class getBeanClass(Element element) {
        return SimpleDateFormat.class; (2)
    }

    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        // this will never be null since the schema explicitly requires that a value be supplied
        String pattern = element.getAttribute("pattern");
        bean.addConstructorArgValue(pattern);

        // this however is an optional property
        String lenient = element.getAttribute("lenient");
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
        }
    }

}
1Spring が提供する AbstractSingleBeanDefinitionParser を使用して、単一の BeanDefinition を作成する多くの基本的な単調な作業を処理します。
2AbstractSingleBeanDefinitionParser スーパークラスには、単一の BeanDefinition が表す型を提供します。
Kotlin
package org.springframework.samples.xml

import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
import org.springframework.util.StringUtils
import org.w3c.dom.Element

import java.text.SimpleDateFormat

class SimpleDateFormatBeanDefinitionParser : AbstractSingleBeanDefinitionParser() { (1)

    override fun getBeanClass(element: Element): Class<*>? { (2)
        return SimpleDateFormat::class.java
    }

    override fun doParse(element: Element, bean: BeanDefinitionBuilder) {
        // this will never be null since the schema explicitly requires that a value be supplied
        val pattern = element.getAttribute("pattern")
        bean.addConstructorArgValue(pattern)

        // this however is an optional property
        val lenient = element.getAttribute("lenient")
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient))
        }
    }
}
1Spring が提供する AbstractSingleBeanDefinitionParser を使用して、単一の BeanDefinition を作成する多くの基本的な単調な作業を処理します。
2AbstractSingleBeanDefinitionParser スーパークラスには、単一の BeanDefinition が表す型を提供します。

この単純なケースでは、これが必要なすべてです。単一の BeanDefinition の作成は、Bean 定義の一意の識別子の抽出および設定と同様に、AbstractSingleBeanDefinitionParser スーパークラスによって処理されます。

10.2.4. ハンドラーとスキーマの登録

コーディングが終了しました。あとは、Spring XML 解析インフラストラクチャにカスタム要素を認識させるだけです。これを行うには、カスタム namespaceHandler とカスタム XSD ファイルを 2 つの特別な目的のプロパティファイルに登録します。これらのプロパティファイルは両方とも、アプリケーションの META-INF ディレクトリに配置され、たとえば、JAR ファイルのバイナリクラスと一緒に配布できます。Spring XML 解析インフラストラクチャは、これらの特別なプロパティファイルを使用して新しい拡張機能を自動的に選択します。その形式については、次の 2 つのセクションで詳しく説明します。

META-INF/spring.handlers の作成

spring.handlers というプロパティファイルには、名前空間ハンドラークラスへの XML スキーマ URI のマッピングが含まれています。この例では、次を記述する必要があります。

http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler

: 文字は Java プロパティ形式の有効な区切り文字であるため、URI の : 文字はバックスラッシュでエスケープする必要があります)

キーと値のペアの最初の部分(キー)は、カスタム名前空間拡張に関連付けられた URI であり、カスタム XSD スキーマで指定されている targetNamespace 属性の値と正確に一致する必要があります。

"META-INF/spring.schemas" の作成

spring.schemas と呼ばれるプロパティファイルには、XML スキーマの場所(スキーマ宣言とともに、xsi:schemaLocation 属性の一部としてスキーマを使用する XML ファイルで参照される)のクラスパスリソースへのマッピングが含まれています。このファイルは、Spring がスキーマファイルを取得するためにインターネットアクセスを必要とするデフォルトの EntityResolver を絶対に使用する必要がないようにするために必要です。このプロパティファイルでマッピングを指定すると、Spring はクラスパスでスキーマ(この場合は org.springframework.samples.xml パッケージの myns.xsd)を検索します。次のスニペットは、カスタムスキーマに追加する必要がある行を示しています。

http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd

: 文字はエスケープする必要があることに注意してください)

クラスパス上の NamespaceHandler クラスと BeanDefinitionParser クラスのすぐ横に XSD ファイルをデプロイすることをお勧めします。

10.2.5. Spring XML 構成でのカスタム拡張機能の使用

自分で実装したカスタム拡張機能を使用することは、Spring が提供する「カスタム」拡張機能の 1 つを使用することと同じです。次の例では、Spring XML 構成ファイルの前の手順で開発されたカスタム <dateformat/> 要素を使用しています。

<?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:myns="http://www.mycompany.example/schema/myns"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">

    <!-- as a top-level bean -->
    <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/> (1)

    <bean id="jobDetailTemplate" abstract="true">
        <property name="dateFormat">
            <!-- as an inner bean -->
            <myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
        </property>
    </bean>

</beans>
1 カスタム Bean。

10.2.6. より詳細な例

このセクションでは、カスタム XML 拡張機能のより詳細な例を示します。

カスタム要素内のカスタム要素のネスト

このセクションで示す例は、次の構成のターゲットを満たすために必要なさまざまなアーティファクトを作成する方法を示しています。

<?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:foo="http://www.foo.example/schema/component"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">

    <foo:component id="bionic-family" name="Bionic-1">
        <foo:component name="Mother-1">
            <foo:component name="Karate-1"/>
            <foo:component name="Sport-1"/>
        </foo:component>
        <foo:component name="Rock-1"/>
    </foo:component>

</beans>

上記の構成では、カスタム拡張機能が相互にネストされています。<foo:component/> 要素によって実際に設定されるクラスは、Component クラスです(次の例に示す)。Component クラスが components プロパティの setter メソッドを公開しないことに注意してください。これにより、setter インジェクションを使用して Component クラスの Bean 定義を構成することが困難になります(むしろ不可能になります)。次のリストは、Component クラスを示しています。

Java
package com.foo;

import java.util.ArrayList;
import java.util.List;

public class Component {

    private String name;
    private List<Component> components = new ArrayList<Component> ();

    // mmm, there is no setter method for the 'components'
    public void addComponent(Component component) {
        this.components.add(component);
    }

    public List<Component> getComponents() {
        return components;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
Kotlin
package com.foo

import java.util.ArrayList

class Component {

    var name: String? = null
    private val components = ArrayList<Component>()

    // mmm, there is no setter method for the 'components'
    fun addComponent(component: Component) {
        this.components.add(component)
    }

    fun getComponents(): List<Component> {
        return components
    }
}

この課題の一般的な解決策は、components プロパティの setter プロパティを公開するカスタム FactoryBean を作成することです。次のリストは、そのようなカスタム FactoryBean を示しています。

Java
package com.foo;

import org.springframework.beans.factory.FactoryBean;

import java.util.List;

public class ComponentFactoryBean implements FactoryBean<Component> {

    private Component parent;
    private List<Component> children;

    public void setParent(Component parent) {
        this.parent = parent;
    }

    public void setChildren(List<Component> children) {
        this.children = children;
    }

    public Component getObject() throws Exception {
        if (this.children != null && this.children.size() > 0) {
            for (Component child : children) {
                this.parent.addComponent(child);
            }
        }
        return this.parent;
    }

    public Class<Component> getObjectType() {
        return Component.class;
    }

    public boolean isSingleton() {
        return true;
    }
}
Kotlin
package com.foo

import org.springframework.beans.factory.FactoryBean
import org.springframework.stereotype.Component

class ComponentFactoryBean : FactoryBean<Component> {

    private var parent: Component? = null
    private var children: List<Component>? = null

    fun setParent(parent: Component) {
        this.parent = parent
    }

    fun setChildren(children: List<Component>) {
        this.children = children
    }

    override fun getObject(): Component? {
        if (this.children != null && this.children!!.isNotEmpty()) {
            for (child in children!!) {
                this.parent!!.addComponent(child)
            }
        }
        return this.parent
    }

    override fun getObjectType(): Class<Component>? {
        return Component::class.java
    }

    override fun isSingleton(): Boolean {
        return true
    }
}

これはうまく機能しますが、エンドユーザーに多くの Spring 接続機能を公開します。やろうとしているのは、この Spring 接続機能のすべてを隠すカスタム拡張を書くことです。前に説明した手順に固執する場合は、次のように、カスタムタグの構造を定義する XSD スキーマを作成することから始めます。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.foo.example/schema/component"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/schema/component"
        elementFormDefault="qualified"
        attributeFormDefault="unqualified">

    <xsd:element name="component">
        <xsd:complexType>
            <xsd:choice minOccurs="0" maxOccurs="unbounded">
                <xsd:element ref="component"/>
            </xsd:choice>
            <xsd:attribute name="id" type="xsd:ID"/>
            <xsd:attribute name="name" use="required" type="xsd:string"/>
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

前述のプロセスに従って、カスタム NamespaceHandler を作成します。

Java
package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class ComponentNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
    }
}
Kotlin
package com.foo

import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class ComponentNamespaceHandler : NamespaceHandlerSupport() {

    override fun init() {
        registerBeanDefinitionParser("component", ComponentBeanDefinitionParser())
    }
}

次はカスタム BeanDefinitionParser です。ComponentFactoryBean を記述する BeanDefinition を作成していることに注意してください。次のリストは、カスタム BeanDefinitionParser 実装を示しています。

Java
package com.foo;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import java.util.List;

public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {

    protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        return parseComponentElement(element);
    }

    private static AbstractBeanDefinition parseComponentElement(Element element) {
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
        factory.addPropertyValue("parent", parseComponent(element));

        List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
        if (childElements != null && childElements.size() > 0) {
            parseChildComponents(childElements, factory);
        }

        return factory.getBeanDefinition();
    }

    private static BeanDefinition parseComponent(Element element) {
        BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
        component.addPropertyValue("name", element.getAttribute("name"));
        return component.getBeanDefinition();
    }

    private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
        ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size());
        for (Element element : childElements) {
            children.add(parseComponentElement(element));
        }
        factory.addPropertyValue("children", children);
    }
}
Kotlin
package com.foo

import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.support.ManagedList
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser
import org.springframework.beans.factory.xml.ParserContext
import org.springframework.util.xml.DomUtils
import org.w3c.dom.Element

import java.util.List

class ComponentBeanDefinitionParser : AbstractBeanDefinitionParser() {

    override fun parseInternal(element: Element, parserContext: ParserContext): AbstractBeanDefinition? {
        return parseComponentElement(element)
    }

    private fun parseComponentElement(element: Element): AbstractBeanDefinition {
        val factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean::class.java)
        factory.addPropertyValue("parent", parseComponent(element))

        val childElements = DomUtils.getChildElementsByTagName(element, "component")
        if (childElements != null && childElements.size > 0) {
            parseChildComponents(childElements, factory)
        }

        return factory.getBeanDefinition()
    }

    private fun parseComponent(element: Element): BeanDefinition {
        val component = BeanDefinitionBuilder.rootBeanDefinition(Component::class.java)
        component.addPropertyValue("name", element.getAttribute("name"))
        return component.beanDefinition
    }

    private fun parseChildComponents(childElements: List<Element>, factory: BeanDefinitionBuilder) {
        val children = ManagedList<BeanDefinition>(childElements.size)
        for (element in childElements) {
            children.add(parseComponentElement(element))
        }
        factory.addPropertyValue("children", children)
    }
}

最後に、META-INF/spring.handlers および META-INF/spring.schemas ファイルを次のように変更して、さまざまなアーティファクトを Spring XML インフラストラクチャに登録する必要があります。

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
「通常の」要素のカスタム属性

独自のカスタムパーサーと関連するアーティファクトを作成するのは難しくありません。ただし、それが正しいことではない場合もあります。既存の Bean 定義にメタデータを追加する必要があるシナリオを考えてみましょう。この場合、独自のカスタム拡張機能全体を作成する必要はありません。むしろ、既存の Bean 定義要素に属性を追加するだけです。

別の例として、クラスター化された JCache (英語) にアクセスする(不明な)サービスオブジェクトの Bean 定義を定義し、指定された JCache インスタンスが周囲のクラスター内で確実に開始されるようにしたいとします。次のリストは、そのような定義を示しています。

<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
        jcache:cache-name="checking.account">
    <!-- other dependencies here... -->
</bean>

'jcache:cache-name' 属性が解析されると、別の BeanDefinition を作成できます。この BeanDefinition は、指定された JCache を初期化します。また、'checkingAccountService' の既存の BeanDefinition を変更して、この新しい JCache 初期化 BeanDefinition に依存するようにすることもできます。次のリストは、JCacheInitializer を示しています。

Java
package com.foo;

public class JCacheInitializer {

    private String name;

    public JCacheInitializer(String name) {
        this.name = name;
    }

    public void initialize() {
        // lots of JCache API calls to initialize the named cache...
    }
}
Kotlin
package com.foo

class JCacheInitializer(private val name: String) {

    fun initialize() {
        // lots of JCache API calls to initialize the named cache...
    }
}

これで、カスタム拡張機能に移動できます。まず、次のように、カスタム属性を記述する XSD スキーマを作成する必要があります。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://www.foo.example/schema/jcache"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.foo.example/schema/jcache"
        elementFormDefault="qualified">

    <xsd:attribute name="cache-name" type="xsd:string"/>

</xsd:schema>

次に、次のように、関連する NamespaceHandler を作成する必要があります。

Java
package com.foo;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class JCacheNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        super.registerBeanDefinitionDecoratorForAttribute("cache-name",
            new JCacheInitializingBeanDefinitionDecorator());
    }

}
Kotlin
package com.foo

import org.springframework.beans.factory.xml.NamespaceHandlerSupport

class JCacheNamespaceHandler : NamespaceHandlerSupport() {

    override fun init() {
        super.registerBeanDefinitionDecoratorForAttribute("cache-name",
                JCacheInitializingBeanDefinitionDecorator())
    }

}

次に、パーサーを作成する必要があります。この場合、XML 属性を解析するため、BeanDefinitionParser ではなく BeanDefinitionDecorator を記述することに注意してください。以下のリストは、BeanDefinitionDecorator の実装を示しています。

Java
package com.foo;

import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {

    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
            ParserContext ctx) {
        String initializerBeanName = registerJCacheInitializer(source, ctx);
        createDependencyOnJCacheInitializer(holder, initializerBeanName);
        return holder;
    }

    private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
            String initializerBeanName) {
        AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
        String[] dependsOn = definition.getDependsOn();
        if (dependsOn == null) {
            dependsOn = new String[]{initializerBeanName};
        } else {
            List dependencies = new ArrayList(Arrays.asList(dependsOn));
            dependencies.add(initializerBeanName);
            dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
        }
        definition.setDependsOn(dependsOn);
    }

    private String registerJCacheInitializer(Node source, ParserContext ctx) {
        String cacheName = ((Attr) source).getValue();
        String beanName = cacheName + "-initializer";
        if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
            BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
            initializer.addConstructorArg(cacheName);
            ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
        }
        return beanName;
    }
}
Kotlin
package com.foo

import org.springframework.beans.factory.config.BeanDefinitionHolder
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.BeanDefinitionDecorator
import org.springframework.beans.factory.xml.ParserContext
import org.w3c.dom.Attr
import org.w3c.dom.Node

import java.util.ArrayList

class JCacheInitializingBeanDefinitionDecorator : BeanDefinitionDecorator {

    override fun decorate(source: Node, holder: BeanDefinitionHolder,
                        ctx: ParserContext): BeanDefinitionHolder {
        val initializerBeanName = registerJCacheInitializer(source, ctx)
        createDependencyOnJCacheInitializer(holder, initializerBeanName)
        return holder
    }

    private fun createDependencyOnJCacheInitializer(holder: BeanDefinitionHolder,
                                                    initializerBeanName: String) {
        val definition = holder.beanDefinition as AbstractBeanDefinition
        var dependsOn = definition.dependsOn
        dependsOn = if (dependsOn == null) {
            arrayOf(initializerBeanName)
        } else {
            val dependencies = ArrayList(listOf(*dependsOn))
            dependencies.add(initializerBeanName)
            dependencies.toTypedArray()
        }
        definition.setDependsOn(*dependsOn)
    }

    private fun registerJCacheInitializer(source: Node, ctx: ParserContext): String {
        val cacheName = (source as Attr).value
        val beanName = "$cacheName-initializer"
        if (!ctx.registry.containsBeanDefinition(beanName)) {
            val initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer::class.java)
            initializer.addConstructorArg(cacheName)
            ctx.registry.registerBeanDefinition(beanName, initializer.getBeanDefinition())
        }
        return beanName
    }
}

最後に、次のように META-INF/spring.handlers および META-INF/spring.schemas ファイルを変更して、さまざまなアーティファクトを Spring XML インフラストラクチャに登録する必要があります。

# in 'META-INF/spring.handlers'
http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
# in 'META-INF/spring.schemas'
http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd

10.3. アプリケーションの起動手順

付録のこのパートには、コアコンテナーに装備されている既存の StartupSteps がリストされています。

各スタートアップステップの名前と詳細情報は公開契約の一部ではなく、変更される可能性があります。これはコアコンテナーの実装の詳細と見なされ、動作の変更に従います。
表 15: コアコンテナーで定義されたアプリケーションの起動手順
名前 説明 タグ

spring.beans.instantiate

Bean とその依存関係のインスタンス化。

beanName the name of the bean, beanType the type required at the injection point.

spring.beans.smart-initialize

SmartInitializingSingleton Bean の初期化。

beanName the name of the bean.

spring.context.annotated-bean-reader.create

AnnotatedBeanDefinitionReader の作成。

spring.context.base-packages.scan

基本パッケージのスキャン。

packages array of base packages for scanning.

spring.context.beans.post-process

Beans の後処理フェーズ。

spring.context.bean-factory.post-process

BeanFactoryPostProcessor Bean の呼び出し。

postProcessor the current post-processor.

spring.context.beandef-registry.post-process

BeanDefinitionRegistryPostProcessor Bean の呼び出し。

postProcessor the current post-processor.

spring.context.component-classes.register

AnnotationConfigApplicationContext#register を介したコンポーネントクラスの登録。

classes array of given classes for registration.

spring.context.config-classes.enhance

CGLIB プロキシを使用した構成クラスの拡張。

classCount count of enhanced classes.

spring.context.config-classes.parse

ConfigurationClassPostProcessor を使用した構成クラスの解析フェーズ。

classCount count of processed classes.

spring.context.refresh

アプリケーションコンテキストのリフレッシュフェーズ。