リファレンスドキュメントのこのパートでは、Spring Framework と多くの Java EE(および関連する)テクノロジーとの統合について説明しています。

1. リモート処理と Web サービス

Spring は、さまざまなテクノロジーによるリモート処理のサポートを提供します。リモーティングのサポートにより、Java インターフェースおよびオブジェクトを入出力として実装したリモート対応サービスの開発が容易になります。現在、Spring は次のリモートテクノロジーをサポートしています。

  • リモートメソッドの呼び出し (RMI)RmiProxyFactoryBean および RmiServiceExporter を使用することにより、Spring は従来の RMI(java.rmi.Remote インターフェースおよび java.rmi.RemoteException を使用)と RMI インボーカー(Java インターフェースを使用)による透過的なリモーティングの両方をサポートします。

  • Spring HTTP インボーカー : Spring は、HTTP を介した Java 直列化を可能にする特別なリモーティング戦略を提供し、(RMI 呼び出し側が行うように)Java インターフェースをサポートします。対応するサポートクラスは HttpInvokerProxyFactoryBean および HttpInvokerServiceExporter です。

  • Hessian : Spring の HessianProxyFactoryBean と HessianServiceExporter を使用することにより、Caucho が提供する軽量のバイナリ HTTP ベースのプロトコルを介してサービスを透過的に公開できます。

  • Java Web サービス : Spring は、JAX-WS を介して Web サービスのリモートサポートを提供します。

  • JMS : 基になるプロトコルとしての JMS を介したリモート処理は、spring-jms モジュールの JmsInvokerServiceExporter および JmsInvokerProxyFactoryBean クラスを通じてサポートされます。

  • AMQP : 基盤となるプロトコルとしての AMQP を介したリモート処理は、別の Spring AMQP プロジェクトによってサポートされています。

Spring のリモート機能について説明する際に、次のドメインモデルと対応するサービスを使用します。

public class Account implements Serializable{

    private String name;

    public String getName(){
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public interface AccountService {

    public void insertAccount(Account account);

    public List<Account> getAccounts(String name);
}
// the implementation doing nothing at the moment
public class AccountServiceImpl implements AccountService {

    public void insertAccount(Account acc) {
        // do something...
    }

    public List<Account> getAccounts(String name) {
        // do something...
    }
}

このセクションでは、RMI を使用してリモートクライアントにサービスを公開し、RMI を使用する場合の欠点について少し説明します。次に、Hessian をプロトコルとして使用する例を続けます。

1.1. RMI

Spring の RMI のサポートを使用すると、RMI インフラストラクチャを通じてサービスを透過的に公開できます。これをセットアップすると、基本的にリモート EJB に似た構成になりますが、セキュリティコンテキスト伝播またはリモートトランザクション伝播の標準サポートがないという事実を除きます。Spring は、RMI インボーカーを使用する場合、このような追加の呼び出しコンテキストのフックを提供するため、たとえば、セキュリティフレームワークまたはカスタムセキュリティ資格情報をプラグインできます。

1.1.1. RmiServiceExporter を使用したサービスのエクスポート

RmiServiceExporter を使用して、AccountService オブジェクトのインターフェースを RMI オブジェクトとして公開できます。RmiProxyFactoryBean を使用して、または従来の RMI サービスの場合はプレーン RMI を介して、インターフェースにアクセスできます。RmiServiceExporter は、RMI インボーカーを介した非 RMI サービスの公開を明示的にサポートします。

まず、Spring コンテナーでサービスをセットアップする必要があります。次の例は、その方法を示しています。

<bean id="accountService" class="example.AccountServiceImpl">
    <!-- any additional properties, maybe a DAO? -->
</bean>

次に、RmiServiceExporter を使用してサービスを公開する必要があります。次の例は、その方法を示しています。

<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
    <!-- does not necessarily have to be the same name as the bean to be exported -->
    <property name="serviceName" value="AccountService"/>
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
    <!-- defaults to 1099 -->
    <property name="registryPort" value="1199"/>
</bean>

前の例では、RMI レジストリのポートをオーバーライドします。多くの場合、アプリケーションサーバーは RMI レジストリも保持しますが、これに干渉しないことが賢明です。さらに、サービス名はサービスをバインドするために使用されます。前述の例では、サービスは 'rmi://HOST:1199/AccountService' でバインドされています。後でこの URL を使用して、クライアント側のサービスにリンクします。

servicePort プロパティは省略されています(デフォルトは 0)。これは、サービスとの通信に匿名ポートが使用されることを意味します。

1.1.2. クライアントでのサービスのリンク

次の例に示すように、クライアントは AccountService を使用してアカウントを管理する単純なオブジェクトです。

public class SimpleObject {

    private AccountService accountService;

    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }

    // additional methods using the accountService
}

クライアントのサービスにリンクするには、別の Spring コンテナーを作成し、次の単純なオブジェクトとサービスリンク構成ビットを含めます。

<bean class="example.SimpleObject">
    <property name="accountService" ref="accountService"/>
</bean>

<bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
    <property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

クライアントでリモートアカウントサービスをサポートするために必要なことはそれだけです。Spring は、呼び出し側を透過的に作成し、RmiServiceExporter を介してリモートでアカウントサービスを有効にします。クライアントでは、RmiProxyFactoryBean を使用してリンクします。

1.2. Hessian を使用して、HTTP を介してリモートでサービスを呼び出す

Hessian は、バイナリ HTTP ベースのリモートプロトコルを提供します。Caucho によって開発されており、https://www.caucho.com/ (英語) で Hessian 自体に関する詳細情報を見つけることができます。

1.2.1. Hessian

Hessian は HTTP を介して通信し、カスタムサーブレットを使用して通信します。Spring の DispatcherServlet 原則(webmvc.html を参照)を使用することで、このようなサーブレットを接続してサービスを公開できます。まず、次の web.xml からの抜粋に示すように、アプリケーションに新しいサーブレットを作成する必要があります。

<servlet>
    <servlet-name>remoting</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>remoting</servlet-name>
    <url-pattern>/remoting/*</url-pattern>
</servlet-mapping>

Spring の DispatcherServlet の原則に精通している場合は、WEB-INF ディレクトリに remoting-servlet.xml という名前の Spring コンテナー構成リソース(サーブレットの名前に続く)を作成する必要があることをご存じでしょう。アプリケーションコンテキストは次のセクションで使用されます。

または、Spring のよりシンプルな HttpRequestHandlerServlet の使用を検討してください。これにより、ルートアプリケーションコンテキスト(デフォルトでは WEB-INF/applicationContext.xml)にリモートエクスポーター定義を埋め込み、個々のサーブレット定義が特定のエクスポーター Bean を指すようにすることができます。この場合、各サーブレット名は、ターゲットエクスポーターの Bean 名と一致する必要があります。

1.2.2. HessianServiceExporter を使用して Bean を公開する

次の例に示すように、新しく作成された remoting-servlet.xml というアプリケーションコンテキストで、HessianServiceExporter を作成してサービスをエクスポートします。

<bean id="accountService" class="example.AccountServiceImpl">
    <!-- any additional properties, maybe a DAO? -->
</bean>

<bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

これで、クライアントでサービスにリンクする準備ができました。(リクエスト URL をサービスにマップするために)明示的なハンドラーマッピングが指定されていないため、BeanNameUrlHandlerMapping を使用します。サービスは、含まれる DispatcherServlet インスタンスのマッピング内の Bean 名で示された URL でエクスポートされます(前に定義したとおり): https://HOST:8080/remoting/AccountService (英語)

または、次の例に示すように、ルートアプリケーションコンテキスト(たとえば WEB-INF/applicationContext.xml)で HessianServiceExporter を作成できます。

<bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter">
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

後者の場合、このエクスポーターに対応するサーブレットを web.xml で定義する必要があります。同じ結果が得られます。エクスポーターは /remoting/AccountService でリクエストパスにマップされます。サーブレット名は、ターゲットエクスポーターの Bean 名と一致する必要があることに注意してください。次の例は、その方法を示しています。

<servlet>
    <servlet-name>accountExporter</servlet-name>
    <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>accountExporter</servlet-name>
    <url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>

1.2.3. クライアント上のサービスへのリンク

HessianProxyFactoryBean を使用することにより、クライアントでサービスにリンクできます。RMI の例と同じ原則が適用されます。別の Bean ファクトリまたはアプリケーションコンテキストを作成し、次の例に示すように、SimpleObject が AccountService を使用してアカウントを管理する次の Bean にメンションします。

<bean class="example.SimpleObject">
    <property name="accountService" ref="accountService"/>
</bean>

<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
    <property name="serviceUrl" value="https://remotehost:8080/remoting/AccountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

1.2.4. Hessian を介して公開されたサービスへの HTTP 基本認証の適用

Hessian の利点の 1 つは、両方のプロトコルが HTTP ベースであるため、HTTP 基本認証を簡単に適用できることです。たとえば、web.xml セキュリティ機能を使用して、通常の HTTP サーバーセキュリティメカニズムを適用できます。通常、ここではユーザーごとのセキュリティ認証情報を使用する必要はありません。代わりに、次の例に示すように、HessianProxyFactoryBean レベルで定義した共有資格情報を使用できます(JDBC DataSource と同様)。

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    <property name="interceptors" ref="authorizationInterceptor"/>
</bean>

<bean id="authorizationInterceptor"
        class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
    <property name="authorizedRoles" value="administrator,operator"/>
</bean>

前の例では、明示的に BeanNameUrlHandlerMapping にメンションし、インターセプターを設定して、管理者とオペレーターのみがこのアプリケーションコンテキストでメンションされた Bean を呼び出せるようにしました。

上記の例は、柔軟な種類のセキュリティインフラストラクチャを示していません。セキュリティに関する限り、より多くのオプションについては、/projects/spring-security/ の Spring Security プロジェクトを参照してください。

1.3. Spring HTTP インボーカー

Hessian とは対照的に、Spring HTTP インボーカーは、独自のスリムな直列化メカニズムを使用する軽量プロトコルであり、標準の Java 直列化メカニズムを使用して HTTP を介してサービスを公開します。引数と戻り値の型が、Hessian が使用する直列化メカニズムを使用して直列化できない複雑な型である場合、これは大きな利点があります(リモーティングテクノロジーを選択する際の考慮事項については、次のセクションを参照してください)。

内部では、Spring は JDK または Apache HttpComponents が提供する標準機能を使用して HTTP 呼び出しを実行します。より高度で使いやすい機能が必要な場合は、後者を使用してください。詳細については、hc.apache.org/httpcomponents-client-ga/ (英語) を参照してください。

安全でない Java の逆直列化による脆弱性に注意してください。操作された入力ストリームは、逆直列化ステップ中にサーバー上で望ましくないコード実行を引き起こす可能性があります。結果として、HTTP 呼び出し側エンドポイントを信頼できないクライアントに公開しないでください。むしろ、独自のサービス間でのみ公開します。一般的に、代わりに他のメッセージ形式(JSON など)を使用することを強くお勧めします。

Java の直列化によるセキュリティの脆弱性が心配な場合は、元々 JDK 9 用に開発されましたが、その間に JDK 8, 7 および 6 にバックポートされた、コア JVM レベルでの汎用直列化フィルターメカニズムを検討してください。https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_a (英語) および https://openjdk.java.net/jeps/290 (英語) を参照してください。

1.3.1. サービスオブジェクトの公開

サービスオブジェクトの HTTP 呼び出し元インフラストラクチャのセットアップは、Hessian を使用して同じことを行う方法に非常に似ています。Hessian サポートは HessianServiceExporter を提供するため、Spring の HttpInvoker サポートは org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter を提供します。

Spring Web MVC DispatcherServlet 内で AccountService (前述)を公開するには、次の例に示すように、ディスパッチャーのアプリケーションコンテキストで次の設定を行う必要があります。

<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

このようなエクスポーター定義は、Hessian のセクションに従って、DispatcherServlet インスタンスの標準マッピング機能を介して公開されます。

または、次の例に示すように、ルートアプリケーションコンテキスト(たとえば 'WEB-INF/applicationContext.xml')で HttpInvokerServiceExporter を作成できます。

<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

さらに、次の例に示すように、このエクスポーターに対応するサーブレットを web.xml で定義し、サーブレットエクスポーターの Bean 名と一致するサーブレット名を使用できます。

<servlet>
    <servlet-name>accountExporter</servlet-name>
    <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>accountExporter</servlet-name>
    <url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>

1.3.2. クライアントでのサービスのリンク

繰り返しになりますが、クライアントからのサービスへのリンクは、Hessian を使用する場合のリンク方法とよく似ています。Spring は、プロキシを使用することにより、HTTP POST リクエストへの呼び出しを、エクスポートされたサービスを指す URL に変換できます。次の例は、この配置を構成する方法を示しています。

<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
    <property name="serviceUrl" value="https://remotehost:8080/remoting/AccountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

前述したように、使用する HTTP クライアントを選択できます。デフォルトでは、HttpInvokerProxy は JDK の HTTP 機能を使用しますが、httpInvokerRequestExecutor プロパティを設定して Apache HttpComponents クライアントを使用することもできます。次の例は、その方法を示しています。

<property name="httpInvokerRequestExecutor">
    <bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/>
</property>

1.4. Java Web サービス

Spring は、標準の Java Web サービス API を完全にサポートしています。

  • JAX-WS を使用した Web サービスの公開

  • JAX-WS を使用した Web サービスへのアクセス

Spring コアでの JAX-WS のストックサポートに加えて、Spring ポートフォリオは、契約先優先のドキュメント駆動型 Web サービスのソリューションである Spring Web Services (英語) も備えています。

1.4.1. JAX-WS を使用してサーブレットベースの Web サービスを公開する

Spring は、JAX-WS サーブレットエンドポイント実装の便利な基本クラス SpringBeanAutowiringSupport を提供します。AccountService を公開するには、Spring の SpringBeanAutowiringSupport クラスを継承し、ここにビジネスロジックを実装します。通常は、呼び出しをビジネスレイヤーに委譲します。Spring の @Autowired アノテーションを使用して、Spring 管理 Bean へのそのような依存関係を表現します。次の例は、SpringBeanAutowiringSupport を継承するクラスを示しています。

/**
 * JAX-WS compliant AccountService implementation that simply delegates
 * to the AccountService implementation in the root web application context.
 *
 * This wrapper class is necessary because JAX-WS requires working with dedicated
 * endpoint classes. If an existing service needs to be exported, a wrapper that
 * extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through
 * the @Autowired annotation) is the simplest JAX-WS compliant way.
 *
 * This is the class registered with the server-side JAX-WS implementation.
 * In the case of a Java EE server, this would simply be defined as a servlet
 * in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting
 * accordingly. The servlet name usually needs to match the specified WS service name.
 *
 * The web service engine manages the lifecycle of instances of this class.
 * Spring bean references will just be wired in here.
 */
import org.springframework.web.context.support.SpringBeanAutowiringSupport;

@WebService(serviceName="AccountService")
public class AccountServiceEndpoint extends SpringBeanAutowiringSupport {

    @Autowired
    private AccountService biz;

    @WebMethod
    public void insertAccount(Account acc) {
        biz.insertAccount(acc);
    }

    @WebMethod
    public Account[] getAccounts(String name) {
        return biz.getAccounts(name);
    }
}

AccountServiceEndpoint は、Spring の機能にアクセスできるように、Spring コンテキストと同じ Web アプリケーションで実行する必要があります。これは、JAX-WS サーブレットエンドポイントデプロイの標準契約を使用する、Java EE 環境のデフォルトです。詳細については、さまざまな Java EE Web サービスチュートリアルを参照してください。

1.4.2. JAX-WS を使用したスタンドアロン Web サービスのエクスポート

Oracle の JDK に付属している組み込みの JAX-WS プロバイダーは、JDK にも含まれている組み込みの HTTP サーバーを使用して Web サービスの公開をサポートします。Spring の SimpleJaxWsServiceExporter は、Spring アプリケーションコンテキスト内の @WebService アノテーション付き Bean をすべて検出し、デフォルトの JAX-WS サーバー(JDK HTTP サーバー)経由でエクスポートします。

このシナリオでは、エンドポイントインスタンスは Spring Bean 自体として定義および管理されます。これらは JAX-WS エンジンに登録されていますが、そのライフサイクルは Spring アプリケーションコンテキスト次第です。これは、Spring 機能(明示的な依存性注入など)をエンドポイントインスタンスに適用できることを意味します。@Autowired によるアノテーション駆動型の注入も機能します。次の例は、これらの Bean を定義する方法を示しています。

<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
    <property name="baseAddress" value="http://localhost:8080/"/>
</bean>

<bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint">
    ...
</bean>

...

この例のエンドポイントは完全に Spring 管理の Bean であるため、AccountServiceEndpoint は Spring の SpringBeanAutowiringSupport から派生できますが、派生する必要はありません。これは、エンドポイントの実装を次のようにできることを意味します(スーパークラスを宣言せずに、Spring の @Autowired 構成アノテーションを引き続き使用できます)。

@WebService(serviceName="AccountService")
public class AccountServiceEndpoint {

    @Autowired
    private AccountService biz;

    @WebMethod
    public void insertAccount(Account acc) {
        biz.insertAccount(acc);
    }

    @WebMethod
    public List<Account> getAccounts(String name) {
        return biz.getAccounts(name);
    }
}

1.4.3. JAX-WS RI の Spring サポートを使用した Web サービスのエクスポート

GlassFish プロジェクトの一部として開発された Oracle の JAX-WS RI は、JAXG-WS Commons プロジェクトの一部として Spring サポートを提供しています。これにより、前のセクションで説明したスタンドアロンモードと同様ですが、今回はサーブレット環境で、JAX-WS エンドポイントを Spring 管理 Bean として定義できます。

これは、Java EE 環境では移植できません。主に、Web アプリケーションの一部として JAX-WS RI を組み込む Tomcat などの非 EE 環境を対象としています。

サーブレットベースのエンドポイントをエクスポートする標準スタイルとの違いは、エンドポイントインスタンス自体のライフサイクルが Spring によって管理されていることと、web.xml で定義されている JAX-WS サーブレットが 1 つしかないことです。標準の Java EE スタイル(前述)では、サービスエンドポイントごとに 1 つのサーブレット定義があり、通常、各エンドポイントは(前述のように @Autowired を使用して)Spring Bean に委譲します。

セットアップと使用スタイルの詳細については、https://jax-ws-commons.java.net/spring/ (英語) を参照してください。

1.4.4. JAX-WS を使用した Web サービスへのアクセス

Spring は、JAX-WS Web サービスプロキシ、つまり LocalJaxWsServiceFactoryBean と JaxWsPortProxyFactoryBean を作成する 2 つのファクトリ Bean を提供します。前者は、作業対象の JAX-WS サービスクラスのみを返すことができます。後者は、ビジネスサービスインターフェースを実装するプロキシを返すことができる本格的なバージョンです。次の例では、JaxWsPortProxyFactoryBean を使用して AccountService エンドポイントのプロキシを作成します(再び)。

<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
    <property name="serviceInterface" value="example.AccountService"/> (1)
    <property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/>
    <property name="namespaceUri" value="https://example/"/>
    <property name="serviceName" value="AccountService"/>
    <property name="portName" value="AccountServiceEndpointPort"/>
</bean>
1serviceInterface は、クライアントが使用するビジネスインターフェースです。

wsdlDocumentUrl は、WSDL ファイルの URL です。Spring は、JAX-WS サービスを作成するために起動時にこれを必要とします。namespaceUri は、.wsdl ファイルの targetNamespace に対応しています。serviceName は、.wsdl ファイルのサービス名に対応しています。portName は、.wsdl ファイルのポート名に対応しています。

Web サービスへのアクセスは簡単です。AccountService というインターフェースとして公開する Bean ファクトリがあるためです。次の例は、これを Spring に接続する方法を示しています。

<bean id="client" class="example.AccountClientImpl">
    ...
    <property name="service" ref="accountWebService"/>
</bean>

次の例に示すように、クライアントコードから、通常のクラスであるかのように Web サービスにアクセスできます。

public class AccountClientImpl {

    private AccountService service;

    public void setService(AccountService service) {
        this.service = service;
    }

    public void foo() {
        service.insertAccount(...);
    }
}
JAX-WS では、エンドポイントインターフェースと実装クラスに @WebService@SOAPBinding などのアノテーションを付ける必要があるため、上記はわずかに単純化されています。これは、プレーン Java インターフェースと実装クラスを JAX-WS エンドポイントアーティファクトとして(簡単に)使用できないことを意味します。最初にそれに応じてアノテーションを付ける必要があります。これらの要件の詳細については、JAX-WS のドキュメントを確認してください。

1.5. JMS

基礎となる通信プロトコルとして JMS を使用することにより、サービスを透過的に公開することもできます。Spring Framework の JMS リモーティングサポートは非常に基本的なものです。same thread と同じ非トランザクション Session で送受信します。その結果、スループットは実装に依存します。これらのシングルスレッドおよび非トランザクション制約は、Spring の JMS リモーティングサポートにのみ適用されることに注意してください。Spring の JMS ベースのメッセージングの豊富なサポートについては、JMS (Java Message Service) を参照してください。

次のインターフェースは、サーバー側とクライアント側の両方で使用されます。

package com.foo;

public interface CheckingAccountService {

    public void cancelAccount(Long accountId);
}

上記のインターフェースの次の簡単な実装は、サーバー側で使用されます。

package com.foo;

public class SimpleCheckingAccountService implements CheckingAccountService {

    public void cancelAccount(Long accountId) {
        System.out.println("Cancelling account [" + accountId + "]");
    }
}

次の構成ファイルには、クライアントとサーバーの両方で共有される JMS インフラストラクチャ 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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://ep-t43:61616"/>
    </bean>

    <bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg value="mmm"/>
    </bean>

</beans>

1.5.1. サーバー側の構成

サーバーでは、次の例に示すように、JmsInvokerServiceExporter を使用するサービスオブジェクトを公開する必要があります。

<?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="checkingAccountService"
            class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
        <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
        <property name="service">
            <bean class="com.foo.SimpleCheckingAccountService"/>
        </property>
    </bean>

    <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="queue"/>
        <property name="concurrentConsumers" value="3"/>
        <property name="messageListener" ref="checkingAccountService"/>
    </bean>

</beans>
package com.foo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Server {

    public static void main(String[] args) throws Exception {
        new ClassPathXmlApplicationContext("com/foo/server.xml", "com/foo/jms.xml");
    }
}

1.5.2. クライアント側の構成

クライアントは、合意されたインターフェース(CheckingAccountService)を実装するクライアント側プロキシを作成するだけです。

次の例では、他のクライアント側オブジェクトに注入できる Bean を定義します(プロキシは JMS を介してサーバー側オブジェクトへの呼び出しの転送を処理します)。

<?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="checkingAccountService"
            class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
        <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="queue" ref="queue"/>
    </bean>

</beans>
package com.foo;

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

public class Client {

    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("com/foo/client.xml", "com/foo/jms.xml");
        CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService");
        service.cancelAccount(new Long(10));
    }
}

1.6. AMQP

基になるプロトコルとしての AMQP を介したリモート処理は、Spring AMQP プロジェクトでサポートされています。詳細については、Spring AMQP リファレンスの Spring リモーティング (英語) セクションを参照してください。

リモートインターフェースには自動検出は実装されていません

実装されたインターフェースの自動検出がリモートインターフェースで発生しない主な理由は、リモート呼び出し元に対してあまりにも多くのドアを開くことを避けるためです。ターゲットオブジェクトは、InitializingBean や DisposableBean など、呼び出し元に公開したくない内部コールバックインターフェースを実装する場合があります。

ターゲットによって実装されるすべてのインターフェースを備えたプロキシを提供することは、通常、ローカルのケースでは問題になりません。ただし、リモートサービスをエクスポートする場合は、リモートの使用を目的とした特定の操作を使用して、特定のサービスインターフェースを公開する必要があります。内部コールバックインターフェースに加えて、ターゲットは複数のビジネスインターフェースを実装する場合があり、そのうちの 1 つだけがリモートの公開を目的としています。これらの理由により、このようなサービスインターフェースを指定する必要があります。

これは、構成の利便性と内部メソッドの偶発的な露出のリスクとのトレードオフです。常にサービスインターフェースを指定することは、それほど労力を必要とせず、特定のメソッドの制御された公開に関して安全な側になります。

1.7. テクノロジーを選択する際の考慮事項

ここで紹介するすべてのテクノロジーには欠点があります。テクノロジーを選択するときは、ニーズ、公開するサービス、およびネットワーク経由で送信するオブジェクトを慎重に検討する必要があります。

RMI を使用する場合、RMI トラフィックをトンネリングしない限り、HTTP プロトコルを介してオブジェクトにアクセスできません。RMI は、フルオブジェクト直列化をサポートするという点でかなり重いプロトコルです。これは、ワイヤ経由での直列化が必要な複雑なデータモデルを使用する場合に重要です。ただし、RMI-JRMP は Java クライアントに関連付けられています。これは、Java から Java へのリモートソリューションです。

Spring の HTTP インボーカーは、HTTP ベースのリモート処理が必要であるが、Java 直列化にも依存している場合に適しています。RMI 呼び出し側と基本インフラストラクチャを共有しますが、トランスポートとして HTTP を使用します。HTTP 呼び出し側は、Java から Java へのリモート処理だけでなく、クライアント側とサーバー側の両方で Spring にも制限されていることに注意してください。(後者は、非 RMI インターフェース用の Spring の RMI インボーカーにも適用されます。)

Hessian は、非 Java クライアントを明示的に許可するため、異種環境で動作する場合に大きな価値を提供する場合があります。ただし、Java 以外のサポートはまだ制限されています。既知の課題には、遅延初期化されたコレクションと組み合わせた Hibernate オブジェクトの直列化が含まれます。そのようなデータモデルがある場合は、Hessian の代わりに RMI または HTTP インボーカーの使用を検討してください。

JMS は、サービスのクラスターを提供し、JMS ブローカーに負荷分散、ディスカバリ、および自動フェイルオーバーを処理させるのに役立ちます。デフォルトでは、JMS リモーティングに Java 直列化が使用されますが、JMS プロバイダーは、XStream などのワイヤーフォーマットに別のメカニズムを使用して、サーバーを他のテクノロジーに実装できます。

最後に大事なことを言い忘れましたが、EJB は標準のロールベースの認証と認可とリモートトランザクションの伝播をサポートするという点で RMI よりも優れています。RMI 呼び出し側または HTTP 呼び出し側を取得して、セキュリティコンテキストの伝播をサポートすることもできますが、これはコア Spring では提供されていません。Spring は、サードパーティまたはカスタムソリューションのプラグインに適したフックのみを提供します。

1.8. REST エンドポイント

Spring Framework には、REST エンドポイントを呼び出すための 2 つの選択肢があります。

  • RestTemplate : 同期テンプレートメソッド API を備えた元の Spring REST クライアント。

  • WebClient : 同期および非同期の両方のシナリオとストリーミングのシナリオをサポートする、ノンブロッキングのリアクティブな代替手段。

5.0 の時点で、RestTemplate はメンテナンスモードになっており、今後は変更およびバグのマイナーリクエストのみが受け入れられます。最新の API を提供し、同期、非同期、およびストリーミングのシナリオをサポートする WebClient の使用を検討してください。

1.8.1. RestTemplate

RestTemplate は、HTTP クライアントライブラリを介した高レベルの API を提供します。REST エンドポイントを 1 行で簡単に呼び出すことができます。オーバーロードされたメソッドの次のグループを公開します。

表 1: RestTemplate メソッド
メソッドグループ 説明

getForObject

GET を介して表現を取得します。

getForEntity

GET を使用して ResponseEntity (つまり、ステータス、ヘッダー、および本文)を取得します。

headForHeaders

HEAD を使用して、リソースのすべてのヘッダーを取得します。

postForLocation

POST を使用して新しいリソースを作成し、レスポンスから Location ヘッダーを返します。

postForObject

POST を使用して新しいリソースを作成し、レスポンスから表現を返します。

postForEntity

POST を使用して新しいリソースを作成し、レスポンスから表現を返します。

put

PUT を使用してリソースを作成または更新します。

patchForObject

PATCH を使用してリソースを更新し、レスポンスから表現を返します。JDK HttpURLConnection は PATCH をサポートしていませんが、Apache HttpComponents などはサポートしていることに注意してください。

delete

DELETE を使用して、指定された URI のリソースを削除します。

optionsForAllow

ALLOW を使用して、リソースに許可された HTTP メソッドを取得します。

exchange

必要に応じて柔軟性を高める、前述のメソッドのより一般化された(そしてあまり意見のない)バージョン。RequestEntity (HTTP メソッド、URL、ヘッダー、および本文を入力として含む)を受け入れ、ResponseEntity を返します。

これらのメソッドでは、Class の代わりに ParameterizedTypeReference を使用して、ジェネリックでレスポンスタイプを指定できます。

execute

コールバックインターフェースを介したリクエストの準備とレスポンスの抽出を完全に制御して、リクエストを実行する最も一般的な方法。

初期化

デフォルトのコンストラクターは、java.net.HttpURLConnection を使用してリクエストを実行します。ClientHttpRequestFactory の実装により、異なる HTTP ライブラリに切り替えることができます。以下の組み込みサポートがあります。

  • Apache HttpComponents

  • Netty

  • OkHttp

例:Apache HttpComponents に切り替えるには、次を使用できます。

RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

各 ClientHttpRequestFactory は、基礎となる HTTP クライアントライブラリに固有の構成オプションを公開します。たとえば、資格情報、接続プーリング、その他の詳細などです。

HTTP リクエストの java.net 実装は、エラー(401 など)を表すレスポンスのステータスにアクセスするときに例外を発生させる可能性があることに注意してください。これが課題になる場合は、別の HTTP クライアントライブラリに切り替えてください。
URI

RestTemplate メソッドの多くは、String 変数引数として、または Map<String,String> として URI テンプレートおよび URI テンプレート変数を受け入れます。

次の例では、String 変数引数を使用しています。

String result = restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");

次の例では、Map<String, String> を使用しています。

Map<String, String> vars = Collections.singletonMap("hotel", "42");

String result = restTemplate.getForObject(
        "https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

次の例に示すように、URI テンプレートは自動的にエンコードされることに注意してください。

restTemplate.getForObject("https://example.com/hotel list", String.class);

// Results in request to "https://example.com/hotel%20list"

RestTemplate の uriTemplateHandler プロパティを使用して、URI のエンコード方法をカスタマイズできます。または、java.net.URI を準備して、URI を受け入れる RestTemplate メソッドの 1 つに渡すことができます。

URI の操作とエンコードの詳細については、URI リンクを参照してください。

ヘッダー

次の例に示すように、exchange() メソッドを使用してリクエストヘッダーを指定できます。

String uriTemplate = "https://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);

RequestEntity<Void> requestEntity = RequestEntity.get(uri)
        .header(("MyRequestHeader", "MyValue")
        .build();

ResponseEntity<String> response = template.exchange(requestEntity, String.class);

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();

ResponseEntity を返す多くの RestTemplate メソッドバリアントを介してレスポンスヘッダーを取得できます。

本文

RestTemplate メソッドに渡されたオブジェクトと RestTemplate メソッドから返されたオブジェクトは、HttpMessageConverter を使用して生のコンテンツとの間で変換されます。

POST では、次の例に示すように、入力オブジェクトはリクエスト本文に直列化されます。

URI location = template.postForLocation("https://example.com/people", person);

リクエストの Content-Type ヘッダーを明示的に設定する必要はありません。ほとんどの場合、ソース Object タイプに基づいて互換性のあるメッセージコンバーターを見つけることができ、選択したメッセージコンバーターはそれに応じてコンテンツタイプを設定します。必要に応じて、exchange メソッドを使用して明示的に Content-Type リクエストヘッダーを提供できます。これにより、選択されるメッセージコンバーターに影響を与えます。

GET では、次の例に示すように、レスポンスの本文が出力 Object に逆直列化されます。

Person person = restTemplate.getForObject("https://example.com/people/{id}", Person.class, 42);

リクエストの Accept ヘッダーを明示的に設定する必要はありません。ほとんどの場合、予期されるレスポンスタイプに基づいて互換性のあるメッセージコンバーターを見つけることができます。これにより、Accept ヘッダーにデータが入力されます。必要に応じて、exchange メソッドを使用して、Accept ヘッダーを明示的に提供できます。

デフォルトでは、RestTemplate は、オプションの変換ライブラリが存在するかどうかを判断するのに役立つクラスパスチェックに応じて、すべての組み込みメッセージコンバーターを登録します。明示的に使用するようにメッセージコンバーターを設定することもできます。

メッセージ変換

spring-web モジュールには、InputStream および OutputStream を介した HTTP リクエストおよびレスポンスの本体の読み取りおよび書き込み用の HttpMessageConverter 契約が含まれています。HttpMessageConverter インスタンスは、クライアント側(たとえば、RestTemplate)およびサーバー側(たとえば、Spring MVC REST コントローラー)で使用されます。

メインメディア(MIME)タイプの具体的な実装はフレームワークで提供され、デフォルトでクライアント側では RestTemplate に、サーバー側では RequestMethodHandlerAdapter に登録されます(メッセージコンバーターの構成を参照)。

HttpMessageConverter の実装については、次のセクションで説明します。すべてのコンバーターで、デフォルトのメディアタイプが使用されますが、supportedMediaTypes Bean プロパティを設定することでそれをオーバーライドできます。次の表は、各実装について説明しています。

テーブル 2: HttpMessageConverter の実装
MessageConverter 説明

StringHttpMessageConverter

HTTP リクエストおよびレスポンスから String インスタンスを読み書きできる HttpMessageConverter 実装。デフォルトでは、このコンバーターはすべてのテキストメディアタイプ(text/*)をサポートし、text/plain の Content-Type で書き込みます。

FormHttpMessageConverter

HTTP リクエストとレスポンスからフォームデータを読み書きできる HttpMessageConverter 実装。デフォルトでは、このコンバーターは application/x-www-form-urlencoded メディアタイプの読み取りと書き込みを行います。フォームデータは MultiValueMap<String, String> から読み取られ、そこに書き込まれます。コンバーターは、MultiValueMap<String, Object> から読み取られたマルチパートデータを書き込むこともできます(読み取ることはできません)。デフォルトでは、multipart/form-data がサポートされています。Spring Framework 5.2, 以降、フォームデータの書き込み用に追加のマルチパートサブタイプをサポートできます。詳細については、FormHttpMessageConverter の javadoc を参照してください。

ByteArrayHttpMessageConverter

HTTP リクエストとレスポンスからバイト配列を読み書きできる HttpMessageConverter 実装。デフォルトでは、このコンバーターはすべてのメディアタイプ(*/*)をサポートし、application/octet-stream の Content-Type で書き込みます。supportedMediaTypes プロパティを設定し、getContentType(byte[]) をオーバーライドすることにより、これをオーバーライドできます。

MarshallingHttpMessageConverter

org.springframework.oxm パッケージから Spring の Marshaller および Unmarshaller 抽象化を使用して XML を読み書きできる HttpMessageConverter 実装。このコンバーターを使用するには、Marshaller および Unmarshaller が必要です。これらは、コンストラクターまたは Bean プロパティを介して注入できます。デフォルトでは、このコンバーターは text/xml および application/xml をサポートしています。

MappingJackson2HttpMessageConverter

Jackson の ObjectMapper を使用して JSON を読み書きできる HttpMessageConverter 実装。Jackson が提供するアノテーションを使用して、必要に応じて JSON マッピングをカスタマイズできます。さらに制御が必要な場合(特定のタイプに対してカスタム JSON シリアライザー / デシリアライザーを提供する必要がある場合)、ObjectMapper プロパティを介してカスタム ObjectMapper を挿入できます。デフォルトでは、このコンバーターは application/json をサポートしています。

MappingJackson2XmlHttpMessageConverter

Jackson XML (GitHub) 拡張機能の XmlMapper を使用して XML を読み書きできる HttpMessageConverter 実装。JAXB または Jackson が提供するアノテーションを使用して、必要に応じて XML マッピングをカスタマイズできます。さらに制御が必要な場合(特定の型に対してカスタム XML シリアライザー / デシリアライザーを提供する必要がある場合)、ObjectMapper プロパティを介してカスタム XmlMapper を挿入できます。デフォルトでは、このコンバーターは application/xml をサポートしています。

SourceHttpMessageConverter

HTTP リクエストおよびレスポンスから javax.xml.transform.Source を読み書きできる HttpMessageConverter 実装。DOMSourceSAXSource および StreamSource のみがサポートされています。デフォルトでは、このコンバーターは text/xml および application/xml をサポートしています。

BufferedImageHttpMessageConverter

HTTP リクエストおよびレスポンスから java.awt.image.BufferedImage を読み書きできる HttpMessageConverter 実装。このコンバーターは、Java I/O API でサポートされているメディアタイプを読み書きします。

Jackson JSON ビュー

次の例に示すように、Jackson JSON ビュー (英語) を指定して、オブジェクトプロパティのサブセットのみを直列化できます。

MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);

RequestEntity<MappingJacksonValue> requestEntity =
    RequestEntity.post(new URI("https://example.com/user")).body(value);

ResponseEntity<String> response = template.exchange(requestEntity, String.class);
マルチパート

マルチパートデータを送信するには、値がパーツコンテンツ用の Object、ファイルパーツ用の Resource、またはヘッダー付きパーツコンテンツ用の HttpEntity である MultiValueMap<String, Object> を提供する必要があります。例:

MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();

parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));

ほとんどの場合、各パーツに Content-Type を指定する必要はありません。コンテンツタイプは、直列化するために選択された HttpMessageConverter に基づいて自動的に決定されます。Resource の場合は、ファイル拡張子に基づいて決定されます。必要に応じて、MediaType に HttpEntity ラッパーを明示的に提供できます。

MultiValueMap の準備ができたら、以下に示すように RestTemplate に渡すことができます。

MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);

MultiValueMap に少なくとも 1 つの非 String 値が含まれている場合、Content-Type は FormHttpMessageConverter によって multipart/form-data に設定されます。MultiValueMap に String 値がある場合、Content-Type は application/x-www-form-urlencoded にデフォルト設定されます。必要に応じて、Content-Type を明示的に設定することもできます。

1.8.2. AsyncRestTemplate を使用する (非推奨)

AsyncRestTemplate は非推奨です。AsyncRestTemplate の使用を検討するすべてのユースケースでは、代わりに WebClient を使用してください。

2. エンタープライズ JavaBeans(EJB)統合

軽量のコンテナーとして、Spring は多くの場合 EJB の代替と見なされます。ほとんどの場合ではなく、多くのアプリケーションおよびユースケースで、コンテナーとしての Spring は、トランザクション、ORM および JDBC アクセスの領域での豊富なサポート機能と組み合わせて、EJB を介して同等の機能を実装するよりも良い選択であると考えています。コンテナーと EJB。

ただし、Spring を使用しても EJB を使用できなくなるわけではないことに注意してください。実際、Spring を使用すると、EJB に簡単にアクセスし、EJB とその中に EJB と機能を実装できます。さらに、Spring を使用して EJB が提供するサービスにアクセスすると、クライアントコードを変更せずに、それらのサービスの実装を後でローカル EJB、リモート EJB、または POJO(プレーンオールド Java オブジェクト)バリアント間で透過的に切り替えることができます。

この章では、Spring が EJB へのアクセスと実装をどのように支援できるかについて説明します。Spring は、ステートレスセッション Bean(SLSB)にアクセスするときに特定の値を提供するため、このトピックについて説明することから始めます。

2.1. EJB へのアクセス

このセクションでは、EJB にアクセスする方法について説明します。

2.1.1. 概念

ローカルまたはリモートステートレスセッション Bean でメソッドを呼び出すには、クライアントコードは通常 JNDI ルックアップを実行して(ローカルまたはリモート)EJB ホームオブジェクトを取得し、そのオブジェクトで create メソッド呼び出しを使用して実際の(ローカルまたはリモート)EJB オブジェクト。次に、EJB で 1 つ以上のメソッドが呼び出されます。

低レベルのコードの繰り返しを避けるために、多くの EJB アプリケーションは Service Locator および Business Delegate パターンを使用します。これらは、クライアントコード全体に JNDI ルックアップをスプレーするよりも優れていますが、通常の実装には重大な欠点があります。

  • 通常、EJB を使用するコードは Service Locator または Business Delegate のシングルトンに依存するため、テストが困難になります。

  • ビジネスデリゲートなしで使用されるサービスロケーターパターンの場合、アプリケーションコードは、EJB ホームで create() メソッドを呼び出し、結果の例外を処理する必要があります。EJB API および EJB プログラミングモデルの複雑さに関連したままです。

  • Business Delegate パターンを実装すると、通常、大幅なコードの重複が発生し、EJB で同じメソッドを呼び出す多数のメソッドを記述する必要があります。

Spring のアプローチは、プロキシレスオブジェクト(通常は Spring コンテナー内で構成されます)の作成と使用を可能にすることです。プロキシオブジェクトはコードレスのビジネスデリゲートとして機能します。そのようなコードに実際の値を実際に追加しない限り、別の Service Locator、別の JNDI ルックアップ、または手書きのビジネスデリゲートでメソッドを複製する必要はありません。

2.1.2. ローカル SLSB へのアクセス

ローカル EJB を使用する必要がある Web コントローラーがあると仮定します。EJB のローカルインターフェースが非 EJB 固有のビジネスメソッドインターフェースを継承するように、ベストプラクティスに従い、EJB ビジネスメソッドインターフェースパターンを使用します。このビジネスメソッドインターフェースを MyComponent と呼びます。次の例は、このようなインターフェースを示しています。

public interface MyComponent {
    ...
}

Business Methods Interface パターンを使用する主な理由の 1 つは、ローカルインターフェースのメソッドシグネチャーと Bean 実装クラス間の同期が自動的に行われるようにすることです。もう 1 つの理由は、意味がある場合は、後でサービスの POJO(プレーンな古い Java オブジェクト)実装に切り替えることがはるかに簡単になるためです。また、ローカルホームインターフェースを実装し、SessionBean および MyComponent ビジネスメソッドインターフェースを実装する実装クラスを提供する必要があります。これで、Web 層コントローラーを EJB 実装に接続するために必要な Java コーディングは、コントローラーで MyComponent タイプの setter メソッドを公開することだけです。これにより、参照がコントローラーのインスタンス変数として保存されます。次の例は、そのメソッドを示しています。

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) {
    this.myComponent = myComponent;
}

その後、コントローラーの任意のビジネスメソッドでこのインスタンス変数を使用できます。ここで、Spring コンテナーからコントローラーオブジェクトを取得すると仮定すると、EJB プロキシオブジェクトである LocalStatelessSessionProxyFactoryBean インスタンスを(同じコンテキストで)構成できます。プロキシを構成し、次の構成エントリを使用してコントローラーの myComponent プロパティを設定します。

<bean id="myComponent"
        class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="ejb/myBean"/>
    <property name="businessInterface" value="com.mycom.MyComponent"/>
</bean>

<bean id="myController" class="com.mycom.myController">
    <property name="myComponent" ref="myComponent"/>
</bean>

Spring AOP フレームワークのおかげで、多くの作業がバックグラウンドで行われていますが、結果を楽しむために AOP コンセプトを使用する必要はありません。myComponent Bean 定義は、ビジネスメソッドインターフェースを実装する EJB のプロキシを作成します。EJB ローカルホームは起動時にキャッシュされるため、JNDI ルックアップは 1 つだけです。EJB が呼び出されるたびに、プロキシはローカル EJB で classname メソッドを呼び出し、EJB で対応するビジネスメソッドを呼び出します。

myController Bean 定義は、コントローラークラスの myComponent プロパティを EJB プロキシに設定します。

別の方法として(できればこのようなプロキシ定義が多い場合)、Spring の「jee」名前空間で <jee:local-slsb> 構成要素の使用を検討してください。次の例は、その方法を示しています。

<jee:local-slsb id="myComponent" jndi-name="ejb/myBean"
        business-interface="com.mycom.MyComponent"/>

<bean id="myController" class="com.mycom.myController">
    <property name="myComponent" ref="myComponent"/>
</bean>

この EJB アクセスメカニズムにより、アプリケーションコードが大幅に簡素化されます。Web 層コード(または他の EJB クライアントコード)は、EJB の使用に依存しません。この EJB 参照を POJO またはモックオブジェクトまたは他のテストスタブに置き換えるには、Java コードの行を変更せずに myComponent Bean 定義を変更できます。さらに、アプリケーションの一部として、JNDI ルックアップまたは他の EJB 接続機能コードの 1 行を記述する必要はありません。

実際のアプリケーションのベンチマークと経験は、このアプローチのパフォーマンスオーバーヘッド(ターゲット EJB のリフレクション呼び出しを含む)は最小限であり、通常の使用では検出できないことを示しています。アプリケーションサーバーの EJB インフラストラクチャにはコストがかかるため、EJB をきめ細かく呼び出すことは避けたいことに注意してください。

JNDI ルックアップに関して 1 つの注意事項があります。Bean コンテナーでは、このクラスは通常、シングルトンとして最適に使用されます(プロトタイプにする理由はありません)。ただし、その Bean コンテナーがシングルトンを事前インスタンス化する場合(さまざまな XML ApplicationContext バリアントと同様)、EJB コンテナーがターゲット EJB をロードする前に Bean コンテナーがロードされると問題が発生する可能性があります。これは、JNDI ルックアップがこのクラスの init() メソッドで実行されてからキャッシュされますが、EJB はまだターゲットの場所にバインドされていないためです。解決策は、このファクトリオブジェクトを事前にインスタンス化するのではなく、最初の使用時に作成できるようにすることです。XML コンテナーでは、lazy-init 属性を使用してこれを制御できます。

大多数の Spring ユーザーには関心がありませんが、EJB でプログラムによる AOP を行うユーザーは LocalSlsbInvokerInterceptor を調べたい場合があります。

2.1.3. リモート SLSB へのアクセス

リモート EJB へのアクセスは、SimpleRemoteStatelessSessionProxyFactoryBean または <jee:remote-slsb> 構成要素が使用されることを除いて、基本的にローカル EJB へのアクセスと同じです。もちろん、Spring の有無にかかわらず、リモート呼び出しセマンティクスが適用されます。別のコンピューターの別の VM にあるオブジェクトのメソッドの呼び出しは、使用シナリオと障害処理の観点から異なる方法で処理する必要がある場合があります。

Spring の EJB クライアントサポートは、Spring 以外のアプローチに比べてもう 1 つの利点を追加します。通常、EJB クライアントコードが EJB をローカルまたはリモートで呼び出す間で簡単に切り替えられることは問題です。これは、リモートインターフェースメソッドは RemoteException をスローすることを宣言する必要があり、クライアントコードはこれを処理する必要があるが、ローカルインターフェースメソッドは必要がないためです。リモート EJB に移動する必要のあるローカル EJB 用に記述されたクライアントコードは、通常リモート例外の処理を追加するために変更する必要があり、ローカル EJB に移動する必要があるリモート EJB 用に記述されたクライアントコードは同じままでもリモート例外の不必要な処理の多く、またはそのコードを削除するために修正されます。Spring リモート EJB プロキシを使用すると、代わりにスローされた RemoteException をビジネスメソッドインターフェースで宣言し、EJB コードを実装することはできません。リモートインターフェースは同一であり(ただし、RemoteException をスローすることを除く)、プロキシを使用して 2 つのインターフェースをあたかも同じであるかのように動的に処理します。つまり、クライアントコードは、チェックされた RemoteException クラスを処理する必要はありません。EJB の呼び出し中にスローされる実際の RemoteException は、RuntimeException のサブクラスである非チェック RemoteAccessException クラスとして再スローされます。その後、クライアントコードを認識したり、気にしたりせずに、ローカル EJB またはリモート EJB(またはプレーン Java オブジェクト)実装間で自由にターゲットサービスを切り替えることができます。もちろん、これはオプションです。ビジネスインターフェースで RemoteException を宣言することを妨げるものは何もありません。

2.1.4. EJB 2.x SLSB と EJB 3 SLSB へのアクセス

Spring を介した EJB 2.x セッション Bean および EJB 3 セッション Bean へのアクセスは、ほとんど透過的です。<jee:local-slsb> および <jee:remote-slsb> 機能を含む Spring の EJB アクセサーは、実行時に実際のコンポーネントに透過的に適応します。見つかった場合はホームインターフェースを処理し(EJB 2.x スタイル)、ホームインターフェースが使用できない場合は(EJB 3 スタイル)直接コンポーネント呼び出しを実行します。

メモ : EJB 3 セッション Bean の場合、JndiObjectFactoryBean / <jee:jndi-lookup> も効果的に使用できます。これは、完全に使用可能なコンポーネント参照がプレーン JNDI ルックアップで公開されているためです。明示的な <jee:local-slsb> または <jee:remote-slsb> ルックアップを定義すると、一貫性のあるより明示的な EJB アクセス構成が提供されます。

3. JMS (Java Message Service)

Spring は、Spring の統合が JDBC API に対して行うのとほぼ同じ方法で JMS API の使用を簡素化する JMS 統合フレームワークを提供します。

JMS は、機能の 2 つの領域、つまりメッセージの生成と消費に大きく分けることができます。JmsTemplate クラスは、メッセージ生成と同期メッセージ受信に使用されます。Java EE のメッセージ駆動型 Bean スタイルに類似した非同期受信用に、Spring は、メッセージ駆動型 POJO(MDP)の作成に使用できる多数のメッセージリスナーコンテナーを提供します。Spring は、メッセージリスナーを作成する宣言的な方法も提供します。

org.springframework.jms.core パッケージは、JMS を使用するためのコア機能を提供します。JDBC の JdbcTemplate と同様に、リソースの作成と解放を処理することで JMS の使用を簡素化する JMS テンプレートクラスが含まれています。Spring テンプレートクラスに共通する設計原則は、一般的な操作を実行するヘルパーメソッドを提供し、より高度な使用のために、処理タスクの本質をユーザー実装のコールバックインターフェースに委譲することです。JMS テンプレートは同じ設計に従います。これらのクラスは、メッセージの送信、メッセージの同期消費、および JMS セッションとメッセージプロデューサーのユーザーへの公開のためのさまざまな便利なメソッドを提供します。

org.springframework.jms.support パッケージは、JMSException 変換機能を提供します。この変換により、チェックされた JMSException 階層が、チェックされていない例外のミラー化された階層に変換されます。チェックされた javax.jms.JMSException のプロバイダー固有のサブクラスが存在する場合、この例外はチェックされていない UncategorizedJmsException にラップされます。

org.springframework.jms.support.converter パッケージは、Java オブジェクトと JMS メッセージ間で変換する MessageConverter 抽象化を提供します。

org.springframework.jms.support.destination パッケージは、JNDI に格納されている宛先にサービスロケーターを提供するなど、JMS 宛先を管理するためのさまざまな戦略を提供します。

org.springframework.jms.annotation パッケージは、@JmsListener を使用して、アノテーション駆動型のリスナーエンドポイントをサポートするために必要なインフラストラクチャを提供します。

org.springframework.jms.config パッケージは、jms 名前空間のパーサー実装と、リスナーコンテナーを構成し、リスナーエンドポイントを作成する java 構成サポートを提供します。

最後に、org.springframework.jms.connection パッケージは、スタンドアロンアプリケーションでの使用に適した ConnectionFactory の実装を提供します。また、JMS 用の Spring の PlatformTransactionManager (巧妙に命名された JmsTransactionManager)の実装も含まれています。これにより、Spring のトランザクション管理メカニズムへのトランザクションリソースとしての JMS のシームレスな統合が可能になります。

Spring Framework 5, 以降、Spring の JMS パッケージは JMS 2.0 を完全にサポートし、実行時に JMS 2.0 API が存在する必要があります。JMS 2.0 互換プロバイダーの使用をお勧めします。

システムで古いメッセージブローカーを使用する場合は、既存のブローカー世代の JMS 2.0 互換ドライバーにアップグレードしてみてください。あるいは、JMS 1.1 ベースのドライバーに対して実行して、JMS 2.0 API jar をクラスパスに配置するだけで、ドライバーに対して JMS 1.1 互換 API のみを使用することもできます。Spring の JMS サポートは、デフォルトで JMS 1.1 規則に準拠しているため、対応する構成でこのようなシナリオをサポートします。ただし、これは移行シナリオでのみ考慮してください。

3.1. Spring JMS の使用

このセクションでは、Spring の JMS コンポーネントの使用方法について説明します。

3.1.1. JmsTemplate を使用する

JmsTemplate クラスは、JMS コアパッケージの中心的なクラスです。メッセージの送信または同期受信時にリソースの作成と解放を処理するため、JMS の使用が簡単になります。

JmsTemplate を使用するコードは、明確に定義された高レベルの契約を提供するコールバックインターフェースを実装するだけで済みます。MessageCreator コールバックインターフェースは、JmsTemplate の呼び出しコードによって提供される Session が与えられると、メッセージを作成します。JMS API のより複雑な使用を可能にするために、SessionCallback は JMS セッションを提供し、ProducerCallback は Session と MessageProducer のペアを公開します。

JMS API は、2 つのタイプの送信メソッドを公開します。1 つは配信モード、優先度、および有効期間を Quality of Service(QOS)パラメーターとして使用する方法、もう 1 つは QOS パラメーターを使用せずデフォルト値を使用する方法です。JmsTemplate には多くの送信メソッドがあるため、QOS パラメーターの設定は Bean プロパティとして公開されており、送信メソッドの数の重複を避けています。同様に、同期受信呼び出しのタイムアウト値は、setReceiveTimeout プロパティを使用して設定されます。

一部の JMS プロバイダーでは、ConnectionFactory の構成を介して管理上のデフォルトの QOS 値を設定できます。これには、MessageProducer インスタンスの send メソッド(send(Destination destination, Message message))の呼び出しが、JMS 仕様で指定されているものとは異なる QOS デフォルト値を使用するという効果があります。QOS 値の一貫した管理を実現するには、ブールプロパティ isExplicitQosEnabled を true に設定して、JmsTemplate が独自の QOS 値を使用できるように明確に有効にする必要があります。

便宜上、JmsTemplate は、操作の一部として作成された一時キューでメッセージの送信と応答の待機を可能にする基本的なリクエスト / 応答操作も公開します。

JmsTemplate クラスのインスタンスは、一度設定されるとスレッドセーフです。これは重要です。なぜなら、JmsTemplate の単一のインスタンスを構成してから、この共有参照を複数の共同作業者に安全に注入できるからです。明確にするために、JmsTemplate はステートフルであり、ConnectionFactory への参照を維持しますが、この状態は会話状態ではありません。

Spring Framework 4.1, 以降、JmsMessagingTemplate は JmsTemplate の上に構築され、メッセージング抽象化、つまり org.springframework.messaging.Message との統合を提供します。これにより、一般的な方法で送信するメッセージを作成できます。

3.1.2. 接続

JmsTemplate には、ConnectionFactory への参照が必要です。ConnectionFactory は JMS 仕様の一部であり、JMS を操作するためのエントリポイントとして機能します。クライアントアプリケーションがファクトリとして使用して JMS プロバイダーとの接続を作成し、SSL 構成オプションなど、ベンダー固有のさまざまな構成パラメーターをカプセル化します。

EJB 内で JMS を使用する場合、ベンダーは、宣言的なトランザクション管理に参加し、接続とセッションのプーリングを実行できるように、JMS インターフェースの実装を提供します。この実装を使用するために、Java EE コンテナーでは通常、EJB またはサーブレットデプロイ記述子内で JMS 接続ファクトリを resource-ref として宣言する必要があります。EJB 内の JmsTemplate でこれらの機能を確実に使用するには、クライアントアプリケーションが ConnectionFactory の管理された実装を参照することを確認する必要があります。

メッセージングリソースのキャッシュ

標準 API には、多くの中間オブジェクトの作成が含まれます。メッセージを送信するには、次の「API」ウォークが実行されます。

ConnectionFactory->Connection->Session->MessageProducer->send

ConnectionFactory 操作と Send 操作の間で、3 つの中間オブジェクトが作成および破棄されます。リソースの使用を最適化し、パフォーマンスを向上させるために、Spring は ConnectionFactory の 2 つの実装を提供します。

SingleConnectionFactory を使用する

Spring は、すべての createConnection() 呼び出しで同じ Connection を返し、close() への呼び出しを無視する ConnectionFactory インターフェース SingleConnectionFactory の実装を提供します。これは、テスト環境およびスタンドアロン環境で役立ちます。そのため、同じ接続を任意の数のトランザクションにまたがる複数の JmsTemplate 呼び出しに使用できます。SingleConnectionFactory は、通常 JNDI から提供される標準 ConnectionFactory への参照を取ります。

CachingConnectionFactory を使用する

CachingConnectionFactory は SingleConnectionFactory の機能を継承し、SessionMessageProducer および MessageConsumer インスタンスのキャッシュを追加します。初期キャッシュサイズは 1 に設定されます。sessionCacheSize プロパティを使用して、キャッシュセッションの数を増やすことができます。セッションは確認モードに基づいてキャッシュされるため、実際のキャッシュされたセッションの数はその数よりも多いことに注意してください。sessionCacheSize が 1 に設定されている場合、最大 4 つのキャッシュセッションインスタンス(確認モードごとに 1 つ)が存在する可能性があります。MessageProducer および MessageConsumer インスタンスは、所有セッション内でキャッシュされ、キャッシュ時にプロデューサーとコンシューマーの固有のプロパティも考慮します。MessageProducers は、宛先に基づいてキャッシュされます。MessageConsumers は、宛先、セレクター、noLocal 配信フラグ、および永続サブスクリプション名(永続コンシューマーを作成する場合)で構成されるキーに基づいてキャッシュされます。

3.1.3. 宛先管理

宛先は、ConnectionFactory インスタンスとして、JNDI で保存および取得できる JMS 管理対象オブジェクトです。Spring アプリケーションコンテキストを設定する場合、JNDI JndiObjectFactoryBean ファクトリクラスまたは <jee:jndi-lookup> を使用して、JMS 宛先へのオブジェクトの参照に依存性注入を実行できます。ただし、アプリケーションに多数の宛先が存在する場合、または JMS プロバイダーに固有の高度な宛先管理機能がある場合、この戦略は面倒です。このような高度な宛先管理の例には、動的宛先の作成や宛先の階層的な名前空間のサポートが含まれます。JmsTemplate は、宛先名の解決を、DestinationResolver インターフェースを実装する JMS 宛先オブジェクトに委譲します。DynamicDestinationResolver は、JmsTemplate で使用されるデフォルトの実装であり、動的な宛先の解決に対応しています。JndiDestinationResolver は、JNDI に含まれる宛先のサービスロケーターとして機能するために提供され、オプションで DynamicDestinationResolver に含まれる動作にフォールバックします。

多くの場合、JMS アプリケーションで使用される送り先は実行時にのみ認識されるため、アプリケーションのデプロイ時に管理者が作成することはできません。これは、よく知られた命名規則に従って実行時に宛先を作成する相互作用するシステムコンポーネント間に共有アプリケーションロジックがあるためです。動的な送り先の作成は JMS 仕様の一部ではありませんが、ほとんどのベンダーはこの機能を提供しています。動的な送り先は、一時的な送り先と区別されるユーザー定義の名前で作成され、JNDI に登録されないことがよくあります。動的な宛先の作成に使用される API は、宛先に関連付けられているプロパティがベンダー固有であるため、プロバイダーごとに異なります。ただし、ベンダーによって時々行われる簡単な実装選択は、JMS 仕様の警告を無視し、メソッド TopicSessioncreateTopic(String topicName) または QueueSessioncreateQueue(String queueName) メソッドを使用して、デフォルトの宛先プロパティで新しい宛先を作成することです。ベンダーの実装に応じて、DynamicDestinationResolver は 1 つだけを解決するのではなく、物理的送信先を作成することもできます。

ブールプロパティ pubSubDomain は、使用されている JMS ドメインの知識で JmsTemplate を構成するために使用されます。デフォルトでは、このプロパティの値は false であり、ポイントツーポイントドメイン Queues が使用されることを示します。このプロパティ(JmsTemplate で使用)は、DestinationResolver インターフェースの実装による動的な宛先解決の動作を決定します。

プロパティ defaultDestination を使用して、デフォルトの宛先で JmsTemplate を構成することもできます。デフォルトの宛先は、特定の宛先を参照しない送信および受信操作です。

3.1.4. メッセージリスナコンテナー

EJB における JMS メッセージの最も一般的な用途の 1 つは、メッセージ駆動型 Bean(MDB)を駆動することです。Spring は、ユーザーを EJB コンテナーに関連付けない方法でメッセージ駆動型 POJO(MDP)を作成するソリューションを提供します。(Spring の MDP サポートの詳細については、非同期受信 : メッセージ駆動型 POJO を参照してください)Spring Framework 4.1, エンドポイントメソッドには @JmsListener でアノテーションを付けることができるため、詳細についてはアノテーション駆動型のリスナーエンドポイントを参照してください。

メッセージリスナコンテナーは、JMS メッセージキューからメッセージを受信し、そこに挿入される MessageListener を駆動するために使用されます。リスナーコンテナーは、メッセージ受信のすべてのスレッド化を処理し、処理のためにリスナーにディスパッチします。メッセージリスナコンテナーは、MDP とメッセージングプロバイダ間の仲介者であり、メッセージの受信登録、トランザクションへの参加、リソースの取得と解放、例外変換などを処理します。これにより、メッセージの受信(および場合によっては応答)に関連する(場合によっては複雑な)ビジネスロジックを記述でき、JMS インフラストラクチャに関する定型的な問題をフレームワークに委譲できます。

Spring にパッケージ化された 2 つの標準 JMS メッセージリスナーコンテナーがあり、それぞれに専用の機能セットがあります。

SimpleMessageListenerContainer を使用する

このメッセージリスナコンテナーは、2 つの標準フレーバのうち単純なものです。起動時に一定数の JMS セッションとコンシューマーを作成し、標準の JMS MessageConsumer.setMessageListener() メソッドを使用してリスナーを登録し、リスナーコールバックを実行するために JMS プロバイダーに任せます。このバリアントでは、ランタイムの要求に動的に適応したり、外部管理トランザクションに参加したりすることはできません。互換性に関しては、スタンドアロン JMS 仕様の精神に非常に近いままですが、一般的に Java EE の JMS 制限と互換性がありません。

SimpleMessageListenerContainer は外部管理トランザクションへの参加を許可しませんが、ネイティブ JMS トランザクションをサポートします。この機能を有効にするには、sessionTransacted フラグを true に切り替えるか、XML 名前空間で acknowledge 属性を transacted に設定します。リスナーからスローされた例外はロールバックにつながり、メッセージが再配信されます。または、CLIENT_ACKNOWLEDGE モードの使用を検討してください。CLIENT_ACKNOWLEDGE モードは、例外の場合にも再配信を提供しますが、トランザクション Session インスタンスを使用しないため、トランザクションプロトコルに他の Session 操作(レスポンスメッセージの送信など)は含まれません。
デフォルトの AUTO_ACKNOWLEDGE モードは、適切な信頼性の保証を提供しません。リスナーの実行が失敗すると(プロバイダーがリスナー呼び出し後に各メッセージを自動的に確認するため、プロバイダーに例外が伝播されないため)、またはリスナーコンテナーがシャットダウンすると(acceptMessagesWhileStopping フラグを設定することでこれを構成できます)メッセージは失われます。信頼性が必要な場合は、トランザクションセッションを必ず使用してください(たとえば、信頼性の高いキュー処理や永続的なトピックサブスクリプションなど)。
DefaultMessageListenerContainer を使用する

ほとんどの場合、このメッセージリスナコンテナーが使用されます。SimpleMessageListenerContainer とは対照的に、このコンテナーバリアントは、実行時の要求に動的に適応させることができ、外部で管理されたトランザクションに参加できます。JtaTransactionManager で構成された場合、受信した各メッセージは XA トランザクションに登録されます。その結果、処理は XA トランザクションのセマンティクスを利用できます。このリスナーコンテナーは、JMS プロバイダーの低い要件、高度な機能(外部管理トランザクションへの参加など)、および Java EE 環境との互換性のバランスが取れています。

コンテナーのキャッシュレベルをカスタマイズできます。キャッシュが有効になっていない場合、メッセージの受信ごとに新しい接続と新しいセッションが作成されることに注意してください。これを高負荷の非永続サブスクリプションと組み合わせると、メッセージが失われる可能性があります。そのような場合は、適切なキャッシュレベルを使用してください。

このコンテナーには、ブローカーがダウンしたときに回復可能な機能もあります。デフォルトでは、簡単な BackOff 実装は 5 秒ごとに再試行します。より詳細な回復オプションのために、カスタム BackOff 実装を指定できます。例については、api-spring-framework/util/backoff/ExponentialBackOff.html [ExponentialBackOff] を参照してください。

兄弟(SimpleMessageListenerContainer)と同様に、DefaultMessageListenerContainer はネイティブ JMS トランザクションをサポートし、確認モードのカスタマイズを可能にします。シナリオに適している場合、これは外部管理トランザクションよりも強くお勧めします。つまり、JVM が死んだ場合に時折重複するメッセージで生きることができる場合です。ビジネスロジックのカスタム重複メッセージ検出手順は、そのような状況をカバーできます。たとえば、ビジネスエンティティの存在チェックまたはプロトコルテーブルチェックの形式です。そのような取り決めは、代替手段よりもはるかに効率的です。JMS メッセージの受信とメッセージリスナーのビジネスロジックの実行をカバーするために、処理全体を XA トランザクションでラップします(DefaultMessageListenerContainer を JtaTransactionManager で構成することにより)(データベース操作などを含む)。
デフォルトの AUTO_ACKNOWLEDGE モードは、適切な信頼性の保証を提供しません。リスナーの実行が失敗すると(プロバイダーがリスナー呼び出し後に各メッセージを自動的に確認するため、プロバイダーに例外が伝播されないため)、またはリスナーコンテナーがシャットダウンすると(acceptMessagesWhileStopping フラグを設定することでこれを構成できます)メッセージは失われます。信頼性が必要な場合は、トランザクションセッションを必ず使用してください(たとえば、信頼性の高いキュー処理や永続的なトピックサブスクリプションなど)。

3.1.5. トランザクション管理

Spring は、単一の JMS ConnectionFactory のトランザクションを管理する JmsTransactionManager を提供します。これにより、データアクセスの章のトランザクション管理セクションに従って、JMS アプリケーションは Spring のマネージドトランザクション機能を活用できます。JmsTransactionManager はローカルリソーストランザクションを実行し、指定された ConnectionFactory からの JMS 接続 / セッションペアをスレッドにバインドします。JmsTemplate は、このようなトランザクションリソースを自動的に検出し、それに応じて動作します。

Java EE 環境では、ConnectionFactory は接続およびセッションインスタンスをプールするため、これらのリソースはトランザクション全体で効率的に再利用されます。スタンドアロン環境では、Spring の SingleConnectionFactory を使用すると、各 JMS Connection が共有され、各トランザクションは独自の独立した Session を持ちます。または、ActiveMQ の PooledConnectionFactory クラスなど、プロバイダー固有のプーリングアダプターの使用を検討してください。

JmsTemplate を JtaTransactionManager および XA 対応 JMS ConnectionFactory とともに使用して、分散トランザクションを実行することもできます。これには、JTA トランザクションマネージャーと、適切に XA が構成された ConnectionFactory を使用する必要があることに注意してください。(Java EE サーバーまたは JMS プロバイダーのドキュメントを確認してください。)

JMS API を使用して Connection から Session を作成する場合、管理されたトランザクション環境と管理されていないトランザクション環境でコードを再利用すると混乱する可能性があります。これは、JMS API には Session を作成するファクトリメソッドが 1 つしかなく、トランザクションモードと確認応答モードの値が必要です。管理された環境では、これらの値の設定は環境のトランザクションインフラストラクチャの責任であるため、これらの値はベンダーの JMS 接続へのラッパーによって無視されます。管理されていない環境で JmsTemplate を使用する場合、プロパティ sessionTransacted および sessionAcknowledgeMode を使用してこれらの値を指定できます。JmsTemplate で PlatformTransactionManager を使用する場合、テンプレートには常にトランザクション JMS JMS Session が与えられます。

3.2. メッセージを送る

JmsTemplate には、メッセージを送信するための多くの便利なメソッドが含まれています。送信メソッドは、javax.jms.Destination オブジェクトを使用して宛先を指定し、他のメソッドは JNDI ルックアップで String を使用して宛先を指定します。宛先引数を取らない send メソッドは、デフォルトの宛先を使用します。

次の例では、MessageCreator コールバックを使用して、提供された Session オブジェクトからテキストメッセージを作成します。

import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.Session;

import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.JmsTemplate;

public class JmsQueueSender {

    private JmsTemplate jmsTemplate;
    private Queue queue;

    public void setConnectionFactory(ConnectionFactory cf) {
        this.jmsTemplate = new JmsTemplate(cf);
    }

    public void setQueue(Queue queue) {
        this.queue = queue;
    }

    public void simpleSend() {
        this.jmsTemplate.send(this.queue, new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                return session.createTextMessage("hello queue world");
            }
        });
    }
}

前の例では、JmsTemplate は、参照を ConnectionFactory に渡すことによって構築されます。別の方法として、引数なしのコンストラクターと connectionFactory が提供され、JavaBean スタイルでインスタンスを構築するために使用できます(BeanFactory またはプレーン Java コードを使用)。または、Spring の JmsGatewaySupport コンビニエンス基本クラスから派生することを検討してください。この基本クラスは、JMS 構成用に事前作成された Bean プロパティを提供します。

send(String destinationName, MessageCreator creator) メソッドを使用すると、宛先の文字列名を使用してメッセージを送信できます。これらの名前が JNDI に登録されている場合、テンプレートの destinationResolver プロパティを JndiDestinationResolver のインスタンスに設定する必要があります。

JmsTemplate を作成し、デフォルトの宛先を指定した場合、send(MessageCreator c) はその宛先にメッセージを送信します。

3.2.1. メッセージコンバーターの使用

ドメインモデルオブジェクトの送信を容易にするために、JmsTemplate には、メッセージのデータコンテンツの引数として Java オブジェクトを取るさまざまな送信メソッドがあります。JmsTemplate のオーバーロードメソッド convertAndSend() および receiveAndConvert() メソッドは、変換プロセスを MessageConverter インターフェースのインスタンスに委譲します。このインターフェースは、Java オブジェクトと JMS メッセージ間で変換する単純な契約を定義します。デフォルトの実装(SimpleMessageConverter)は、String と TextMessagebyte[] と BytesMesssage、および java.util.Map と MapMessage の間の変換をサポートしています。コンバーターを使用することにより、ユーザーとアプリケーションコードは、JMS を介して送信または受信されるビジネスオブジェクトに焦点を当てることができ、JMS メッセージとしてどのように表現されるかの詳細に関心を持ちません。

現在、サンドボックスには MapMessageConverter が含まれています。MapMessageConverter は、リフレクションを使用して JavaBean と MapMessage を変換します。自分で実装できる他の一般的な実装の選択肢は、既存の XML マーシャリングパッケージ(JAXB や XStream など)を使用してオブジェクトを表す TextMessage を作成するコンバーターです。

コンバータークラス内に一般的にカプセル化できないメッセージのプロパティ、ヘッダー、および本文の設定に対応するために、MessagePostProcessor インターフェースでは、変換後、送信前にメッセージにアクセスできます。次の例は、java.util.Map がメッセージに変換された後にメッセージヘッダーとプロパティを変更する方法を示しています。

public void sendWithConversion() {
    Map map = new HashMap();
    map.put("Name", "Mark");
    map.put("Age", new Integer(47));
    jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
        public Message postProcessMessage(Message message) throws JMSException {
            message.setIntProperty("AccountID", 1234);
            message.setJMSCorrelationID("123-00001");
            return message;
        }
    });
}

これにより、次の形式のメッセージが生成されます。

MapMessage={
    Header={
        ... standard headers ...
        CorrelationID={123-00001}
    }
    Properties={
        AccountID={Integer:1234}
    }
    Fields={
        Name={String:Mark}
        Age={Integer:47}
    }
}

3.2.2. SessionCallback および ProducerCallback の使用

送信操作は多くの一般的な使用シナリオをカバーしますが、JMS Session または MessageProducer で複数の操作を実行したい場合があります。SessionCallback および ProducerCallback は、それぞれ JMS Session および Session / MessageProducer ペアを公開します。JmsTemplate の execute() メソッドは、これらのコールバックメソッドを実行します。

3.3. メッセージを受信する

これは、Spring で JMS を使用してメッセージを受信する方法を説明しています。

3.3.1. 同期受信

通常、JMS は非同期処理に関連付けられていますが、メッセージを同期的に消費できます。オーバーロードされた receive(..) メソッドは、この機能を提供します。同期受信中、呼び出しスレッドはメッセージが利用可能になるまでブロックします。これは、呼び出しスレッドが潜在的に無期限にブロックされる可能性があるため、危険な操作になる可能性があります。receiveTimeout プロパティは、メッセージの待機をあきらめるまで受信者が待機する時間を指定します。

3.3.2. 非同期受信 : メッセージ駆動型 POJO

Spring は、@JmsListener アノテーションを使用してアノテーション付きリスナーエンドポイントもサポートし、エンドポイントをプログラムで登録するためのオープンなインフラストラクチャを提供します。これは、非同期レシーバーをセットアップする最も便利な方法です。詳細については、リスナーエンドポイントアノテーションを有効にするを参照してください。

EJB の世界のメッセージ駆動型 Bean(MDB)と同様の方法で、メッセージ駆動型 POJO(MDP)は JMS メッセージのレシーバーとして機能します。MDP の 1 つの制限(ただし MessageListenerAdapter を使用するを参照)は、javax.jms.MessageListener インターフェースを実装する必要があるということです。POJO が複数のスレッドでメッセージを受信する場合、実装がスレッドセーフであることを確認することが重要です。

次の例は、MDP の簡単な実装を示しています。

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class ExampleListener implements MessageListener {

    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            try {
                System.out.println(((TextMessage) message).getText());
            }
            catch (JMSException ex) {
                throw new RuntimeException(ex);
            }
        }
        else {
            throw new IllegalArgumentException("Message must be of type TextMessage");
        }
    }
}

MessageListener を実装したら、メッセージリスナーコンテナーを作成します。

次の例は、Spring(この場合は DefaultMessageListenerContainer)に同梱されているメッセージリスナーコンテナーの 1 つを定義および構成する方法を示しています。

<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="jmsexample.ExampleListener"/>

<!-- and this is the message listener container -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destination" ref="destination"/>
    <property name="messageListener" ref="messageListener"/>
</bean>

各実装でサポートされる機能の詳細な説明については、さまざまなメッセージリスナーコンテナー(すべて MessageListenerContainer(Javadoc) を実装)の Spring javadoc を参照してください。

3.3.3. SessionAwareMessageListener インターフェースの使用

SessionAwareMessageListener インターフェースは、JMS MessageListener インターフェースと同様の契約を提供する Spring 固有のインターフェースですが、Message の受信元である JMS Session へのメッセージ処理メソッドアクセスも提供します。次のリストは、SessionAwareMessageListener インターフェースの定義を示しています。

package org.springframework.jms.listener;

public interface SessionAwareMessageListener {

    void onMessage(Message message, Session session) throws JMSException;
}

MDP が(onMessage(Message, Session) メソッドで提供される Session を使用して)受信したメッセージに応答できるようにする場合は、MDP にこのインターフェースを実装することを選択できます(標準 JMS MessageListener インターフェースに優先)。Spring に同梱されているすべてのメッセージリスナーコンテナーの実装は、MessageListener または SessionAwareMessageListener インターフェースのいずれかを実装する MDP をサポートしています。SessionAwareMessageListener を実装するクラスには、インターフェースを介して Spring に結び付けられるという警告が付いています。使用するかどうかの選択は、アプリケーション開発者またはアーキテクトとして完全にあなたに任されています。

SessionAwareMessageListener インターフェースの onMessage(..) メソッドは JMSException をスローすることに注意してください。標準の JMS MessageListener インターフェースとは異なり、SessionAwareMessageListener インターフェースを使用する場合、スローされた例外を処理するのはクライアントコードの責任です。

3.3.4. MessageListenerAdapter を使用する

MessageListenerAdapter クラスは、Spring の非同期メッセージングサポートの最後のコンポーネントです。簡単に言えば、ほぼすべてのクラスを MDP として公開できます(ただし、いくつかの制約があります)。

次のインターフェース定義を検討してください。

public interface MessageDelegate {

    void handleMessage(String message);

    void handleMessage(Map message);

    void handleMessage(byte[] message);

    void handleMessage(Serializable message);
}

インターフェースは MessageListener インターフェースも SessionAwareMessageListener インターフェースも拡張しませんが、MessageListenerAdapter クラスを使用することにより、MDP として使用できることに注意してください。また、受信および処理できるさまざまな Message タイプの内容に従って、さまざまなメッセージ処理メソッドがどのように強く型付けされているかに注意してください。

次に、MessageDelegate インターフェースの以下の実装を検討してください。

public class DefaultMessageDelegate implements MessageDelegate {
    // implementation elided for clarity...
}

特に、MessageDelegate インターフェースの前の実装(DefaultMessageDelegate クラス)に JMS 依存関係がまったくないことに注意してください。これは、次の構成を介して MDP を作成できる POJO です。

<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
    <constructor-arg>
        <bean class="jmsexample.DefaultMessageDelegate"/>
    </constructor-arg>
</bean>

<!-- and this is the message listener container... -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destination" ref="destination"/>
    <property name="messageListener" ref="messageListener"/>
</bean>

次の例は、JMS TextMessage メッセージの受信のみを処理できる別の MDP を示しています。メッセージ処理メソッドが実際に receive と呼ばれることに注意してください(MessageListenerAdapter のメッセージ処理メソッドの名前はデフォルトで handleMessage になります)が、構成可能です(このセクションで後述します)。また、receive(..) メソッドが JMS TextMessage メッセージのみを受信して応答するように厳密に入力されていることに注意してください。次のリストは、TextMessageDelegate インターフェースの定義を示しています。

public interface TextMessageDelegate {

    void receive(TextMessage message);
}

次のリストは、TextMessageDelegate インターフェースを実装するクラスを示しています。

public class DefaultTextMessageDelegate implements TextMessageDelegate {
    // implementation elided for clarity...
}

アテンダント MessageListenerAdapter の構成は次のようになります。

<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
    <constructor-arg>
        <bean class="jmsexample.DefaultTextMessageDelegate"/>
    </constructor-arg>
    <property name="defaultListenerMethod" value="receive"/>
    <!-- we don't want automatic message context extraction -->
    <property name="messageConverter">
        <null/>
    </property>
</bean>

messageListener が TextMessage 以外のタイプの JMS Message を受信した場合、IllegalStateException がスローされる(その後、飲み込まれる)ことに注意してください。MessageListenerAdapter クラスのもう 1 つの機能は、ハンドラーメソッドが非 void 値を返す場合に、自動的にレスポンス Message を送り返す機能です。次のインターフェースとクラスを検討してください。

public interface ResponsiveTextMessageDelegate {

    // notice the return type...
    String receive(TextMessage message);
}
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
    // implementation elided for clarity...
}

DefaultResponsiveTextMessageDelegate を MessageListenerAdapter と組み合わせて使用する場合、'receive(..)' メソッドの実行から返される null 以外の値は(デフォルト構成で) TextMessage に変換されます。結果の TextMessage は、元の Message の JMS Reply-To プロパティで定義された Destination (存在する場合)または MessageListenerAdapter に設定されたデフォルト Destination (設定されている場合)に送信されます。Destination が見つからない場合、InvalidDestinationException がスローされます(この例外は飲み込まれず、呼び出しスタックに伝播することに注意してください)。

3.3.5. トランザクション内のメッセージの処理

トランザクション内でメッセージリスナーを呼び出すには、リスナーコンテナーの再構成のみが必要です。

リスナーコンテナー定義の sessionTransacted フラグを使用して、ローカルリソーストランザクションをアクティブ化できます。各メッセージリスナ呼び出しは、アクティブな JMS トランザクション内で動作し、リスナの実行に失敗した場合にメッセージ受信がロールバックされます。(SessionAwareMessageListener を介した)レスポンスメッセージの送信は同じローカルトランザクションの一部ですが、他のリソース操作(データベースアクセスなど)は独立して動作します。これには通常、データベース処理はコミットされましたがメッセージ処理がコミットに失敗した場合に対応するために、リスナー実装での重複メッセージの検出が必要です。

以下の Bean 定義を考慮してください。

<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destination" ref="destination"/>
    <property name="messageListener" ref="messageListener"/>
    <property name="sessionTransacted" value="true"/>
</bean>

外部管理トランザクションに参加するには、トランザクションマネージャーを構成し、外部管理トランザクションをサポートするリスナーコンテナー(通常は DefaultMessageListenerContainer)を使用する必要があります。

XA トランザクション参加用のメッセージリスナーコンテナーを構成するには、JtaTransactionManager (デフォルトでは、Java EE サーバーのトランザクションサブシステムに委譲する)を構成します。基礎となる JMS ConnectionFactory は XA 対応であり、JTA トランザクションコーディネーターに適切に登録される必要があることに注意してください。(Java EE サーバーの JNDI リソースの設定を確認してください)これにより、メッセージ受信と(たとえば)データベースアクセスを同じトランザクションの一部にすることができます(XA トランザクションログのオーバーヘッドを犠牲にして、コミットセマンティクスを統一)。

次の Bean 定義は、トランザクションマネージャーを作成します。

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

次に、以前のコンテナー構成に追加する必要があります。コンテナーが残りを処理します。次の例は、その方法を示しています。

<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destination" ref="destination"/>
    <property name="messageListener" ref="messageListener"/>
    <property name="transactionManager" ref="transactionManager"/> (1)
</bean>
1 トランザクションマネージャー。

3.4. JCA メッセージエンドポイントのサポート

バージョン 2.5 から、Spring は JCA ベースの MessageListener コンテナーのサポートも提供します。JmsMessageEndpointManager は、プロバイダーの ResourceAdapter クラス名から ActivationSpec クラス名を自動的に決定しようとします。次の例に示すように、通常 Spring の汎用 JmsActivationSpecConfig を提供することができます。

<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
    <property name="resourceAdapter" ref="resourceAdapter"/>
    <property name="activationSpecConfig">
        <bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
            <property name="destinationName" value="myQueue"/>
        </bean>
    </property>
    <property name="messageListener" ref="myMessageListener"/>
</bean>

または、特定の ActivationSpec オブジェクトで JmsMessageEndpointManager をセットアップできます。ActivationSpec オブジェクトは、(<jee:jndi-lookup> を使用して)JNDI ルックアップから取得することもできます。次の例は、その方法を示しています。

<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
    <property name="resourceAdapter" ref="resourceAdapter"/>
    <property name="activationSpec">
        <bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
            <property name="destination" value="myQueue"/>
            <property name="destinationType" value="javax.jms.Queue"/>
        </bean>
    </property>
    <property name="messageListener" ref="myMessageListener"/>
</bean>

Spring の ResourceAdapterFactoryBean を使用して、次の例に示すように、ターゲット ResourceAdapter をローカルに構成できます。

<bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
    <property name="resourceAdapter">
        <bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
            <property name="serverUrl" value="tcp://localhost:61616"/>
        </bean>
    </property>
    <property name="workManager">
        <bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
    </property>
</bean>

指定された WorkManager は、通常は SimpleTaskWorkManager インスタンスの asyncTaskExecutor プロパティを介して、環境固有のスレッドプールを指すこともできます。複数のアダプターを使用する場合は、すべての ResourceAdapter インスタンスに共有スレッドプールを定義することを検討してください。

一部の環境(WebLogic 9 以上など)では、代わりに(<jee:jndi-lookup> を使用して)JNDI から ResourceAdapter オブジェクト全体を取得できます。Spring ベースのメッセージリスナーは、サーバーがホストする ResourceAdapter と対話できます。サーバーがホストする ResourceAdapter もサーバーの組み込み WorkManager を使用します。

詳細については、JmsMessageEndpointManager(Javadoc) JmsActivationSpecConfig(Javadoc) 、および ResourceAdapterFactoryBean(Javadoc) の javadoc を参照してください。

Spring は、JMS に関連付けられていない汎用 JCA メッセージエンドポイントマネージャー org.springframework.jca.endpoint.GenericMessageEndpointManager も提供します。このコンポーネントでは、メッセージリスナタイプ(CCI MessageListener など)およびプロバイダ固有の ActivationSpec オブジェクトを使用できます。JCA プロバイダーのドキュメントを参照してコネクターの実際の機能を確認し、Spring 固有の構成の詳細については GenericMessageEndpointManager(Javadoc) javadoc を参照してください。

JCA ベースのメッセージエンドポイント管理は、EJB 2.1 メッセージ駆動型 Bean に非常に似ています。同じ基になるリソースプロバイダー契約を使用します。EJB 2.1 MDB と同様に、Spring コンテキストでも JCA プロバイダーでサポートされている任意のメッセージリスナーインターフェースを使用できます。それでも、Spring は、JMS の明示的な「便利な」サポートを提供します。これは、JMS が JCA エンドポイント管理契約で使用される最も一般的なエンドポイント API であるためです。

3.5. アノテーション駆動型のリスナーエンドポイント

メッセージを非同期で受信する最も簡単な方法は、アノテーション付きのリスナーエンドポイントインフラストラクチャを使用することです。簡単に言えば、管理対象の Bean のメソッドを JMS リスナーエンドポイントとして公開できます。次の例は、その使用方法を示しています。

@Component
public class MyService {

    @JmsListener(destination = "myDestination")
    public void processOrder(String data) { ... }
}

前の例の考え方は、メッセージが javax.jms.DestinationmyDestination で利用可能な場合は常に、processOrder メソッドがそれに応じて呼び出されることです(この場合、MessageListenerAdapter が提供するものと同様の JMS メッセージの内容で)。

アノテーション付きエンドポイントインフラストラクチャは、JmsListenerContainerFactory を使用して、アノテーション付きメソッドごとに背後でメッセージリスナーコンテナーを作成します。このようなコンテナーは、アプリケーションコンテキストに対して登録されていませんが、JmsListenerEndpointRegistry Bean を使用して管理目的で簡単に見つけることができます。

@JmsListener は Java 8 の繰り返し可能なアノテーションであるため、@JmsListener 宣言を追加することにより、複数の JMS 宛先を同じメソッドに関連付けることができます。

3.5.1. リスナーエンドポイントアノテーションを有効にする

@JmsListener アノテーションのサポートを有効にするには、次の例に示すように、@EnableJms を @Configuration クラスの 1 つに追加できます。

@Configuration
@EnableJms
public class AppConfig {

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        factory.setDestinationResolver(destinationResolver());
        factory.setSessionTransacted(true);
        factory.setConcurrency("3-10");
        return factory;
    }
}

デフォルトでは、インフラストラクチャは、メッセージリスナーコンテナーの作成に使用するファクトリのソースとして、jmsListenerContainerFactory という名前の Bean を探します。この場合(および JMS インフラストラクチャのセットアップを無視して)、3 スレッドのコアポーリングサイズと 10 スレッドの最大プールサイズで processOrder メソッドを呼び出すことができます。

リスナーコンテナーファクトリをカスタマイズして各アノテーションに使用するか、JmsListenerConfigurer インターフェースを実装して明示的なデフォルトを設定できます。デフォルトは、特定のコンテナーファクトリなしで少なくとも 1 つのエンドポイントが登録されている場合にのみ必要です。詳細と例については、JmsListenerConfigurer(Javadoc) を実装するクラスの javadoc を参照してください。

XML 構成を好む場合、次の例に示すように、<jms:annotation-driven> 要素を使用できます。

<jms:annotation-driven/>

<bean id="jmsListenerContainerFactory"
        class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
    <property name="connectionFactory" ref="connectionFactory"/>
    <property name="destinationResolver" ref="destinationResolver"/>
    <property name="sessionTransacted" value="true"/>
    <property name="concurrency" value="3-10"/>
</bean>

3.5.2. プログラムによるエンドポイント登録

JmsListenerEndpoint は、JMS エンドポイントのモデルを提供し、そのモデルのコンテナーの構成を担当します。このインフラストラクチャにより、JmsListener アノテーションによって検出されるエンドポイントに加えて、プログラムでエンドポイントを構成できます。次の例は、その方法を示しています。

@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {

    @Override
    public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
        SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
        endpoint.setId("myJmsEndpoint");
        endpoint.setDestination("anotherQueue");
        endpoint.setMessageListener(message -> {
            // processing
        });
        registrar.registerEndpoint(endpoint);
    }
}

前の例では、実際の MessageListener を呼び出して提供する SimpleJmsListenerEndpoint を使用しました。ただし、独自のエンドポイントバリアントを作成して、カスタム呼び出しメカニズムを記述することもできます。

@JmsListener の使用を完全にスキップし、JmsListenerConfigurer を介してエンドポイントのみをプログラムで登録できることに注意してください。

3.5.3. アノテーション付きエンドポイントメソッドシグネチャー

これまで、エンドポイントに単純な String を注入していましたが、実際には非常に柔軟なメソッドシグネチャーを持つことができます。次の例では、Order にカスタムヘッダーを挿入するように書き換えます。

@Component
public class MyService {

    @JmsListener(destination = "myDestination")
    public void processOrder(Order order, @Header("order_type") String orderType) {
        ...
    }
}

JMS リスナーエンドポイントに挿入できる主な要素は次のとおりです。

  • 生の javax.jms.Message またはそのサブクラス(受信メッセージタイプと一致する場合)。

  • ネイティブ JMS API へのオプションのアクセス用の javax.jms.Session (たとえば、カスタム応答を送信するため)。

  • 受信 JMS メッセージを表す org.springframework.messaging.Message。このメッセージには、カスタムヘッダーと標準ヘッダー(JmsHeaders で定義されている)の両方が含まれていることに注意してください。

  • @Header - 標準の JMS ヘッダーを含む特定のヘッダー値を抽出するためのアノテーション付きメソッド引数。

  • すべてのヘッダーにアクセスするために java.util.Map にも割り当て可能でなければならない @Headers アノテーション付き引数。

  • サポートされているタイプ(Message または Session)のいずれでもない非アノテーション付き要素は、ペイロードと見なされます。パラメーターに @Payload のアノテーションを付けることにより、明示的にすることができます。@Valid を追加して検証をオンにすることもできます。

Spring の Message 抽象化を挿入する機能は、トランスポート固有の API に依存することなく、トランスポート固有のメッセージに格納されているすべての情報を活用するのに特に役立ちます。次の例は、その方法を示しています。

@JmsListener(destination = "myDestination")
public void processOrder(Message<Order> order) { ... }

メソッド引数の処理は DefaultMessageHandlerMethodFactory によって提供され、追加のメソッド引数をサポートするためにさらにカスタマイズできます。変換と検証のサポートもそこでカスタマイズできます。

たとえば、処理する前に Order が有効であることを確認したい場合、次の例に示すように、ペイロードに @Valid でアノテーションを付け、必要なバリデーターを構成できます。

@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {

    @Override
    public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
        registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
    }

    @Bean
    public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
        DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
        factory.setValidator(myValidator());
        return factory;
    }
}

3.5.4. レスポンス管理

MessageListenerAdapter の既存のサポートにより、メソッドは void 以外の戻り値型をすでに持つことができます。その場合、呼び出しの結果は javax.jms.Message にカプセル化され、元のメッセージの JMSReplyTo ヘッダーで指定された宛先、またはリスナーに設定されたデフォルトの宛先のいずれかに送信されます。メッセージング抽象化の @SendTo アノテーションを使用して、デフォルトの宛先を設定できるようになりました。

processOrder メソッドが OrderStatus を返すようになったと仮定すると、次の例に示すように、自動的にレスポンスを送信するように記述できます。

@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
    // order processing
    return status;
}
@JmsListener アノテーション付きメソッドが複数ある場合は、@SendTo アノテーションをクラスレベルに配置して、デフォルトの返信先を共有することもできます。

トランスポートに依存しない方法で追加のヘッダーを設定する必要がある場合は、次のような方法で代わりに Message を返すことができます。

@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
    // order processing
    return MessageBuilder
            .withPayload(status)
            .setHeader("code", 1234)
            .build();
}

実行時にレスポンスの宛先を計算する必要がある場合は、実行時に使用する宛先も提供する JmsResponse インスタンスにレスポンスをカプセル化できます。前の例を次のように書き換えることができます。

@JmsListener(destination = "myDestination")
public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
    // order processing
    Message<OrderStatus> response = MessageBuilder
            .withPayload(status)
            .setHeader("code", 1234)
            .build();
    return JmsResponse.forQueue(response, "status");
}

最後に、優先度や存続時間など、レスポンスにいくつかの QoS 値を指定する必要がある場合、次の例に示すように、それに応じて JmsListenerContainerFactory を構成できます。

@Configuration
@EnableJms
public class AppConfig {

    @Bean
    public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        QosSettings replyQosSettings = new QosSettings();
        replyQosSettings.setPriority(2);
        replyQosSettings.setTimeToLive(10000);
        factory.setReplyQosSettings(replyQosSettings);
        return factory;
    }
}

3.6. JMS 名前空間のサポート

Spring は、JMS 構成を簡素化するための XML 名前空間を提供します。JMS 名前空間要素を使用するには、次の例に示すように、JMS スキーマを参照する必要があります。

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

    <!-- bean definitions here -->

</beans>
1JMS スキーマの参照。

名前空間は、<annotation-driven/><listener-container/> と <jca-listener-container/> の 3 つのトップレベル要素で構成されています。<annotation-driven/> は、アノテーション駆動型のリスナーエンドポイントの使用を可能にします。<listener-container/> および <jca-listener-container/> は、共有リスナーコンテナー構成を定義し、<listener/> 子要素を含めることができます。次の例は、2 つのリスナーの基本的な構成を示しています。

<jms:listener-container>

    <jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>

    <jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>

</jms:listener-container>

上記の例は、MessageListenerAdapter を使用するに示すように、2 つの異なるリスナーコンテナー Bean 定義と 2 つの異なる MessageListenerAdapter Bean 定義を作成することと同等です。前の例に示した属性に加えて、listener 要素にはいくつかのオプションのものを含めることができます。次の表に、使用可能なすべての属性を示します。

表 3: JMS <listener> 要素の属性
属性 説明

id

ホスティングリスナーコンテナーの Bean 名。指定しない場合、Bean 名が自動的に生成されます。

destination (必須)

DestinationResolver 戦略によって解決された、このリスナーの宛先名。

ref (必須)

ハンドラーオブジェクトの Bean 名。

method

呼び出すハンドラーメソッドの名前。ref 属性が MessageListener または Spring SessionAwareMessageListener を指している場合、この属性は省略できます。

response-destination

レスポンスメッセージを送信するデフォルトのレスポンス宛先の名前。これは、JMSReplyTo フィールドを含まないリクエストメッセージの場合に適用されます。この宛先のタイプは、リスナーコンテナーの response-destination-type 属性によって決定されます。これは、各結果オブジェクトがレスポンスメッセージに変換される戻り値を持つリスナーメソッドにのみ適用されることに注意してください。

subscription

永続サブスクリプションの名前(ある場合)。

selector

このリスナーのオプションのメッセージセレクター。

concurrency

このリスナーに対して開始する同時セッションまたはコンシューマーの数。この値は、最大数を示す単純な数値(たとえば、5)、または下限と上限を示す範囲(たとえば、3-5)のいずれかです。指定された最小値はヒントにすぎず、実行時に無視される場合があることに注意してください。デフォルトは、コンテナーによって提供される値です。

<listener-container/> 要素は、いくつかのオプション属性も受け入れます。これにより、さまざまな戦略(taskExecutor や destinationResolver など)のカスタマイズ、および基本的な JMS 設定とリソース参照が可能になります。これらの属性を使用することにより、名前空間の利便性を活用しながら、高度にカスタマイズされたリスナーコンテナーを定義できます。

次の例に示すように、Bean の id を指定して factory-id 属性を介して公開することにより、そのような設定を JmsListenerContainerFactory として自動的に公開できます。

<jms:listener-container connection-factory="myConnectionFactory"
        task-executor="myTaskExecutor"
        destination-resolver="myDestinationResolver"
        transaction-manager="myTransactionManager"
        concurrency="10">

    <jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>

    <jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>

</jms:listener-container>

次の表に、使用可能なすべての属性を示します。個々のプロパティの詳細については、AbstractMessageListenerContainer(Javadoc) のクラスレベルの javadoc およびその具象サブクラスを参照してください。javadoc では、トランザクションの選択とメッセージの再配信シナリオについても説明しています。

表 4: JMS <listener-container> 要素の属性
属性 説明

container-type

このリスナーコンテナーのタイプ。使用可能なオプションは defaultsimpledefault102 または simple102 です(デフォルトのオプションは default です)。

container-class

完全修飾クラス名としてのカスタムリスナーコンテナー実装クラス。デフォルトは、container-type 属性に応じて、Spring の標準 DefaultMessageListenerContainer または SimpleMessageListenerContainer です。

factory-id

指定された id を持つ JmsListenerContainerFactory としてこの要素で定義された設定を公開し、他のエンドポイントで再利用できるようにします。

connection-factory

JMS ConnectionFactory Bean への参照(デフォルトの Bean 名は connectionFactory です)。

task-executor

JMS リスナー呼び出し側の Spring TaskExecutor への参照。

destination-resolver

JMS Destination インスタンスを解決するための DestinationResolver 戦略への参照。

message-converter

JMS メッセージをリスナーメソッドの引数に変換するための MessageConverter 戦略への参照。デフォルトは SimpleMessageConverter です。

error-handler

MessageListener の実行中に発生する可能性のあるキャッチされない例外を処理するための ErrorHandler 戦略への参照。

destination-type

このリスナーの JMS 宛先タイプ: queuetopicdurableTopicsharedTopic または sharedDurableTopic。これにより、コンテナーの pubSubDomainsubscriptionDurable および subscriptionShared プロパティが潜在的に有効になります。デフォルトは queue (これらの 3 つのプロパティを無効にします)。

response-destination-type

レスポンスの JMS 宛先タイプ: queue または topic。デフォルトは、destination-type 属性の値です。

client-id

このリスナーコンテナーの JMS クライアント ID。永続サブスクリプションを使用する場合は、指定する必要があります。

cache

JMS リソースのキャッシュレベル: noneconnectionsessionconsumer または auto デフォルト(auto)では、外部トランザクションマネージャーが指定されていない限り、キャッシュレベルは実質的に consumer です。その場合、有効なデフォルトは none (Java EE スタイルのトランザクション管理で、指定された ConnectionFactory は XA 対応です)プール)。

acknowledge

ネイティブ JMS 確認モード: autoclientdups-ok または transactedtransacted の値は、ローカルに処理された Session をアクティブにします。別の方法として、後の表で説明する transaction-manager 属性を指定できます。デフォルトは auto です。

transaction-manager

外部 PlatformTransactionManager (通常は Spring の JtaTransactionManager などの XA ベースのトランザクションコーディネーター)への参照。指定しない場合、ネイティブの確認応答が使用されます(acknowledge 属性を参照)。

concurrency

リスナーごとに開始する同時セッションまたはコンシューマーの数。最大数を示す単純な数値(たとえば、5)、または下限と上限を示す範囲(たとえば、3-5)のいずれかです。指定された最小値は単なるヒントであり、実行時に無視される場合があることに注意してください。デフォルトは 1 です。トピックリスナーの場合、またはキューの順序が重要な場合は、同時実行性を 1 に制限する必要があります。一般的なキュー用に上げることを検討してください。

prefetch

単一セッションにロードするメッセージの最大数。この数を増やすと、同時消費者が枯渇する可能性があることに注意してください。

receive-timeout

受信呼び出しに使用するタイムアウト(ミリ秒)。デフォルトは 1000 (1 秒)です。-1 はタイムアウトがないことを示します。

back-off

回復試行の間隔を計算するために使用する BackOff インスタンスを指定します。BackOffExecution 実装が BackOffExecution#STOP を返す場合、リスナーコンテナーはそれ以上回復を試みません。このプロパティが設定されている場合、recovery-interval 値は無視されます。デフォルトは、間隔が 5000 ミリ秒(つまり、5 秒)の FixedBackOff です。

recovery-interval

回復試行の間隔をミリ秒単位で指定します。指定した間隔で FixedBackOff を作成する便利な方法を提供します。より多くの回復オプションについては、代わりに BackOff インスタンスを指定することを検討してください。デフォルトは 5000 ミリ秒(つまり、5 秒)です。

phase

このコンテナーが開始および停止するライフサイクルフェーズ。値が低いほど、このコンテナーは早く起動し、遅く停止します。デフォルトは Integer.MAX_VALUE です。これは、コンテナーが可能な限り遅く起動し、できるだけ早く停止することを意味します。

次の例に示すように、jms スキーマサポートを使用した JCA ベースのリスナーコンテナーの構成は非常に似ています。

<jms:jca-listener-container resource-adapter="myResourceAdapter"
        destination-resolver="myDestinationResolver"
        transaction-manager="myTransactionManager"
        concurrency="10">

    <jms:listener destination="queue.orders" ref="myMessageListener"/>

</jms:jca-listener-container>

次の表は、JCA バリアントで使用可能な構成オプションを示しています。

表 5: JMS <jca-listener-container /> 要素の属性
属性 説明

factory-id

指定された id を持つ JmsListenerContainerFactory としてこの要素で定義された設定を公開し、他のエンドポイントで再利用できるようにします。

resource-adapter

JCA ResourceAdapter Bean への参照(デフォルトの Bean 名は resourceAdapter です)。

activation-spec-factory

JmsActivationSpecFactory への参照。デフォルトでは、JMS プロバイダーとその ActivationSpec クラスを自動検出します(DefaultJmsActivationSpecFactory(Javadoc) を参照)。

destination-resolver

JMS Destinations を解決するための DestinationResolver 戦略への参照。

message-converter

JMS メッセージをリスナーメソッドの引数に変換するための MessageConverter 戦略への参照。デフォルトは SimpleMessageConverter です。

destination-type

このリスナーの JMS 宛先タイプ: queuetopicdurableTopicsharedTopic。または sharedDurableTopic。これにより、コンテナーの pubSubDomainsubscriptionDurable および subscriptionShared プロパティが潜在的に有効になります。デフォルトは queue (これらの 3 つのプロパティを無効にします)。

response-destination-type

レスポンスの JMS 宛先タイプ: queue または topic。デフォルトは、destination-type 属性の値です。

client-id

このリスナーコンテナーの JMS クライアント ID。永続サブスクリプションを使用する場合は指定する必要があります。

acknowledge

ネイティブ JMS 確認モード: autoclientdups-ok または transactedtransacted の値は、ローカルに処理された Session をアクティブにします。別の方法として、後で説明する transaction-manager 属性を指定できます。デフォルトは auto です。

transaction-manager

受信メッセージごとに XA トランザクションを開始するための Spring JtaTransactionManager または javax.transaction.TransactionManager への参照。指定しない場合、ネイティブの確認応答が使用されます(acknowledge 属性を参照)。

concurrency

リスナーごとに開始する同時セッションまたはコンシューマーの数。最大数を示す単純な数値(たとえば 5)、または下限と上限を示す範囲(たとえば 3-5)のいずれかです。指定された最小値は単なるヒントであり、JCA リスナーコンテナーを使用する場合、実行時に通常無視されることに注意してください。デフォルトは 1 です。

prefetch

単一セッションにロードするメッセージの最大数。この数を増やすと、同時消費者が枯渇する可能性があることに注意してください。

4. JMX

Spring の JMX(Java Management Extensions)サポートは、Spring アプリケーションを JMX インフラストラクチャに簡単かつ透過的に統合できる機能を提供します。

JMX?

この章は、JMX の概要ではありません。JMX を使用する理由を説明しようとはしていません。JMX を初めて使用する場合は、この章の最後にあるその他のリソースを参照してください。

具体的には、Spring の JMX サポートは 4 つのコア機能を提供します。

  • Spring Bean の JMX MBean としての自動登録。

  • Bean の管理インターフェースを制御するための柔軟なメカニズム。

  • リモート、JSR-160 コネクターを介した MBean の宣言的な公開。

  • ローカルおよびリモート MBean リソースの単純なプロキシ。

これらの機能は、アプリケーションコンポーネントを Spring または JMX インターフェースおよびクラスに結合することなく機能するように設計されています。実際、ほとんどの場合、Spring JMX 機能を利用するために、アプリケーションクラスは Spring または JMX を認識する必要はありません。

4.1. Bean を JMX にエクスポートする

Spring の JMX フレームワークのコアクラスは MBeanExporter です。このクラスは、Spring Bean を取得し、JMX MBeanServer に登録します。例:次のクラスを検討します。

package org.springframework.jmx;

public class JmxTestBean implements IJmxTestBean {

    private String name;
    private int age;
    private boolean isSuperman;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int add(int x, int y) {
        return x + y;
    }

    public void dontExposeMe() {
        throw new RuntimeException();
    }
}

この Bean のプロパティとメソッドを MBean の属性と操作として公開するには、次の例に示すように、構成ファイルで MBeanExporter クラスのインスタンスを構成し、Bean を渡すことができます。

<beans>
    <!-- this bean must not be lazily initialized if the exporting is to happen -->
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
    </bean>
    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>
</beans>

上記の構成スニペットの適切な Bean 定義は、exporter Bean です。beans プロパティは、どの Bean を JMX MBeanServer にエクスポートする必要があるかを正確に MBeanExporter に伝えます。デフォルト構成では、beansMap の各エントリのキーは、対応するエントリ値によって参照される Bean の ObjectName として使用されます。Bean の ObjectName インスタンスの制御に従って、この動作を変更できます。

この構成では、testBean Bean は ObjectNamebean:name=testBean1 で MBean として公開されます。デフォルトでは、Bean のすべての public プロパティは属性として公開され、すべての public メソッド(Object クラスから継承されたものを除く)は操作として公開されます。

MBeanExporter は Lifecycle Bean です(起動とシャットダウンのコールバックを参照)。デフォルトでは、MBean はアプリケーションのライフサイクル中にできるだけ遅くエクスポートされます。autoStartup フラグを設定することにより、エクスポートが行われる phase を構成したり、自動登録を無効にしたりできます。

4.1.1. MBeanServer の作成

前のセクションに示されている構成は、1 つ(および 1 つ)の MBeanServer が既に実行されている環境でアプリケーションが実行されていることを前提としています。この場合、Spring は実行中の MBeanServer を見つけて、そのサーバー(存在する場合)に Bean を登録しようとします。この動作は、アプリケーションが独自の MBeanServer を持つコンテナー(Tomcat や IBM WebSphere など)内で実行される場合に役立ちます。

ただし、このアプローチは、スタンドアロン環境や、MBeanServer を提供しないコンテナー内で実行する場合には役に立ちません。これに対処するには、org.springframework.jmx.support.MBeanServerFactoryBean クラスのインスタンスを構成に追加して、MBeanServer インスタンスを宣言的に作成できます。次の例に示すように、MBeanExporter インスタンスの server プロパティの値を MBeanServerFactoryBean によって返される MBeanServer 値に設定することにより、特定の MBeanServer が使用されるようにすることもできます。

<beans>

    <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>

    <!--
    this bean needs to be eagerly pre-instantiated in order for the exporting to occur;
    this means that it must not be marked as lazily initialized
    -->
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
        <property name="server" ref="mbeanServer"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

</beans>

上記の例では、MBeanServer のインスタンスが MBeanServerFactoryBean によって作成され、server プロパティを介して MBeanExporter に提供されます。独自の MBeanServer インスタンスを提供する場合、MBeanExporter は実行中の MBeanServer を見つけようとせず、提供された MBeanServer インスタンスを使用します。これが正しく機能するには、クラスパスに JMX 実装が必要です。

4.1.2. 既存の MBeanServer の再利用

サーバーが指定されていない場合、MBeanExporter は実行中の MBeanServer を自動的に検出しようとします。これは、MBeanServer インスタンスが 1 つだけ使用されるほとんどの環境で機能します。ただし、複数のインスタンスが存在する場合、エクスポーターは間違ったサーバーを選択する可能性があります。このような場合、次の例に示すように、MBeanServeragentId を使用して、使用するインスタンスを示す必要があります。

<beans>
    <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
        <!-- indicate to first look for a server -->
        <property name="locateExistingServerIfPossible" value="true"/>
        <!-- search for the MBeanServer instance with the given agentId -->
        <property name="agentId" value="MBeanServer_instance_agentId>"/>
    </bean>
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="server" ref="mbeanServer"/>
        ...
    </bean>
</beans>

既存の MBeanServer にルックアップメソッドで取得される動的(または不明) agentId があるプラットフォームまたはケースの場合、次の例に示すように factory-method を使用する必要があります。

<beans>
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="server">
            <!-- Custom MBeanServerLocator -->
            <bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/>
        </property>
    </bean>

    <!-- other beans here -->

</beans>

4.1.3. 遅延初期化された MBean

遅延初期化用に構成された MBeanExporter を使用して Bean を構成する場合、MBeanExporter はこの契約を破らず、Bean のインスタンス化を回避します。代わりに、プロキシを MBeanServer に登録し、プロキシで最初の呼び出しが発生するまでコンテナーから Bean の取得を延期します。

4.1.4. MBean の自動登録

MBeanExporter を介してエクスポートされ、すでに有効な MBean である Bean は、Spring からの介入なしに MBeanServer にそのまま登録されます。次の例に示すように、autodetect プロパティを true に設定することにより、MBean を MBeanExporter によって自動的に検出させることができます。

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="autodetect" value="true"/>
</bean>

<bean name="spring:mbean=true" class="org.springframework.jmx.export.TestDynamicMBean"/>

上記の例では、spring:mbean=true と呼ばれる Bean はすでに有効な JMX MBean であり、Spring によって自動的に登録されます。デフォルトでは、JMX 登録用に自動検出される Bean の Bean 名は ObjectName として使用されます。Bean の ObjectName インスタンスの制御で詳述されているように、この動作をオーバーライドできます。

4.1.5. 登録動作の制御

Spring MBeanExporter が ObjectNamebean:name=testBean1 を使用して MBean を MBeanServer に登録しようとするシナリオを考えます。MBean インスタンスが同じ ObjectName にすでに登録されている場合、デフォルトの動作は失敗します(そして InstanceAlreadyExistsException をスローします)。

MBean が MBeanServer に登録されたときに何が起こるかを正確に制御できます。Spring の JMX サポートでは、MBean が同じ ObjectName にすでに登録されていることが登録プロセスで検出された場合、3 つの異なる登録動作により登録動作を制御できます。次の表は、これらの登録動作をまとめたものです。

表 6: 登録動作
登録動作 説明

FAIL_ON_EXISTING

これがデフォルトの登録動作です。MBean インスタンスが同じ ObjectName にすでに登録されている場合、登録されている MBean は登録されず、InstanceAlreadyExistsException がスローされます。既存の MBean は影響を受けません。

IGNORE_EXISTING

MBean インスタンスが同じ ObjectName にすでに登録されている場合、登録されている MBean は登録されません。既存の MBean は影響を受けず、Exception はスローされません。これは、複数のアプリケーションが共通の MBean を共有 MBeanServer で共有したい設定で役立ちます。

REPLACE_EXISTING

MBean インスタンスが同じ ObjectName にすでに登録されている場合、以前に登録された既存の MBean は登録解除され、新しい MBean がその場所に登録されます(新しい MBean は以前のインスタンスを効果的に置き換えます)。

上記の表の値は、RegistrationPolicy クラスの列挙として定義されています。デフォルトの登録動作を変更する場合は、MBeanExporter 定義の registrationPolicy プロパティの値をこれらの値のいずれかに設定する必要があります。

次の例は、デフォルトの登録動作から REPLACE_EXISTING 動作に変更する方法を示しています。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
        <property name="registrationPolicy" value="REPLACE_EXISTING"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

</beans>

4.2. Bean の管理インターフェースの制御

前のセクションの例では、Bean の管理インターフェースをほとんど制御できませんでした。エクスポートされた各 Bean の public プロパティとメソッドはすべて、それぞれ JMX 属性と操作として公開されます。エクスポートされた Bean のどのプロパティとメソッドが実際に JMX 属性と操作として公開されるかをきめ細かく制御するために、Spring JMX は Bean の管理インターフェースを制御するための包括的で拡張可能なメカニズムを提供します。

4.2.1. MBeanInfoAssembler インターフェースの使用

背後で、MBeanExporter は org.springframework.jmx.export.assembler.MBeanInfoAssembler インターフェースの実装に委譲します。org.springframework.jmx.export.assembler.MBeanInfoAssembler インターフェースは、公開される各 Bean の管理インターフェースを定義するロールを果たします。デフォルトの実装 org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler は、すべてのパブリックプロパティとメソッドを公開する管理インターフェースを定義します(前のセクションの例で見たように)。Spring は、MBeanInfoAssembler インターフェースの 2 つの追加の実装を提供します。これにより、ソースレベルのメタデータまたは任意のインターフェースを使用して、生成された管理インターフェースを制御できます。

4.2.2. ソースレベルのメタデータの使用 : Java アノテーション

MetadataMBeanInfoAssembler を使用することにより、ソースレベルのメタデータを使用して、Bean の管理インターフェースを定義できます。メタデータの読み取りは、org.springframework.jmx.export.metadata.JmxAttributeSource インターフェースによってカプセル化されます。Spring JMX は、Java アノテーション、つまり org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource を使用するデフォルトの実装を提供します。MetadataMBeanInfoAssembler を正しく機能させるには、JmxAttributeSource インターフェースの実装インスタンスを使用して MetadataMBeanInfoAssembler を構成する必要があります(デフォルトはありません)。

Bean を JMX にエクスポートするようにマークするには、Bean クラスに ManagedResource アノテーションを付ける必要があります。ManagedOperation アノテーションを使用して操作として公開する各メソッドをマークし、ManagedAttribute アノテーションを使用して公開する各プロパティをマークする必要があります。プロパティをマークする場合、getter または setter のアノテーションを省略して、それぞれ書き込み専用または読み取り専用の属性を作成できます。

ManagedResource アノテーション付き Bean は、操作または属性を公開するメソッドと同様に、パブリックでなければなりません。

次の例は、MBeanServer の作成で使用した JmxTestBean クラスのアノテーション付きバージョンを示しています。

package org.springframework.jmx;

import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedAttribute;

@ManagedResource(
        objectName="bean:name=testBean4",
        description="My Managed Bean",
        log=true,
        logFile="jmx.log",
        currencyTimeLimit=15,
        persistPolicy="OnUpdate",
        persistPeriod=200,
        persistLocation="foo",
        persistName="bar")
public class AnnotationTestBean implements IJmxTestBean {

    private String name;
    private int age;

    @ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15)
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @ManagedAttribute(description="The Name Attribute",
            currencyTimeLimit=20,
            defaultValue="bar",
            persistPolicy="OnUpdate")
    public void setName(String name) {
        this.name = name;
    }

    @ManagedAttribute(defaultValue="foo", persistPeriod=300)
    public String getName() {
        return name;
    }

    @ManagedOperation(description="Add two numbers")
    @ManagedOperationParameters({
        @ManagedOperationParameter(name = "x", description = "The first number"),
        @ManagedOperationParameter(name = "y", description = "The second number")})
    public int add(int x, int y) {
        return x + y;
    }

    public void dontExposeMe() {
        throw new RuntimeException();
    }

}

上記の例では、JmxTestBean クラスに ManagedResource アノテーションが付けられ、この ManagedResource アノテーションに一連のプロパティが設定されていることがわかります。これらのプロパティは、MBeanExporter によって生成される MBean のさまざまなアスペクトを構成するために使用でき、後でソースレベルのメタデータ型で詳しく説明します。

age プロパティと name プロパティの両方に ManagedAttribute アノテーションが付けられていますが、age プロパティの場合は、getter のみがマークされています。これにより、これらのプロパティの両方が管理インターフェースに属性として含まれますが、age 属性は読み取り専用です。

最後に、add(int, int) メソッドは ManagedOperation 属性でマークされていますが、dontExposeMe() メソッドはマークされていません。これにより、MetadataMBeanInfoAssembler を使用する場合、管理インターフェースには 1 つの操作(add(int, int))のみが含まれます。

次の構成は、MetadataMBeanInfoAssembler を使用するように MBeanExporter を構成する方法を示しています。

<beans>
    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="assembler" ref="assembler"/>
        <property name="namingStrategy" ref="namingStrategy"/>
        <property name="autodetect" value="true"/>
    </bean>

    <bean id="jmxAttributeSource"
            class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>

    <!-- will create management interface using annotation metadata -->
    <bean id="assembler"
            class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
        <property name="attributeSource" ref="jmxAttributeSource"/>
    </bean>

    <!-- will pick up the ObjectName from the annotation -->
    <bean id="namingStrategy"
            class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
        <property name="attributeSource" ref="jmxAttributeSource"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.AnnotationTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>
</beans>

上記の例では、MetadataMBeanInfoAssembler Bean は AnnotationJmxAttributeSource クラスのインスタンスで構成され、アセンブラープロパティを介して MBeanExporter に渡されています。Spring で公開された MBean のメタデータ駆動型管理インターフェースを利用するために必要なことはこれだけです。

4.2.3. ソースレベルのメタデータ型

次の表は、Spring JMX で使用できるソースレベルのメタデータタイプを示しています。

表 7: ソースレベルのメタデータ型
目的 アノテーション アノテーション型

Class のすべてのインスタンスを JMX 管理対象リソースとしてマークします。

@ManagedResource

クラス

メソッドを JMX 操作としてマークします。

@ManagedOperation

メソッド

getter または setter を JMX 属性の半分としてマークします。

@ManagedAttribute

メソッド (getter および setter のみ)

操作パラメーターの説明を定義します。

@ManagedOperationParameter および @ManagedOperationParameters

メソッド

次の表に、これらのソースレベルのメタデータタイプで使用できる構成パラメーターを示します。

表 8: ソースレベルのメタデータパラメーター
パラメーター 説明 適用先

ObjectName

マネージリソースの ObjectName を決定するために MetadataNamingStrategy によって使用されます。

ManagedResource

description

リソース、属性、または操作のわかりやすい説明を設定します。

ManagedResourceManagedAttributeManagedOperation または ManagedOperationParameter

currencyTimeLimit

currencyTimeLimit 記述子フィールドの値を設定します。

ManagedResource または ManagedAttribute

defaultValue

defaultValue 記述子フィールドの値を設定します。

ManagedAttribute

log

log 記述子フィールドの値を設定します。

ManagedResource

logFile

logFile 記述子フィールドの値を設定します。

ManagedResource

persistPolicy

persistPolicy 記述子フィールドの値を設定します。

ManagedResource

persistPeriod

persistPeriod 記述子フィールドの値を設定します。

ManagedResource

persistLocation

persistLocation 記述子フィールドの値を設定します。

ManagedResource

persistName

persistName 記述子フィールドの値を設定します。

ManagedResource

name

操作パラメーターの表示名を設定します。

ManagedOperationParameter

index

操作パラメーターのインデックスを設定します。

ManagedOperationParameter

4.2.4. AutodetectCapableMBeanInfoAssembler インターフェースの使用

構成をさらに簡素化するために、Spring には AutodetectCapableMBeanInfoAssembler インターフェースが含まれています。これは、MBeanInfoAssembler インターフェースを継承して、MBean リソースの自動検出のサポートを追加します。AutodetectCapableMBeanInfoAssembler のインスタンスを使用して MBeanExporter を構成する場合、JMX に公開するために Bean を含めることに「投票」することができます。

AutodetectCapableMBeanInfo インターフェースの唯一の実装は MetadataMBeanInfoAssembler であり、ManagedResource 属性でマークされた Bean を含めるために投票します。この場合のデフォルトのアプローチは、Bean 名を ObjectName として使用することです。これにより、次のような構成になります。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <!-- notice how no 'beans' are explicitly configured here -->
        <property name="autodetect" value="true"/>
        <property name="assembler" ref="assembler"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

    <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
        <property name="attributeSource">
            <bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
        </property>
    </bean>

</beans>

上記の構成では、MBeanExporter に Bean が渡されないことに注意してください。ただし、JmxTestBean は ManagedResource 属性でマークされており、MetadataMBeanInfoAssembler がこれを検出し、それを含めるために投票するため、依然として登録されています。このアプローチの唯一の課題は、JmxTestBean の名前にビジネス上の意味があることです。Bean の ObjectName インスタンスの制御で定義されている ObjectName 作成のデフォルト動作を変更することにより、この課題に対処できます。

4.2.5. Java インターフェースを使用した管理インターフェースの定義

MetadataMBeanInfoAssembler に加えて、Spring には InterfaceBasedMBeanInfoAssembler も含まれています。これにより、インターフェースのコレクションで定義されたメソッドのセットに基づいて公開されるメソッドとプロパティを制限できます。

MBean を公開する標準的なメカニズムはインターフェースと単純な命名スキームを使用することですが、InterfaceBasedMBeanInfoAssembler は命名規則の必要性を削除し、複数のインターフェースを使用できるようにし、Bean が MBean インターフェースを実装する必要性を削除することにより、この機能を継承します。

前に示した JmxTestBean クラスの管理インターフェースを定義するために使用される次のインターフェースを検討してください。

public interface IJmxTestBean {

    public int add(int x, int y);

    public long myOperation();

    public int getAge();

    public void setAge(int age);

    public void setName(String name);

    public String getName();

}

このインターフェースは、JMX MBean の操作と属性として公開されるメソッドとプロパティを定義します。次のコードは、このインターフェースを管理インターフェースの定義として使用するように Spring JMX を構成する方法を示しています。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean5" value-ref="testBean"/>
            </map>
        </property>
        <property name="assembler">
            <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
                <property name="managedInterfaces">
                    <value>org.springframework.jmx.IJmxTestBean</value>
                </property>
            </bean>
        </property>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

</beans>

上記の例では、InterfaceBasedMBeanInfoAssembler は、Bean の管理インターフェースを構築するときに IJmxTestBean インターフェースを使用するように構成されています。InterfaceBasedMBeanInfoAssembler によって処理される Bean は、JMX 管理インターフェースの生成に使用されるインターフェースを実装する必要がないことを理解することが重要です。

上記の場合、IJmxTestBean インターフェースを使用して、すべての Bean のすべての管理インターフェースを構築します。多くの場合、これは望ましい動作ではないため、Bean ごとに異なるインターフェースを使用することをお勧めします。この場合、InterfaceBasedMBeanInfoAssembler に interfaceMappings プロパティを介して Properties インスタンスを渡すことができます。各エントリのキーは Bean 名で、各エントリの値はその Bean に使用するインターフェース名のコンマ区切りリストです。

managedInterfaces または interfaceMappings プロパティのいずれかで管理インターフェースが指定されていない場合、InterfaceBasedMBeanInfoAssembler は Bean に反映し、その Bean によって実装されたすべてのインターフェースを使用して管理インターフェースを作成します。

4.2.6. MethodNameBasedMBeanInfoAssembler を使用する

MethodNameBasedMBeanInfoAssembler では、JMX に属性および操作として公開されるメソッド名のリストを指定できます。次のコードはサンプル構成を示しています。

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
        <map>
            <entry key="bean:name=testBean5" value-ref="testBean"/>
        </map>
    </property>
    <property name="assembler">
        <bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
            <property name="managedMethods">
                <value>add,myOperation,getName,setName,getAge</value>
            </property>
        </bean>
    </property>
</bean>

上記の例では、add および myOperation メソッドが JMX 操作として公開され、getName()setName(String) および getAge() が JMX 属性の適切な半分として公開されていることがわかります。上記のコードでは、メソッドマッピングは JMX に公開される Bean に適用されます。Bean ごとにメソッドの公開を制御するには、MethodNameMBeanInfoAssembler の methodMappings プロパティを使用して、Bean 名をメソッド名のリストにマッピングします。

4.3. Bean の ObjectName インスタンスの制御

バックグラウンドでは、MBeanExporter は ObjectNamingStrategy の実装に委譲して、登録する各 Bean の ObjectName インスタンスを取得します。デフォルトでは、デフォルトの実装である KeyNamingStrategy は beansMap のキーを ObjectName として使用します。さらに、KeyNamingStrategy は beansMap のキーを Properties ファイルのエントリにマッピングして、ObjectName を解決できます。KeyNamingStrategy に加えて、Spring は、IdentityNamingStrategy (Bean の JVM ID に基づいて ObjectName を構築する)と MetadataNamingStrategy (ObjectName を取得するためにソースレベルのメタデータを使用する)の 2 つの追加 ObjectNamingStrategy 実装を提供します。

4.3.1. プロパティからの ObjectName インスタンスの読み取り

独自の KeyNamingStrategy インスタンスを構成し、Bean キーを使用するのではなく、Properties インスタンスから ObjectName インスタンスを読み取るように構成できます。KeyNamingStrategy は、Bean キーに対応するキーを使用して、Properties のエントリを見つけようとします。エントリが見つからない場合、または Properties インスタンスが null の場合、Bean キー自体が使用されます。

次のコードは、KeyNamingStrategy のサンプル構成を示しています。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="testBean" value-ref="testBean"/>
            </map>
        </property>
        <property name="namingStrategy" ref="namingStrategy"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

    <bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy">
        <property name="mappings">
            <props>
                <prop key="testBean">bean:name=testBean1</prop>
            </props>
        </property>
        <property name="mappingLocations">
            <value>names1.properties,names2.properties</value>
        </property>
    </bean>

</beans>

上記の例では、KeyNamingStrategy のインスタンスを、マッピングプロパティで定義された Properties インスタンスと、マッピングプロパティで定義されたパスにあるプロパティファイルからマージされた Properties インスタンスで構成します。この構成では、Bean キーに対応するキーを持つ Properties インスタンスのエントリであるため、testBean Bean には bean:name=testBean1 の ObjectName が与えられます。

Properties インスタンスにエントリが見つからない場合、Bean キー名が ObjectName として使用されます。

4.3.2. MetadataNamingStrategy を使用する

MetadataNamingStrategy は、各 Bean の ManagedResource 属性の objectName プロパティを使用して、ObjectName を作成します。次のコードは、MetadataNamingStrategy の構成を示しています。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="testBean" value-ref="testBean"/>
            </map>
        </property>
        <property name="namingStrategy" ref="namingStrategy"/>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

    <bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
        <property name="attributeSource" ref="attributeSource"/>
    </bean>

    <bean id="attributeSource"
            class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>

</beans>

objectName が ManagedResource 属性に提供されていない場合、ObjectName は次の形式で作成されます: [ 完全修飾パッケージ名 ]:type = [short-classname]、name = [bean-name] 例:次の Bean に対して生成される ObjectName は com.example:type=MyClass,name=myBean になります。

<bean id="myBean" class="com.example.MyClass"/>

4.3.3. アノテーションベースの MBean エクスポートの構成

アノテーションベースのアプローチを使用して管理インターフェースを定義する場合は、MBeanExporter の便利なサブクラスである AnnotationMBeanExporter を使用できます。このサブクラスのインスタンスを定義する場合、namingStrategyassembler および attributeSource 構成は必要なくなりました。これは、常に標準の Java アノテーションベースのメタデータを使用するためです(自動検出も常に有効になっています)。実際、MBeanExporter Bean を定義するのではなく、次の例に示すように、@EnableMBeanExport@Configuration アノテーションではさらに単純な構文がサポートされています。

@Configuration
@EnableMBeanExport
public class AppConfig {

}

XML ベースの構成を好む場合、<context:mbean-export/> 要素は同じ目的を果たし、次のリストに表示されます。

<context:mbean-export/>

必要に応じて、特定の MBean server への参照を提供できます。defaultDomain 属性(AnnotationMBeanExporter のプロパティ)は、生成された MBean ObjectName ドメインの代替値を受け入れます。これは、次の例に示すように、前の MetadataNamingStrategy セクションで説明したように、完全修飾パッケージ名の代わりに使用されます。

@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
@Configuration
ContextConfiguration {

}

次の例は、前述のアノテーションベースの例に相当する XML を示しています。

<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
Bean クラスの JMX アノテーションの自動検出と組み合わせて、インターフェースベースの AOP プロキシを使用しないでください。インターフェースベースのプロキシは、ターゲットクラスを「隠し」ます。これにより、JMX 管理のリソースアノテーションも隠されます。その場合はターゲットクラスのプロキシを使用する必要があります(<aop:config/><tx:annotation-driven/> で「proxy-target-class」フラグを設定するなど)。そうしないと、JMX Bean が起動時に静かに無視される場合があります。

4.4. JSR-160 コネクターの使用

リモートアクセスの場合、Spring JMX モジュールは、サーバー側とクライアント側の両方のコネクターを作成するために、org.springframework.jmx.support パッケージ内に 2 つの FactoryBean 実装を提供します。

4.4.1. サーバー側のコネクター

Spring JMX で JSR-160 JMXConnectorServer を作成、開始、公開するには、次の構成を使用できます。

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>

デフォルトでは、ConnectorServerFactoryBean は service:jmx:jmxmp://localhost:9875 にバインドされた JMXConnectorServer を作成します。serverConnector Bean は、ローカルホスト、ポート 9875 の JMXMP プロトコルを介して、クライアントにローカル MBeanServer を公開します。JMXMP プロトコルは、JSR 160 仕様でオプションとしてマークされていることに注意してください。現在、メインのオープンソース JMX 実装である MX4J および JDK で提供されている実装は、JMXMP をサポートしていません。

別の URL を指定し、JMXConnectorServer 自体を MBeanServer に登録するには、次の例に示すように、それぞれ serviceUrl および ObjectName プロパティを使用できます。

<bean id="serverConnector"
        class="org.springframework.jmx.support.ConnectorServerFactoryBean">
    <property name="objectName" value="connector:name=rmi"/>
    <property name="serviceUrl"
            value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
</bean>

ObjectName プロパティが設定されている場合、Spring はその ObjectName の MBeanServer にコネクターを自動的に登録します。次の例は、JMXConnector を作成するときに ConnectorServerFactoryBean に渡すことができるパラメーターの完全なセットを示しています。

<bean id="serverConnector"
        class="org.springframework.jmx.support.ConnectorServerFactoryBean">
    <property name="objectName" value="connector:name=iiop"/>
    <property name="serviceUrl"
        value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
    <property name="threaded" value="true"/>
    <property name="daemon" value="true"/>
    <property name="environment">
        <map>
            <entry key="someKey" value="someValue"/>
        </map>
    </property>
</bean>

RMI ベースのコネクターを使用する場合、名前の登録を完了するには、ルックアップサービス(tnameserv または rmiregistry)を開始する必要があることに注意してください。Spring を使用して RMI を介してリモートサービスをエクスポートする場合、Spring はすでに RMI レジストリを構築しています。そうでない場合は、次の構成スニペットを使用してレジストリを簡単に開始できます。

<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
    <property name="port" value="1099"/>
</bean>

4.4.2. クライアント側のコネクター

リモート JSR-160 対応 MBeanServer に MBeanServerConnection を作成するには、次の例に示すように、MBeanServerConnectionFactoryBean を使用できます。

<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
    <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>

4.4.3. Hessian または SOAP を介した JMX

JSR-160 は、クライアントとサーバーの間で通信が行われる方法の拡張を許可します。前のセクションで示した例では、JSR-160 仕様(IIOP および JRMP)および(オプションの)JMXMP で必要な必須の RMI ベースの実装を使用しています。次の例に示すように、他のプロバイダーまたは JMX 実装(MX4J (英語) など)を使用することにより、単純な HTTP または SSL などを介して SOAP または Hessian などのプロトコルを利用できます。

<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
    <property name="objectName" value="connector:name=burlap"/>
    <property name="serviceUrl" value="service:jmx:burlap://localhost:9874"/>
</bean>

前の例では、MX4J 3.0.0 を使用しました。詳細については、MX4J の公式ドキュメントを参照してください。

4.5. プロキシを介した MBean へのアクセス

Spring JMX を使用すると、ローカルまたはリモート MBeanServer に登録されている MBean に呼び出しを再ルーティングするプロキシを作成できます。これらのプロキシは、MBean とやり取りできる標準の Java インターフェースを提供します。次のコードは、ローカル MBeanServer で実行されている MBean のプロキシを構成する方法を示しています。

<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
    <property name="objectName" value="bean:name=testBean"/>
    <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
</bean>

上記の例では、bean:name=testBean の ObjectName に登録された MBean に対してプロキシが作成されていることがわかります。プロキシが実装するインターフェースのセットは proxyInterfaces プロパティによって制御され、これらのインターフェースのメソッドとプロパティを MBean の操作と属性にマッピングするルールは、InterfaceBasedMBeanInfoAssembler で使用されるルールと同じです。

MBeanProxyFactoryBean は、MBeanServerConnection を介してアクセス可能な任意の MBean へのプロキシを作成できます。デフォルトでは、ローカル MBeanServer が検索および使用されますが、これをオーバーライドして、リモート MBeans を指すプロキシに対応するためにリモート MBeanServer を指す MBeanServerConnection を提供できます。

<bean id="clientConnector"
        class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
    <property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/>
</bean>

<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
    <property name="objectName" value="bean:name=testBean"/>
    <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
    <property name="server" ref="clientConnector"/>
</bean>

前の例では、MBeanServerConnectionFactoryBean を使用するリモートマシンを指す MBeanServerConnection を作成します。この MBeanServerConnection は、server プロパティを介して MBeanProxyFactoryBean に渡されます。作成されたプロキシは、この MBeanServerConnection を介してすべての呼び出しを MBeanServer に転送します。

4.6. 通知

Spring の JMX 製品には、JMX 通知の包括的なサポートが含まれています。

4.6.1. 通知用のリスナーの登録

Spring の JMX サポートにより、任意の数の NotificationListeners を任意の数の MBean に簡単に登録できます(これには、Spring の MBeanExporter によってエクスポートされた MBean および他のメカニズムを通じて登録された MBean が含まれます)。例:ターゲット MBean の属性が変更されるたびに(Notification を介して)情報を受け取りたいシナリオを考えます。次の例では、コンソールに通知を書き込みます。

package com.example;

import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;

public class ConsoleLoggingNotificationListener
        implements NotificationListener, NotificationFilter {

    public void handleNotification(Notification notification, Object handback) {
        System.out.println(notification);
        System.out.println(handback);
    }

    public boolean isNotificationEnabled(Notification notification) {
        return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
    }

}

次の例では、ConsoleLoggingNotificationListener (前の例で定義)を notificationListenerMappings に追加します。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
        <property name="notificationListenerMappings">
            <map>
                <entry key="bean:name=testBean1">
                    <bean class="com.example.ConsoleLoggingNotificationListener"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

</beans>

上記の構成を使用すると、ターゲット MBean(bean:name=testBean1)から JMX Notification がブロードキャストされるたびに、notificationListenerMappings プロパティを介してリスナーとして登録された ConsoleLoggingNotificationListener Bean に通知されます。ConsoleLoggingNotificationListener Bean は、Notification に応じて適切と判断したアクションを実行できます。

次の例に示すように、エクスポートされた Bean とリスナー間のリンクとして、ストレート Bean 名を使用することもできます。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
        <property name="notificationListenerMappings">
            <map>
                <entry key="testBean">
                    <bean class="com.example.ConsoleLoggingNotificationListener"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

</beans>

取り囲む MBeanExporter がエクスポートするすべての Bean に対して単一の NotificationListener インスタンスを登録する場合、次の例に示すように、notificationListenerMappings プロパティマップのエントリのキーとして特別なワイルドカード(*)を使用できます。

<property name="notificationListenerMappings">
    <map>
        <entry key="*">
            <bean class="com.example.ConsoleLoggingNotificationListener"/>
        </entry>
    </map>
</property>

逆を行う(つまり、MBean に対して多数の個別のリスナーを登録する)必要がある場合は、代わりに(notificationListenerMappings プロパティよりも) notificationListeners リストプロパティを使用する必要があります。今回は、単一の MBean に対して NotificationListener を構成する代わりに、NotificationListenerBean インスタンスを構成します。NotificationListenerBean は、NotificationListener と ObjectName (または ObjectNames)をカプセル化し、それが MBeanServer で登録されます。NotificationListenerBean は、NotificationFilter や高度な JMX 通知シナリオで使用できる任意のハンドバックオブジェクトなど、他の多くのプロパティもカプセル化します。

NotificationListenerBean インスタンスを使用する場合の構成は、次の例に示すように、以前に提示されたものと大きく変わりません。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean"/>
            </map>
        </property>
        <property name="notificationListeners">
            <list>
                <bean class="org.springframework.jmx.export.NotificationListenerBean">
                    <constructor-arg>
                        <bean class="com.example.ConsoleLoggingNotificationListener"/>
                    </constructor-arg>
                    <property name="mappedObjectNames">
                        <list>
                            <value>bean:name=testBean1</value>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
    </bean>

    <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

</beans>

上記の例は、最初の通知の例と同等です。それでは、Notification が発生するたびにハンドバックオブジェクトを与えたいと考え、また NotificationFilter を供給することで余分な Notifications を除外したいとします。次の例は、これらのゴールを達成します。

<beans>

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
            <map>
                <entry key="bean:name=testBean1" value-ref="testBean1"/>
                <entry key="bean:name=testBean2" value-ref="testBean2"/>
            </map>
        </property>
        <property name="notificationListeners">
            <list>
                <bean class="org.springframework.jmx.export.NotificationListenerBean">
                    <constructor-arg ref="customerNotificationListener"/>
                    <property name="mappedObjectNames">
                        <list>
                            <!-- handles notifications from two distinct MBeans -->
                            <value>bean:name=testBean1</value>
                            <value>bean:name=testBean2</value>
                        </list>
                    </property>
                    <property name="handback">
                        <bean class="java.lang.String">
                            <constructor-arg value="This could be anything..."/>
                        </bean>
                    </property>
                    <property name="notificationFilter" ref="customerNotificationListener"/>
                </bean>
            </list>
        </property>
    </bean>

    <!-- implements both the NotificationListener and NotificationFilter interfaces -->
    <bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>

    <bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="TEST"/>
        <property name="age" value="100"/>
    </bean>

    <bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
        <property name="name" value="ANOTHER TEST"/>
        <property name="age" value="200"/>
    </bean>

</beans>

(ハンドバックオブジェクトとは何か、実際には NotificationFilter とは何かについての詳細は、「JMX 通知モデル」というタイトルの JMX 仕様(1.2)のセクションを参照してください)

4.6.2. 通知の公開

Spring は、Notifications を受信するための登録だけでなく、Notifications の公開もサポートします。

このセクションは、実際には MBeanExporter を介して MBean として公開された Spring 管理 Bean のみに関連しています。既存のユーザー定義 MBean は、通知の公開に標準の JMX API を使用する必要があります。

Spring の JMX 通知発行サポートの主要なインターフェースは、NotificationPublisher インターフェースです(org.springframework.jmx.export.notification パッケージで定義されています)。MBeanExporter インスタンスを介して MBean としてエクスポートされる Bean は、関連する NotificationPublisherAware インターフェースを実装して、NotificationPublisher インスタンスにアクセスできます。NotificationPublisherAware インターフェースは、NotificationPublisher のインスタンスを単純な setter メソッドを介して実装 Bean に提供し、Bean はそれを使用して Notifications を公開できます。

NotificationPublisher(Javadoc) インターフェースの javadoc に記載されているように、NotificationPublisher メカニズムを介してイベントを発行するマネージド Bean は、通知リスナーの状態管理を行いません。Spring の JMX サポートは、すべての JMX インフラストラクチャの課題を処理します。アプリケーション開発者として行う必要があるのは、NotificationPublisherAware インターフェースを実装し、提供された NotificationPublisher インスタンスを使用してイベントの発行を開始することだけです。NotificationPublisher は、管理対象 Bean が MBeanServer に登録された後に設定されることに注意してください。

NotificationPublisher インスタンスの使用は非常に簡単です。JMX Notification インスタンス(または適切な Notification サブクラスのインスタンス)を作成し、発行するイベントに関連するデータを通知に入力し、Notification を渡して NotificationPublisher インスタンスで sendNotification(Notification) を呼び出します。

次の例では、JmxTestBean のエクスポートされたインスタンスは、add(int, int) 操作が呼び出されるたびに NotificationEvent を公開します。

package org.springframework.jmx;

import org.springframework.jmx.export.notification.NotificationPublisherAware;
import org.springframework.jmx.export.notification.NotificationPublisher;
import javax.management.Notification;

public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {

    private String name;
    private int age;
    private boolean isSuperman;
    private NotificationPublisher publisher;

    // other getters and setters omitted for clarity

    public int add(int x, int y) {
        int answer = x + y;
        this.publisher.sendNotification(new Notification("add", this, 0));
        return answer;
    }

    public void dontExposeMe() {
        throw new RuntimeException();
    }

    public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
        this.publisher = notificationPublisher;
    }

}

NotificationPublisher インターフェースとそれをすべて機能させるための機械は、Spring の JMX サポートの優れた機能の 1 つです。ただし、クラスを Spring と JMX の両方に結合する価格タグが付いています。通常どおり、ここでのアドバイスは実用的です。NotificationPublisher が提供する機能が必要で、Spring と JMX の両方への結合を受け入れることができる場合は、そうしてください。

4.7. その他のリソース

このセクションには、JMX に関するその他のリソースへのリンクが含まれています。

5. JCA CCI

Java EE は、エンタープライズ情報システム(EIS)へのアクセスを標準化するための仕様、JCA(Java EE Connector Architecture)を提供します。この仕様は、2 つの異なる部分に分かれています。

  • コネクタープロバイダーが実装する必要がある SPI(サービスプロバイダーインターフェース)。これらのインターフェースは、Java EE アプリケーションサーバーにデプロイできるリソースアダプターを構成します。このようなシナリオでは、サーバーは接続プーリング、トランザクション、およびセキュリティを管理します(管理モード)。アプリケーションサーバーは、クライアントアプリケーションの外部に保持されている構成の管理も担当します。コネクターは、アプリケーションサーバーなしでも使用できます。この場合、アプリケーションは直接設定する必要があります(非管理モード)。

  • CCI(Common Client Interface)は、アプリケーションがコネクターと対話するために使用できるため、EIS と通信します。ローカルトランザクション境界設定用の API も提供されます。

Spring CCI サポートの目的は、Spring Framework の一般的なリソースおよびトランザクション管理機能を使用して、典型的な Spring スタイルで CCI コネクターにアクセスするクラスを提供することです。

コネクターのクライアント側は、常に CCI を使用するとは限りません。一部のコネクターは独自の API を公開し、Java EE コンテナーのシステム契約(接続プーリング、グローバルトランザクション、およびセキュリティ)を使用する JCA リソースアダプターを提供します。Spring は、そのようなコネクター固有の API の特別なサポートを提供しません。

5.1. CCI の構成

このセクションでは、Common Client Interface(CCI)の構成方法について説明します。次のトピックが含まれます。

5.1.1. コネクター構成

JCA CCI を使用するベースリソースは ConnectionFactory インターフェースです。使用するコネクターは、このインターフェースの実装を提供する必要があります。

コネクターを使用するには、アプリケーションサーバーにコネクターをデプロイし、サーバーの JNDI 環境(マネージモード)から ConnectionFactory を取得します。コネクターは、RAR ファイル(リソースアダプターアーカイブ)としてパッケージ化され、デプロイの特性を記述する ra.xml ファイルを含む必要があります。リソースの実際の名前は、デプロイ時に指定されます。Spring 内でアクセスするには、Spring の JndiObjectFactoryBean または <jee:jndi-lookup> を使用して、JNDI 名でファクトリをフェッチできます。

コネクターを使用するもう 1 つの方法は、コネクターをアプリケーションに埋め込み(非管理モード)、アプリケーションサーバーを使用してコネクターをデプロイおよび構成しないことです。Spring は、(LocalConnectionFactoryBean)と呼ばれる FactoryBean 実装を通じて、コネクターを Bean として構成する可能性を提供します。この方法では、クラスパスにコネクターライブラリのみが必要です(RAR ファイルと ra.xml 記述子は不要です)。ライブラリは、必要に応じてコネクターの RAR ファイルから抽出する必要があります。

ConnectionFactory インスタンスにアクセスしたら、コンポーネントにインジェクトできます。これらのコンポーネントは、プレーンな CCI API に対してコーディングするか、CCI アクセスに Spring のサポートクラスを使用できます(例: CciTemplate)。

非管理モードでコネクターを使用する場合、リソースは現在のスレッドの現在のグローバルトランザクションに登録または登録解除されることがないため、グローバルトランザクションを使用できません。リソースは、実行されている可能性のあるグローバル Java EE トランザクションを認識しません。

5.1.2. Spring での ConnectionFactory 構成

EIS に接続するには、アプリケーションサーバー(管理モードの場合)から ConnectionFactory を取得するか、非管理モードの場合 Spring から直接取得する必要があります。

管理モードでは、JNDI から ConnectionFactory にアクセスできます。そのプロパティは、アプリケーションサーバーで構成されます。次の例は、その方法を示しています。

<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>

非管理モードでは、Spring の構成で使用する ConnectionFactory を JavaBean として構成する必要があります。LocalConnectionFactoryBean クラスはこのセットアップスタイルを提供し、コネクターの ManagedConnectionFactory 実装を渡し、アプリケーションレベルの CCI ConnectionFactory を公開します。次の例は、その方法を示しています。

<bean id="eciManagedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
    <property name="serverName" value="TXSERIES"/>
    <property name="connectionURL" value="tcp://localhost/"/>
    <property name="portNumber" value="2006"/>
</bean>

<bean id="eciConnectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
    <property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
</bean>
特定の ConnectionFactory を直接インスタンス化することはできません。コネクターの ManagedConnectionFactory インターフェースの対応する実装を実行する必要があります。このインターフェースは、JCA SPI 仕様の一部です。

5.1.3. CCI 接続の構成

JCA CCI では、コネクターの ConnectionSpec 実装を使用して、EIS への接続を構成できます。そのプロパティを構成するには、ターゲット接続ファクトリを専用アダプター ConnectionSpecConnectionFactoryAdapter でラップする必要があります。専用の ConnectionSpec を connectionSpec プロパティで構成できます(内部 Bean として)。

CCI ConnectionFactory インターフェースは CCI 接続を取得する 2 つの異なる方法を定義するため、このプロパティは必須ではありません。多くの場合、アプリケーションサーバー(マネージモード)または対応するローカル ManagedConnectionFactory 実装で ConnectionSpec プロパティの一部を構成できます。以下のリストは、ConnectionFactory インターフェース定義の関連部分を示しています。

public interface ConnectionFactory implements Serializable, Referenceable {
    ...
    Connection getConnection() throws ResourceException;
    Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException;
    ...
}

Spring は ConnectionSpecConnectionFactoryAdapter を提供します。これにより、指定されたファクトリのすべての操作に使用する ConnectionSpec インスタンスを指定できます。アダプターの connectionSpec プロパティが指定されている場合、アダプターは ConnectionSpec 引数で getConnection バリアントを使用します。それ以外の場合、アダプターはその引数なしでバリアントを使用します。次の例は、ConnectionSpecConnectionFactoryAdapter を構成する方法を示しています。

<bean id="managedConnectionFactory"
        class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
    <property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
    <property name="driverName" value="org.hsqldb.jdbcDriver"/>
</bean>

<bean id="targetConnectionFactory"
        class="org.springframework.jca.support.LocalConnectionFactoryBean">
    <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="connectionFactory"
        class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
    <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
    <property name="connectionSpec">
        <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
            <property name="user" value="sa"/>
            <property name="password" value=""/>
        </bean>
    </property>
</bean>

5.1.4. 単一の CCI 接続を使用する

単一の CCI 接続を使用する場合、Spring はこれを管理するための追加の ConnectionFactory アダプターを提供します。SingleConnectionFactory アダプタークラスは、単一の接続を遅延的に開き、この Bean がアプリケーションのシャットダウン時に破棄されたときに閉じます。このクラスは、それに応じて動作する特別な Connection プロキシを公開し、すべて同じ基礎となる物理接続を共有します。次の例は、SingleConnectionFactory アダプタークラスの使用方法を示しています。

<bean id="eciManagedConnectionFactory"
        class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
    <property name="serverName" value="TEST"/>
    <property name="connectionURL" value="tcp://localhost/"/>
    <property name="portNumber" value="2006"/>
</bean>

<bean id="targetEciConnectionFactory"
        class="org.springframework.jca.support.LocalConnectionFactoryBean">
    <property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
</bean>

<bean id="eciConnectionFactory"
        class="org.springframework.jca.cci.connection.SingleConnectionFactory">
    <property name="targetConnectionFactory" ref="targetEciConnectionFactory"/>
</bean>
この ConnectionFactory アダプターを ConnectionSpec で直接構成することはできません。特定の ConnectionSpec に対して単一の接続が必要な場合は、SingleConnectionFactory が通信する中間の ConnectionSpecConnectionFactoryAdapter を使用できます。

5.2. Spring の CCI アクセスサポートの使用

このセクションでは、Spring の CCI サポートを使用してさまざまな目的を達成する方法について説明します。次のトピックが含まれます。

5.2.1. レコード変換

Spring の JCA CCI サポートの目的の 1 つは、CCI レコードを操作するための便利な機能を提供することです。Spring の CciTemplate で使用するために、レコードを作成し、レコードからデータを抽出する戦略を指定できます。このセクションで説明するインターフェースは、アプリケーションでレコードを直接操作したくない場合に入力および出力レコードを使用するように戦略を構成します。

入力 Record を作成するには、RecordCreator インターフェースの専用実装を使用できます。次のリストは、RecordCreator インターフェース定義を示しています。

public interface RecordCreator {

    Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException;

}

createRecord(..) メソッドは、使用する ConnectionFactory の RecordFactory に対応する RecordFactory インスタンスをパラメーターとして受け取ります。この参照を使用して、IndexedRecord または MappedRecord インスタンスを作成できます。次のサンプルは、RecordCreator インターフェースとインデックス付きまたはマッピングされたレコードの使用方法を示しています。

public class MyRecordCreator implements RecordCreator {

    public Record createRecord(RecordFactory recordFactory) throws ResourceException {
        IndexedRecord input = recordFactory.createIndexedRecord("input");
        input.add(new Integer(id));
        return input;
    }

}

出力 Record を使用して、EIS からデータを受信できます。RecordExtractor インターフェースの特定の実装を Spring の CciTemplate に渡して、出力 Record からデータを抽出できます。以下のリストは、RecordExtractor インターフェース定義を示しています。

public interface RecordExtractor {

    Object extractData(Record record) throws ResourceException, SQLException, DataAccessException;

}

次の例は、RecordExtractor インターフェースの使用方法を示しています。

public class MyRecordExtractor implements RecordExtractor {

    public Object extractData(Record record) throws ResourceException {
        CommAreaRecord commAreaRecord = (CommAreaRecord) record;
        String str = new String(commAreaRecord.toByteArray());
        String field1 = string.substring(0,6);
        String field2 = string.substring(6,1);
        return new OutputObject(Long.parseLong(field1), field2);
    }

}

5.2.2. CciTemplate を使用する

CciTemplate は、コア CCI サポートパッケージ(org.springframework.jca.cci.core)の中心クラスです。リソースの作成と解放を処理するため、CCI の使用が簡単になります。これにより、常に接続を閉じるのを忘れるなどの一般的なエラーを回避できます。接続および相互作用オブジェクトのライフサイクルを管理し、アプリケーションコードがアプリケーションデータから入力レコードを生成し、出力レコードからアプリケーションデータを抽出することに集中できるようにします。

JCA CCI 仕様では、EIS で操作を呼び出す 2 つの異なるメソッドを定義しています。CCI Interaction インターフェースは、次のリストに示すように、2 つの実行メソッドシグネチャーを提供します。

public interface javax.resource.cci.Interaction {

    ...

    boolean execute(InteractionSpec spec, Record input, Record output) throws ResourceException;

    Record execute(InteractionSpec spec, Record input) throws ResourceException;

    ...

}

呼び出されたテンプレートメソッドに応じて、CciTemplate は対話で呼び出す execute メソッドを認識します。いずれにしても、正しく初期化された InteractionSpec インスタンスは必須です。

CciTemplate.execute(..) は 2 つの方法で使用できます。

  • 直接 Record 引数付き。この場合、CCI 入力レコードを渡す必要があり、返されるオブジェクトは対応する CCI 出力レコードです。

  • レコードマッピングを使用したアプリケーションオブジェクト。この場合、対応する RecordCreator および RecordExtractor インスタンスを提供する必要があります。

最初のアプローチでは、テンプレートの次のメソッド(Interaction インターフェースのメソッドに直接対応する)が使用されます。

public class CciTemplate implements CciOperations {

    public Record execute(InteractionSpec spec, Record inputRecord)
            throws DataAccessException { ... }

    public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord)
            throws DataAccessException { ... }

}

2 番目のアプローチでは、引数としてレコード作成およびレコード抽出戦略を指定する必要があります。使用されるインターフェースは、レコード変換に関する前のセクションで説明したものです。次のリストは、対応する CciTemplate メソッドを示しています。

public class CciTemplate implements CciOperations {

    public Record execute(InteractionSpec spec,
            RecordCreator inputCreator) throws DataAccessException {
        // ...
    }

    public Object execute(InteractionSpec spec, Record inputRecord,
            RecordExtractor outputExtractor) throws DataAccessException {
        // ...
    }

    public Object execute(InteractionSpec spec, RecordCreator creator,
            RecordExtractor extractor) throws DataAccessException {
        // ...
    }

}

outputRecordCreator プロパティがテンプレートに設定されていない限り(次のセクションを参照)、すべてのメソッドは、2 つのパラメーター InteractionSpec と入力 Record を使用して、CCI Interaction の対応する execute メソッドを呼び出します。戻り値として出力 Record を受け取ります。

CciTemplate は、createIndexRecord(..) および createMappedRecord(..) メソッドを介して、RecordCreator 実装の外部で IndexRecord および MappedRecord を作成するメソッドも提供します。これを DAO 実装内で使用して、Record インスタンスを作成し、対応する CciTemplate.execute(..) メソッドに渡すことができます。以下のリストは、CciTemplate インターフェース定義を示しています。

public class CciTemplate implements CciOperations {

    public IndexedRecord createIndexedRecord(String name) throws DataAccessException { ... }

    public MappedRecord createMappedRecord(String name) throws DataAccessException { ... }

}

5.2.3. DAO サポートの使用

Spring の CCI サポートは、DAO の抽象クラスを提供し、ConnectionFactory または CciTemplate インスタンスの挿入をサポートします。クラスの名前は CciDaoSupport です。シンプルな setConnectionFactory および setCciTemplate メソッドを提供します。内部的に、このクラスは、渡された ConnectionFactory の CciTemplate インスタンスを作成し、サブクラスの具体的なデータアクセス実装に公開します。次の例は、CciDaoSupport の使用方法を示しています。

public abstract class CciDaoSupport {

    public void setConnectionFactory(ConnectionFactory connectionFactory) {
        // ...
    }

    public ConnectionFactory getConnectionFactory() {
        // ...
    }

    public void setCciTemplate(CciTemplate cciTemplate) {
        // ...
    }

    public CciTemplate getCciTemplate() {
        // ...
    }

}

5.2.4. 自動出力レコード生成

使用するコネクターがパラメーターとして入出力レコードを持つ Interaction.execute(..) メソッドのみをサポートする場合(つまり、適切な出力レコードを返す代わりに、目的の出力レコードを渡す必要がある場合)、CciTemplate の outputRecordCreator プロパティを次のように設定できます。レスポンスが受信されたときに JCA コネクターによって入力される出力レコードを自動的に生成します。このレコードは、テンプレートの呼び出し元に返されます。

このプロパティは、その目的に使用される RecordCreator インターフェースの実装を保持します。CciTemplate で outputRecordCreator プロパティを直接指定する必要があります。次の例は、その方法を示しています。

cciTemplate.setOutputRecordCreator(new EciOutputRecordCreator());

あるいは、Spring 構成で CciTemplate が専用 Bean インスタンスとして構成されている場合、このアプローチをお勧めします。次の方法で Bean を定義できます。

<bean id="eciOutputRecordCreator" class="eci.EciOutputRecordCreator"/>

<bean id="cciTemplate" class="org.springframework.jca.cci.core.CciTemplate">
    <property name="connectionFactory" ref="eciConnectionFactory"/>
    <property name="outputRecordCreator" ref="eciOutputRecordCreator"/>
</bean>
CciTemplate クラスはスレッドセーフであるため、通常は共有インスタンスとして構成されます。

5.2.5. CciTemplateInteraction の概要

次の表は、CciTemplate クラスのメカニズムと、CCI Interaction インターフェースで呼び出される対応するメソッドをまとめたものです。

表 9: インタラクション実行メソッドの使用
CciTemplate メソッドのシグネチャー CciTemplateoutputRecordCreator プロパティ CCI インタラクションで呼び出される execute メソッド

Record execute(InteractionSpec, Record)

未設定

Record execute(InteractionSpec, Record)

Record execute(InteractionSpec, Record)

設定

boolean execute(InteractionSpec, Record, Record)

void execute(InteractionSpec、Record、Record)

未設定

void execute(InteractionSpec、Record、Record)

void execute(InteractionSpec, Record, Record)

設定

void execute(InteractionSpec, Record, Record)

Record execute(InteractionSpec, RecordCreator)

未設定

Record execute(InteractionSpec, Record)

Record execute(InteractionSpec, RecordCreator)

設定

void execute(InteractionSpec, Record, Record)

Record execute(InteractionSpec, Record, RecordExtractor)

未設定

Record execute(InteractionSpec, Record)

Record execute(InteractionSpec, Record, RecordExtractor)

設定

void execute(InteractionSpec, Record, Record)

Record execute(InteractionSpec, RecordCreator, RecordExtractor)

未設定

Record execute(InteractionSpec, Record)

Record execute(InteractionSpec, RecordCreator, RecordExtractor)

設定

void execute(InteractionSpec, Record, Record)

5.2.6. CCI 接続と相互作用を直接使用する

CciTemplate では、JdbcTemplate および JmsTemplate と同じ方法で、CCI 接続および相互作用を直接操作することもできます。これは、たとえば、CCI 接続または対話で複数の操作を実行する場合に便利です。

ConnectionCallback インターフェースは、引数として CCI Connection (カスタム操作を実行するため)と、Connection が作成された CCI ConnectionFactory を提供します。後者は便利です(たとえば、関連付けられた RecordFactory インスタンスを取得し、インデックス付き / マップされたレコードを作成するため)。以下のリストは、ConnectionCallback インターフェース定義を示しています。

public interface ConnectionCallback {

    Object doInConnection(Connection connection, ConnectionFactory connectionFactory)
            throws ResourceException, SQLException, DataAccessException;

}

InteractionCallback インターフェースは、CCI Interaction (カスタム操作を実行するため)と、対応する CCI ConnectionFactory を提供します。以下のリストは、InteractionCallback インターフェース定義を示しています。

public interface InteractionCallback {

    Object doInInteraction(Interaction interaction, ConnectionFactory connectionFactory)
        throws ResourceException, SQLException, DataAccessException;

}
InteractionSpec オブジェクトは、複数のテンプレート呼び出しで共有するか、すべてのコールバックメソッド内で新しく作成できます。これは完全に DAO の実装次第です。

5.2.7. CciTemplate の使用例

このセクションでは、IBM CICS ECI コネクターを使用して、ECP モードで CICS にアクセスするための CciTemplate の使用箇所を示します。

最初に、次の例に示すように、CCI InteractionSpec でいくつかの初期化を行って、アクセスする CICS プログラムと対話する方法を指定する必要があります。

ECIInteractionSpec interactionSpec = new ECIInteractionSpec();
interactionSpec.setFunctionName("MYPROG");
interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);

次に、プログラムは Spring のテンプレートを使用して CCI を使用し、次の例に示すように、カスタムオブジェクトと CCI Records 間のマッピングを指定できます。

public class MyDaoImpl extends CciDaoSupport implements MyDao {

    public OutputObject getData(InputObject input) {
        ECIInteractionSpec interactionSpec = ...;

    OutputObject output = (ObjectOutput) getCciTemplate().execute(interactionSpec,
        new RecordCreator() {
            public Record createRecord(RecordFactory recordFactory) throws ResourceException {
                return new CommAreaRecord(input.toString().getBytes());
            }
        },
        new RecordExtractor() {
            public Object extractData(Record record) throws ResourceException {
                CommAreaRecord commAreaRecord = (CommAreaRecord)record;
                String str = new String(commAreaRecord.toByteArray());
                String field1 = string.substring(0,6);
                String field2 = string.substring(6,1);
                return new OutputObject(Long.parseLong(field1), field2);
            }
        });

        return output;
    }
}

前に説明したように、コールバックを使用して、CCI 接続または対話で直接作業できます。次の例は、その方法を示しています。

public class MyDaoImpl extends CciDaoSupport implements MyDao {

    public OutputObject getData(InputObject input) {
        ObjectOutput output = (ObjectOutput) getCciTemplate().execute(
            new ConnectionCallback() {
                public Object doInConnection(Connection connection,
                        ConnectionFactory factory) throws ResourceException {

                    // do something...

                }
            });
        }
        return output;
    }

}
ConnectionCallback を使用すると、使用される Connection は CciTemplate によって管理および終了されますが、コールバックの実装は、接続で作成された対話を管理する必要があります。

より具体的なコールバックについては、InteractionCallback を実装できます。その場合、渡された Interaction は CciTemplate によって管理およびクローズされます。次の例は、その方法を示しています。

public class MyDaoImpl extends CciDaoSupport implements MyDao {

    public String getData(String input) {
        ECIInteractionSpec interactionSpec = ...;
        String output = (String) getCciTemplate().execute(interactionSpec,
            new InteractionCallback() {
                public Object doInInteraction(Interaction interaction,
                        ConnectionFactory factory) throws ResourceException {
                    Record input = new CommAreaRecord(inputString.getBytes());
                    Record output = new CommAreaRecord();
                    interaction.execute(holder.getInteractionSpec(), input, output);
                    return new String(output.toByteArray());
                }
            });
        return output;
    }

}

前述の例では、関連する Spring Bean の対応する構成は、非管理モードの次の例のようになります。

<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
    <property name="serverName" value="TXSERIES"/>
    <property name="connectionURL" value="local:"/>
    <property name="userName" value="CICSUSER"/>
    <property name="password" value="CICS"/>
</bean>

<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
    <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="component" class="mypackage.MyDaoImpl">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>

管理モード(つまり、Java EE 環境)では、構成は次の例のようになります。

<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>

<bean id="component" class="MyDaoImpl">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>

5.3. 操作オブジェクトとしての CCI アクセスのモデリング

org.springframework.jca.cci.object パッケージには、Spring の JDBC 操作オブジェクトに類似した再利用可能な操作オブジェクトを介して、EIS に異なるスタイルでアクセスできるサポートクラスが含まれています(データアクセスの章の JDBC セクションを参照)。これは通常、CCI API をカプセル化します。アプリケーションレベルの入力オブジェクトは操作オブジェクトに渡されるため、入力レコードを作成し、受信したレコードデータをアプリケーションレベルの出力オブジェクトに変換して返すことができます。

このアプローチは、CciTemplate クラスと RecordCreator または RecordExtractor インターフェースに内部的に基づいており、Spring のコア CCI サポートの機構を再利用しています。

5.3.1. MappingRecordOperation を使用する

MappingRecordOperation は、本質的に CciTemplate と同じ作業を実行しますが、特定の事前設定された操作をオブジェクトとして表します。入力オブジェクトを入力レコードに変換する方法と、出力レコードを出力オブジェクトに変換する方法(レコードマッピング)を指定する 2 つのテンプレートメソッドを提供します。

  • createInputRecord(..): 入力オブジェクトを入力 Record に変換する方法を指定するには

  • extractOutputData(..): 出力 Record から出力オブジェクトを抽出する方法を指定するには

次のリストは、これらのメソッドのシグネチャーを示しています。

public abstract class MappingRecordOperation extends EisOperation {

    ...

    protected abstract Record createInputRecord(RecordFactory recordFactory,
            Object inputObject) throws ResourceException, DataAccessException {
        // ...
    }

    protected abstract Object extractOutputData(Record outputRecord)
            throws ResourceException, SQLException, DataAccessException {
        // ...
    }

    ...

}

その後、EIS 操作を実行するには、単一の execute メソッドを使用して、アプリケーションレベルの入力オブジェクトを渡し、結果としてアプリケーションレベルの出力オブジェクトを受け取る必要があります。次の例は、その方法を示しています。

public abstract class MappingRecordOperation extends EisOperation {

    ...

    public Object execute(Object inputObject) throws DataAccessException {
    }

    ...
}

CciTemplate クラスとは異なり、この execute(..) メソッドには引数として InteractionSpec がありません。代わりに、InteractionSpec は操作に対してグローバルです。特定の InteractionSpec を使用して操作オブジェクトをインスタンス化するには、次のコンストラクターを使用する必要があります。次の例は、その方法を示しています。

InteractionSpec spec = ...;
MyMappingRecordOperation eisOperation = new MyMappingRecordOperation(getConnectionFactory(), spec);
...

5.3.2. MappingCommAreaOperation を使用する

一部のコネクターは、EIS に送信するパラメーターとそれによって返されるデータを含むバイトの配列を表す COMMAREA に基づくレコードを使用します。Spring は、レコードではなく COMMAREA で直接作業するための特別な操作クラスを提供します。MappingCommAreaOperation クラスは MappingRecordOperation クラスを継承して、この特別な COMMAREA サポートを提供します。暗黙的に CommAreaRecord クラスを入力および出力レコードタイプとして使用し、入力オブジェクトを入力 COMMAREA に変換し、出力 COMMAREA を出力オブジェクトに変換する 2 つの新しいメソッドを提供します。以下のリストは、関連するメソッドシグネチャーを示しています。

public abstract class MappingCommAreaOperation extends MappingRecordOperation {

    ...

    protected abstract byte[] objectToBytes(Object inObject)
            throws IOException, DataAccessException;

    protected abstract Object bytesToObject(byte[] bytes)
        throws IOException, DataAccessException;

    ...

}

5.3.3. 自動出力レコード生成

すべての MappingRecordOperation サブクラスは内部で CciTemplate に基づいているため、CciTemplate の場合と同じように出力レコードを自動的に生成する方法が利用できます。すべての操作オブジェクトは、対応する setOutputRecordCreator(..) メソッドを提供します。詳細については、自動出力レコード生成を参照してください。

5.3.4. 要約

操作オブジェクトのアプローチでは、CciTemplate クラスと同じ方法でレコードを使用します。

表 10: インタラクション実行メソッドの使用
MappingRecordOperation メソッドのシグネチャー MappingRecordOperationoutputRecordCreator プロパティ CCI インタラクションで呼び出される execute メソッド

Object execute(Object)

未設定

Record execute(InteractionSpec, Record)

Object execute(Object)

設定

boolean execute(InteractionSpec, Record, Record)

5.3.5. MappingRecordOperation の使用例

このセクションでは、MappingRecordOperation を使用して Blackbox CCI コネクターでデータベースにアクセスする方法を示します。

このコネクターの元のバージョンは、Oracle から入手可能な Java EE SDK(バージョン 1.3)によって提供されます。

最初に、CCI InteractionSpec で初期化を実行して、実行する SQL リクエストを指定する必要があります。次の例では、リクエストのパラメーターを CCI レコードに変換する方法と、CCI 結果レコードを Person クラスのインスタンスに変換する方法を直接定義します。

public class PersonMappingOperation extends MappingRecordOperation {

    public PersonMappingOperation(ConnectionFactory connectionFactory) {
        setConnectionFactory(connectionFactory);
        CciInteractionSpec interactionSpec = new CciConnectionSpec();
        interactionSpec.setSql("select * from person where person_id=?");
        setInteractionSpec(interactionSpec);
    }

    protected Record createInputRecord(RecordFactory recordFactory,
            Object inputObject) throws ResourceException {
        Integer id = (Integer) inputObject;
        IndexedRecord input = recordFactory.createIndexedRecord("input");
        input.add(new Integer(id));
        return input;
    }

    protected Object extractOutputData(Record outputRecord)
            throws ResourceException, SQLException {
        ResultSet rs = (ResultSet) outputRecord;
        Person person = null;
        if (rs.next()) {
            Person person = new Person();
            person.setId(rs.getInt("person_id"));
            person.setLastName(rs.getString("person_last_name"));
            person.setFirstName(rs.getString("person_first_name"));
        }
        return person;
    }
}

その後、アプリケーションは引数として個人識別子を使用して操作オブジェクトを実行できます。操作オブジェクトはスレッドセーフであるため、共有インスタンスとして設定できることに注意してください。以下は、個人識別子を引数として操作オブジェクトを実行します。

public class MyDaoImpl extends CciDaoSupport implements MyDao {

    public Person getPerson(int id) {
        PersonMappingOperation query = new PersonMappingOperation(getConnectionFactory());
        Person person = (Person) query.execute(new Integer(id));
        return person;
    }
}

非管理モードでは、Spring Bean の対応する構成は次のようになります。

<bean id="managedConnectionFactory"
        class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
    <property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
    <property name="driverName" value="org.hsqldb.jdbcDriver"/>
</bean>

<bean id="targetConnectionFactory"
        class="org.springframework.jca.support.LocalConnectionFactoryBean">
    <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="connectionFactory"
        class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
    <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
    <property name="connectionSpec">
        <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
            <property name="user" value="sa"/>
            <property name="password" value=""/>
        </bean>
    </property>
</bean>

<bean id="component" class="MyDaoImpl">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>

管理モード(つまり、Java EE 環境)では、構成は次のようになります。

<jee:jndi-lookup id="targetConnectionFactory" jndi-name="eis/blackbox"/>

<bean id="connectionFactory"
        class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
    <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
    <property name="connectionSpec">
        <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
            <property name="user" value="sa"/>
            <property name="password" value=""/>
        </bean>
    </property>
</bean>

<bean id="component" class="MyDaoImpl">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>

5.3.6. MappingCommAreaOperation の使用例

このセクションでは、MappingCommAreaOperation の使用を使用して、IBM CICS ECI コネクターを使用して ECI モードで CICS にアクセスする方法を示します。

最初に、次の例に示すように、CCI InteractionSpec を初期化して、アクセスする CICS プログラムと対話する方法を指定する必要があります。

public abstract class EciMappingOperation extends MappingCommAreaOperation {

    public EciMappingOperation(ConnectionFactory connectionFactory, String programName) {
        setConnectionFactory(connectionFactory);
        ECIInteractionSpec interactionSpec = new ECIInteractionSpec(),
        interactionSpec.setFunctionName(programName);
        interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);
        interactionSpec.setCommareaLength(30);
        setInteractionSpec(interactionSpec);
        setOutputRecordCreator(new EciOutputRecordCreator());
    }

    private static class EciOutputRecordCreator implements RecordCreator {
        public Record createRecord(RecordFactory recordFactory) throws ResourceException {
            return new CommAreaRecord();
        }
    }

}

次の例に示すように、抽象 EciMappingOperation クラスをサブクラス化して、カスタムオブジェクトと Records 間のマッピングを指定できます。

public class MyDaoImpl extends CciDaoSupport implements MyDao {

    public OutputObject getData(Integer id) {
        EciMappingOperation query = new EciMappingOperation(getConnectionFactory(), "MYPROG") {

            protected abstract byte[] objectToBytes(Object inObject) throws IOException {
                Integer id = (Integer) inObject;
                return String.valueOf(id);
            }

            protected abstract Object bytesToObject(byte[] bytes) throws IOException;
                String str = new String(bytes);
                String field1 = str.substring(0,6);
                String field2 = str.substring(6,1);
                String field3 = str.substring(7,1);
                return new OutputObject(field1, field2, field3);
            }
        });

        return (OutputObject) query.execute(new Integer(id));
    }

}

非管理モードでは、Spring Bean の対応する構成は次のようになります。

<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
    <property name="serverName" value="TXSERIES"/>
    <property name="connectionURL" value="local:"/>
    <property name="userName" value="CICSUSER"/>
    <property name="password" value="CICS"/>
</bean>

<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
    <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="component" class="MyDaoImpl">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>

管理モード(つまり、Java EE 環境)では、構成は次のようになります。

<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>

<bean id="component" class="MyDaoImpl">
    <property name="connectionFactory" ref="connectionFactory"/>
</bean>

5.4. トランザクション

JCA は、リソースアダプターのトランザクションサポートのいくつかのレベルを指定します。リソースアダプターがサポートするトランザクションの種類は、ra.xml ファイルで指定されています。基本的に 3 つのオプションがあります:なし(たとえば、CICS EPI コネクターを使用)、ローカルトランザクション(たとえば、CICS ECI コネクターを使用)、およびグローバルトランザクション(たとえば、IMS コネクターを使用)。次の例では、グローバルオプションを設定します。

<connector>
    <resourceadapter>
        <!-- <transaction-support>NoTransaction</transaction-support> -->
        <!-- <transaction-support>LocalTransaction</transaction-support> -->
        <transaction-support>XATransaction</transaction-support>
    <resourceadapter>
<connector>

グローバルトランザクションの場合、Spring の汎用トランザクションインフラストラクチャを使用して、JtaTransactionManager をバックエンドとするトランザクションを境界設定できます(Java EE サーバーの分散トランザクションコーディネーターに配属されます)。

単一の CCI ConnectionFactory のローカルトランザクションの場合、Spring は、JDBC の DataSourceTransactionManager と同様に、CCI に特定のトランザクション管理戦略を提供します。CCI API は、ローカルトランザクションオブジェクトと対応するローカルトランザクション境界メソッドを定義します。Spring の CciLocalTransactionManager は、Spring の汎用 PlatformTransactionManager 抽象化に完全に準拠した方法で、このようなローカル CCI トランザクションを実行します。次の例では、CciLocalTransactionManager を構成します。

<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>

<bean id="eciTransactionManager"
        class="org.springframework.jca.cci.connection.CciLocalTransactionManager">
    <property name="connectionFactory" ref="eciConnectionFactory"/>
</bean>

Spring のいずれのトランザクション区分機能でも、宣言的であれプログラムであれ、両方のトランザクション戦略を使用できます。これは、Spring の一般的な PlatformTransactionManager 抽象化の結果であり、実際の実行戦略からトランザクションの境界を切り離します。必要に応じて JtaTransactionManager と CciLocalTransactionManager を切り替えて、トランザクションの境界をそのまま維持できます。

Spring のトランザクション機能の詳細については、トランザクション管理を参照してください。

6. メール

このセクションでは、Spring Framework を使用してメールを送信する方法について説明します。

ライブラリの依存関係

Spring Framework のメールライブラリを使用するには、次の JAR がアプリケーションのクラスパスに存在する必要があります。

このライブラリは Web 上で自由に利用できます。たとえば、Maven セントラルで com.sun.mail:javax.mail として利用できます。

Spring Framework は、メールを送信するための有用なユーティリティライブラリを提供します。このライブラリは、基礎となるメールシステムの詳細からユーザーを保護し、クライアントに代わって低レベルのリソース処理を行います。

org.springframework.mail パッケージは、Spring Framework のメールサポートのルートレベルパッケージです。メールを送信するための中心的なインターフェースは、MailSender インターフェースです。from や to (その他多数)などの単純なメールのプロパティをカプセル化する単純な値オブジェクトは、SimpleMailMessage クラスです。このパッケージには、チェック例外の階層も含まれており、下位レベルのメールシステム例外よりも高いレベルの抽象化を提供します。ルート例外は MailException です。リッチメールの例外階層の詳細については、javadoc を参照してください。

org.springframework.mail.javamail.JavaMailSender インターフェースは、MIME メッセージのサポートなどの特殊な JavaMail 機能を(継承元の) MailSender インターフェースに追加します。JavaMailSender は、MimeMessage を準備するための org.springframework.mail.javamail.MimeMessagePreparator と呼ばれるコールバックインターフェースも提供します。

6.1. 使用方法

次の例に示すように、OrderManager というビジネスインターフェースがあると仮定します。

public interface OrderManager {

    void placeOrder(Order order);

}

さらに、オーダー番号付きのメールメッセージを生成し、関連するオーダーを行った顧客に送信する必要があるという要件があると仮定します。

6.1.1. MailSender および SimpleMailMessage の基本的な使用箇所

次の例は、MailSender および SimpleMailMessage を使用して、誰かがオーダーしたときにメールを送信する方法を示しています。

import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

public class SimpleOrderManager implements OrderManager {

    private MailSender mailSender;
    private SimpleMailMessage templateMessage;

    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void setTemplateMessage(SimpleMailMessage templateMessage) {
        this.templateMessage = templateMessage;
    }

    public void placeOrder(Order order) {

        // Do the business calculations...

        // Call the collaborators to persist the order...

        // Create a thread safe "copy" of the template message and customize it
        SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
        msg.setTo(order.getCustomer().getEmailAddress());
        msg.setText(
            "Dear " + order.getCustomer().getFirstName()
                + order.getCustomer().getLastName()
                + ", thank you for placing order. Your order number is "
                + order.getOrderNumber());
        try{
            this.mailSender.send(msg);
        }
        catch (MailException ex) {
            // simply log it and go on...
            System.err.println(ex.getMessage());
        }
    }

}

次の例は、前述のコードの Bean 定義を示しています。

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="mail.mycompany.example"/>
</bean>

<!-- this is a template message that we can pre-load with default state -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
    <property name="from" value="[email protected] (英語)  "/>
    <property name="subject" value="Your order"/>
</bean>

<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
    <property name="mailSender" ref="mailSender"/>
    <property name="templateMessage" ref="templateMessage"/>
</bean>

6.1.2. JavaMailSender および MimeMessagePreparator の使用

このセクションでは、MimeMessagePreparator コールバックインターフェースを使用する OrderManager の別の実装について説明します。次の例では、mailSender プロパティのタイプは JavaMailSender であるため、JavaMail MimeMessage クラスを使用できます。

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import javax.mail.internet.MimeMessage;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessagePreparator;

public class SimpleOrderManager implements OrderManager {

    private JavaMailSender mailSender;

    public void setMailSender(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void placeOrder(final Order order) {
        // Do the business calculations...
        // Call the collaborators to persist the order...

        MimeMessagePreparator preparator = new MimeMessagePreparator() {
            public void prepare(MimeMessage mimeMessage) throws Exception {
                mimeMessage.setRecipient(Message.RecipientType.TO,
                        new InternetAddress(order.getCustomer().getEmailAddress()));
                mimeMessage.setFrom(new InternetAddress("[email protected] (英語)  "));
                mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
                        order.getCustomer().getLastName() + ", thanks for your order. " +
                        "Your order number is " + order.getOrderNumber() + ".");
            }
        };

        try {
            this.mailSender.send(preparator);
        }
        catch (MailException ex) {
            // simply log it and go on...
            System.err.println(ex.getMessage());
        }
    }

}
メールコードは分野横断的な問題であり、カスタム Spring AOP アスペクトにリファクタリングする候補になる可能性があります。これは、OrderManager ターゲットの適切な結合点で実行できます。

Spring Framework のメールサポートは、標準の JavaMail 実装に付属しています。詳細については、関連する javadoc を参照してください。

6.2. JavaMail MimeMessageHelper の使用

JavaMail メッセージを処理するときに非常に便利なクラスは org.springframework.mail.javamail.MimeMessageHelper です。これにより、冗長な JavaMail API を使用する必要がなくなります。MimeMessageHelper を使用すると、次の例に示すように、MimeMessage を簡単に作成できます。

// of course you would use DI in any real-world cases
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");

MimeMessage message = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setTo("[email protected] (英語)  ");
helper.setText("Thank you for ordering!");

sender.send(message);

6.2.1. 添付ファイルとインラインリソースの送信

マルチパートメールメッセージでは、添付ファイルとインラインリソースの両方を使用できます。インラインリソースの例には、メッセージで使用したいが添付ファイルとして表示したくないイメージまたはスタイルシートが含まれます。

添付

次の例は、MimeMessageHelper を使用して、単一の JPEG イメージが添付されたメールを送信する方法を示しています。

JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");

MimeMessage message = sender.createMimeMessage();

// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected] (英語)  ");

helper.setText("Check out this image!");

// let's attach the infamous windows Sample file (this time copied to c:/)
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addAttachment("CoolImage.jpg", file);

sender.send(message);
インラインリソース

次の例は、MimeMessageHelper を使用して、インラインイメージを含むメールを送信する方法を示しています。

JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");

MimeMessage message = sender.createMimeMessage();

// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected] (英語)  ");

// use the true flag to indicate the text included is HTML
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);

// let's include the infamous windows Sample file (this time copied to c:/)
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addInline("identifier1234", res);

sender.send(message);
インラインリソースは、指定された Content-ID (上記の例では identifier1234)を使用して MimeMessage に追加されます。テキストとリソースを追加する順序は非常に重要です。最初にテキストを追加してから、リソースを追加してください。逆にそれをやっていると、うまくいきません。

6.2.2. テンプレートライブラリを使用してメールコンテンツを作成する

前のセクションで示した例のコードは、message.setText(..) などのメソッド呼び出しを使用して、メールメッセージのコンテンツを明示的に作成しました。これは単純なケースでは問題ありませんが、前述の例のコンテキストでは問題ありません。この例では、API の基本を説明することを目的としています。

ただし、一般的なエンタープライズアプリケーションでは、多くの場合、開発者は前述のアプローチを使用してメールメッセージのコンテンツを作成しません。

  • Java コードで HTML ベースのメールコンテンツを作成するのは面倒でエラーが発生しやすくなります。

  • 表示ロジックとビジネスロジックの間には明確な分離はありません。

  • メールコンテンツの表示構造を変更するには、Java コードの記述、再コンパイル、再デプロイなどが必要です。

通常、これらの課題に対処するために取られるアプローチは、テンプレートライブラリ(FreeMarker など)を使用してメールコンテンツの表示構造を定義することです。これにより、コードは、メールテンプレートでレンダリングされるデータの作成とメールの送信のみに任されます。メールメッセージの内容がやや複雑になる場合は間違いなくベストプラクティスであり、Spring Framework の FreeMarker のサポートクラスを使用すると、非常に簡単になります。

7. タスクの実行とスケジューリング

Spring Framework は、それぞれ TaskExecutor および TaskScheduler インターフェースを使用したタスクの非同期実行およびスケジューリングの抽象化を提供します。Spring は、アプリケーションサーバー環境内の CommonJ へのスレッドプールまたは委譲をサポートするこれらのインターフェースの実装も備えています。最終的に、共通インターフェースの背後にあるこれらの実装を使用することで、Java SE 5、Java SE 6、および Java EE 環境の違いが抽象化されます。

Spring は、Timer (1.3 以降の JDK の一部)および Quartz スケジューラー(https://www.quartz-scheduler.org/ (英語) )によるスケジューリングをサポートする統合クラスも備えています。FactoryBean と Timer または Trigger インスタンスへのオプションの参照をそれぞれ使用することにより、これらのスケジューラーを両方ともセットアップできます。さらに、Quartz スケジューラと Timer の両方の便利なクラスを使用して、既存のターゲットオブジェクトのメソッドを呼び出すことができます(通常の MethodInvokingFactoryBean 操作に類似)。

7.1. Spring TaskExecutor の抽象化

エグゼキュータは、スレッドプールの概念の JDK 名です。「executor」の命名は、基礎となる実装が実際にプールであるという保証がないという事実によるものです。エグゼキュータはシングルスレッドでも同期でもかまいません。Spring の抽象化により、Java SE 環境と Java EE 環境の間で実装の詳細が隠されます。

Spring の TaskExecutor インターフェースは、java.util.concurrent.Executor インターフェースと同一です。実際、元々、存在する主な理由は、スレッドプールを使用するときに Java 5 の必要性を抽象化することでした。インターフェースには、スレッドプールのセマンティクスと設定に基づいて実行するタスクを受け入れる単一のメソッド(execute(Runnable task))があります。

TaskExecutor はもともと、他の Spring コンポーネントに、必要に応じてスレッドプーリングの抽象化を提供するために作成されました。ApplicationEventMulticaster、JMS の AbstractMessageListenerContainer、および Quartz 統合などのコンポーネントはすべて、TaskExecutor 抽象化を使用してスレッドをプールします。ただし、Bean がスレッドプーリング動作を必要とする場合、この抽象化を独自のニーズに使用することもできます。

7.1.1. TaskExecutor タイプ

Spring には、TaskExecutor の事前構築済み実装が多数含まれています。おそらく、自分で実装する必要はないはずです。Spring が提供するバリアントは次のとおりです。

  • SyncTaskExecutor: この実装では、呼び出しを非同期で実行しません。代わりに、各呼び出しは呼び出しスレッドで行われます。これは主に、単純なテストケースなど、マルチスレッドが不要な状況で使用されます。

  • SimpleAsyncTaskExecutor: この実装は、スレッドを再利用しません。むしろ、呼び出しごとに新しいスレッドを起動します。ただし、スロットが解放されるまで、制限を超える呼び出しをブロックする同時実行制限はサポートします。真のプーリングを探している場合は、このリストの後半の ThreadPoolTaskExecutor を参照してください。

  • ConcurrentTaskExecutor: この実装は、java.util.concurrent.Executor インスタンス用のアダプターです。Executor 構成パラメーターを Bean プロパティとして公開する代替手段(ThreadPoolTaskExecutor)があります。ConcurrentTaskExecutor を直接使用する必要はほとんどありません。ただし、ThreadPoolTaskExecutor がニーズに対して十分な柔軟性がない場合は、ConcurrentTaskExecutor が代替です。

  • ThreadPoolTaskExecutor: この実装が最も一般的に使用されます。java.util.concurrent.ThreadPoolExecutor を構成するための Bean プロパティを公開し、TaskExecutor にラップします。別の種類の java.util.concurrent.Executor に適応する必要がある場合は、代わりに ConcurrentTaskExecutor を使用することをお勧めします。

  • WorkManagerTaskExecutor: この実装は、そのバッキングサービスプロバイダーとして CommonJ WorkManager を使用し、Spring アプリケーションコンテキスト内の WebLogic または WebSphere で CommonJ ベースのスレッドプール統合をセットアップするための中心的な便利なクラスです。

  • DefaultManagedTaskExecutor: この実装では、JSR-236 互換のランタイム環境(Java EE 7+ アプリケーションサーバーなど)で JNDI が取得した ManagedExecutorService を使用し、そのために CommonJ WorkManager を置き換えます。

7.1.2. TaskExecutor を使用する

Spring の TaskExecutor 実装は、単純な JavaBeans として使用されます。次の例では、ThreadPoolTaskExecutor を使用して一連のメッセージを非同期に出力する Bean を定義します。

import org.springframework.core.task.TaskExecutor;

public class TaskExecutorExample {

    private class MessagePrinterTask implements Runnable {

        private String message;

        public MessagePrinterTask(String message) {
            this.message = message;
        }

        public void run() {
            System.out.println(message);
        }
    }

    private TaskExecutor taskExecutor;

    public TaskExecutorExample(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    public void printMessages() {
        for(int i = 0; i < 25; i++) {
            taskExecutor.execute(new MessagePrinterTask("Message" + i));
        }
    }
}

ご覧のように、プールからスレッドを取得して自分で実行するのではなく、Runnable をキューに追加します。次に、TaskExecutor はその内部ルールを使用して、タスクが実行されるタイミングを決定します。

TaskExecutor が使用するルールを構成するには、簡単な Bean プロパティを公開します。

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="5"/>
    <property name="maxPoolSize" value="10"/>
    <property name="queueCapacity" value="25"/>
</bean>

<bean id="taskExecutorExample" class="TaskExecutorExample">
    <constructor-arg ref="taskExecutor"/>
</bean>

7.2. Spring TaskScheduler の抽象化

TaskExecutor 抽象化に加えて、Spring 3.0 は、将来のある時点で実行するタスクをスケジュールするためのさまざまな方法を備えた TaskScheduler を導入しました。次のリストは、TaskScheduler インターフェース定義を示しています。

public interface TaskScheduler {

    ScheduledFuture schedule(Runnable task, Trigger trigger);

    ScheduledFuture schedule(Runnable task, Instant startTime);

    ScheduledFuture schedule(Runnable task, Date startTime);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);

    ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);

    ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}

最も単純な方法は、Runnable と Date のみを使用する schedule という名前の方法です。これにより、指定した時間後にタスクが 1 回実行されます。他のすべての方法では、タスクを繰り返し実行するようにスケジュールできます。固定レートおよび固定遅延メソッドは、単純な定期的な実行用ですが、Trigger を受け入れるメソッドははるかに柔軟です。

7.2.1. Trigger インターフェース

Trigger インターフェースは基本的に JZR-236 に触発されており、Spring 3.0, の時点ではまだ正式に実装されていません。Trigger の基本的な考え方は、実行時間は過去の実行結果または任意の条件に基づいて決定される可能性があるということです。これらの決定が前の実行の結果を考慮に入れている場合、その情報は TriggerContext 内で利用可能です。次のリストに示すように、Trigger インターフェース自体は非常に単純です。

public interface Trigger {

    Date nextExecutionTime(TriggerContext triggerContext);
}

TriggerContext は最も重要な部分です。すべての関連データをカプセル化し、必要に応じて将来の拡張に備えています。TriggerContext はインターフェースです(デフォルトでは SimpleTriggerContext 実装が使用されます)。次のリストは、Trigger 実装で使用可能なメソッドを示しています。

public interface TriggerContext {

    Date lastScheduledExecutionTime();

    Date lastActualExecutionTime();

    Date lastCompletionTime();
}

7.2.2. Trigger の実装

Spring は、Trigger インターフェースの 2 つの実装を提供します。最も興味深いのは CronTrigger です。これにより、cron 式に基づいてタスクのスケジューリングが可能になります。例:次のタスクは、毎時 15 分実行するようにスケジュールされていますが、平日の 9 時から 5 時までの「営業時間」にのみ実行されます。

scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));

もう 1 つの実装は、固定期間、オプションの初期遅延値、および期間を固定レートまたは固定遅延として解釈する必要があるかどうかを示すブール値を受け入れる PeriodicTrigger です。TaskScheduler インターフェースは、固定レートまたは固定遅延でタスクをスケジュールするためのメソッドをすでに定義しているため、これらのメソッドは可能な限り直接使用する必要があります。PeriodicTrigger 実装の価値は、Trigger 抽象化に依存するコンポーネント内で使用できることです。例:定期的なトリガー、cron ベースのトリガー、さらにはカスタムトリガーの実装を交換可能に使用できると便利な場合があります。このようなコンポーネントは依存性注入を利用できるため、そのような Triggers を外部で構成できるため、簡単に変更または拡張できます。

7.2.3. TaskScheduler の実装

Spring の TaskExecutor 抽象化と同様に、TaskScheduler 配置の主な利点は、アプリケーションのスケジューリングニーズがデプロイ環境から切り離されていることです。この抽象化レベルは、アプリケーション自体によってスレッドが直接作成されるべきではないアプリケーションサーバー環境にデプロイする場合に特に関連します。このようなシナリオの場合、Spring は、WebLogic または WebSphere の CommonJ TimerManager に委譲する TimerManagerTaskScheduler と、Java EE 7+ 環境の JSR-236 ManagedScheduledExecutorService に委譲する最近の DefaultManagedTaskScheduler を提供します。通常、両方とも JNDI ルックアップで構成されます。

外部スレッド管理が要件でない場合は常に、より簡単な代替手段はアプリケーション内のローカル ScheduledExecutorService セットアップです。これは Spring の ConcurrentTaskScheduler を介して調整できます。便宜上、Spring は ThreadPoolTaskScheduler も提供します。ThreadPoolTaskScheduler は、ScheduledExecutorService に内部的に委譲して、ThreadPoolTaskExecutor のラインに沿って共通の Bean スタイルの構成を提供します。これらのバリアントは、寛容なアプリケーションサーバー環境、特に Tomcat および Jetty でのローカルに埋め込まれたスレッドプールのセットアップでも完全に機能します。

7.3. スケジューリングと非同期実行のアノテーションサポート

Spring は、タスクスケジューリングと非同期メソッド実行の両方にアノテーションサポートを提供します。

7.3.1. スケジューリングアノテーションを有効にする

@Scheduled および @Async アノテーションのサポートを有効にするには、次の例に示すように、@EnableScheduling および @EnableAsync を @Configuration クラスのいずれかに追加できます。

@Configuration
@EnableAsync
@EnableScheduling
public class AppConfig {
}

アプリケーションに関連するアノテーションを選択できます。例: @Scheduled のサポートのみが必要な場合は、@EnableAsync を省略できます。よりきめ細かな制御を行うには、SchedulingConfigurer インターフェース、AsyncConfigurer インターフェース、またはその両方を追加で実装できます。詳細については、SchedulingConfigurer(Javadoc) および AsyncConfigurer(Javadoc) javadoc を参照してください。

XML 構成が必要な場合は、次の例に示すように、<task:annotation-driven> 要素を使用できます。

<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>

上記の XML では、@Async アノテーションを持つメソッドに対応するタスクを処理するためのエグゼキューターリファレンスが提供され、@Scheduled アノテーションが付けられたメソッドを管理するためのスケジューラリファレンスが提供されることに注意してください。

@Async アノテーションを処理するためのデフォルトのアドバイスモードは proxy であり、プロキシを介した呼び出しのみの傍受を許可します。同じクラス内のローカル呼び出しは、そのように傍受することはできません。より高度なインターセプトモードについては、コンパイル時またはロード時のウィービングと組み合わせて aspectj モードに切り替えることを検討してください。

7.3.2. @Scheduled アノテーション

@Scheduled アノテーションをトリガーメタデータとともにメソッドに追加できます。例:次のメソッドは固定遅延で 5 秒ごとに呼び出されます。つまり、期間は先行する各呼び出しの補完時間から測定されます。

@Scheduled(fixedDelay=5000)
public void doSomething() {
    // something that should run periodically
}

固定レートの実行が必要な場合は、アノテーション内で指定されたプロパティ名を変更できます。次のメソッドは、5 秒ごとに呼び出されます(各呼び出しの連続する開始時間の間で測定されます)。

@Scheduled(fixedRate=5000)
public void doSomething() {
    // something that should run periodically
}

固定遅延タスクと固定レートタスクの場合、次の fixedRate の例に示すように、メソッドの最初の実行まで待機するミリ秒数を指定することにより、初期遅延を指定できます。

@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
    // something that should run periodically
}

単純な定期的なスケジューリングでは十分な表現力がない場合は、cron 式を指定できます。次の例は、平日にのみ実行されます。

@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
    // something that should run on weekdays only
}
zone 属性を使用して、cron 式が解決されるタイムゾーンを指定することもできます。

スケジュールするメソッドには void リターンが必要であり、引数を期待してはならないことに注意してください。メソッドがアプリケーションコンテキストから他のオブジェクトと対話する必要がある場合、それらは通常、依存性注入によって提供されます。

Spring Framework 4.3, 以降、@Scheduled メソッドは任意のスコープの Bean でサポートされます。

実行時に同じ @Scheduled アノテーションクラスの複数のインスタンスを初期化していないことを確認してください。ただし、各インスタンスへのコールバックをスケジュールしたい場合を除きます。これに関連して、@Scheduled アノテーションが付けられ、コンテナーに通常の Spring Bean として登録されている Bean クラスで @Configurable を使用しないようにしてください。それ以外の場合は、各 @Scheduled メソッドが 2 回呼び出されるため、二重の初期化(コンテナーを介して 1 回、@Configurable を介して 1 回)が発生します。

7.3.3. @Async アノテーション

メソッドに @Async アノテーションを付けて、そのメソッドの呼び出しが非同期に行われるようにすることができます。つまり、呼び出し元は呼び出し時にすぐに戻りますが、メソッドの実際の実行は Spring TaskExecutor に送信されたタスクで発生します。最も単純なケースでは、次の例に示すように、void を返すメソッドにアノテーションを適用できます。

@Async
void doSomething() {
    // this will be run asynchronously
}

@Scheduled アノテーションが付けられたメソッドとは異なり、これらのメソッドは、コンテナーによって管理されているスケジュールされたタスクからではなく、実行時に呼び出し元によって「通常」の方法で呼び出されるため、引数を期待できます。例:次のコードは、@Async アノテーションの正当なアプリケーションです。

@Async
void doSomething(String s) {
    // this will be run asynchronously
}

値を返すメソッドでさえ非同期に呼び出すことができます。ただし、そのようなメソッドには Future -typed 戻り値が必要です。これにより、非同期実行の利点が得られるため、呼び出し元は、Future で get() を呼び出す前に他のタスクを実行できます。次の例は、値を返すメソッドで @Async を使用する方法を示しています。

@Async
Future<String> returnSomething(int i) {
    // this will be run asynchronously
}
@Async メソッドは、通常の java.util.concurrent.Future 戻り値型を宣言するだけでなく、Spring の org.springframework.util.concurrent.ListenableFuture を宣言することもできます。Spring 4.2,JDK 8 の java.util.concurrent.CompletableFuture 以降では、非同期タスクとのより豊かな相互作用と、追加の処理ステップを含む即時の合成が可能です。

@Async を @PostConstruct などのライフサイクルコールバックと組み合わせて使用することはできません。Spring Bean を非同期的に初期化するには、現在、次の例に示すように、個別の初期化 Spring Bean を使用して、ターゲットで @Async アノテーション付きメソッドを呼び出す必要があります。

public class SampleBeanImpl implements SampleBean {

    @Async
    void doSomething() {
        // ...
    }

}

public class SampleBeanInitializer {

    private final SampleBean bean;

    public SampleBeanInitializer(SampleBean bean) {
        this.bean = bean;
    }

    @PostConstruct
    public void initialize() {
        bean.doSomething();
    }

}
@Async に相当する直接的な XML はありません。そのようなメソッドは、非同期的に実行するために外部で再宣言するのではなく、最初は非同期実行用に設計する必要があるためです。ただし、Spring AOP を使用して Spring の AsyncExecutionInterceptor をカスタムポイントカットと組み合わせて手動でセットアップできます。

7.3.4. @Async によるエグゼキューター修飾

デフォルトでは、メソッドで @Async を指定する場合、使用されるエグゼキューターは、非同期サポートを有効にするときに構成されたエグゼキューター、つまり XML または AsyncConfigurer 実装を使用している場合は「アノテーション駆動型」要素です。ただし、特定のメソッドの実行時にデフォルト以外のエグゼキューターを使用する必要があることを示す必要がある場合は、@Async アノテーションの value 属性を使用できます。次の例は、その方法を示しています。

@Async("otherExecutor")
void doSomething(String s) {
    // this will be run asynchronously by "otherExecutor"
}

この場合、"otherExecutor" は、Spring コンテナー内の Executor Bean の名前にすることも、Executor に関連付けられた修飾子の名前にすることもできます(たとえば、<qualifier> エレメントまたは Spring の @Qualifier アノテーションで指定)。

7.3.5. @Async を使用した例外管理

@Async メソッドに Future -typed 戻り値がある場合、メソッド実行中にスローされた例外を管理するのは簡単です。この例外は、Future の結果で get を呼び出すときにスローされるためです。ただし、void 戻り型では、例外はキャッチされず、送信できません。このような例外を処理する AsyncUncaughtExceptionHandler を提供できます。次の例は、その方法を示しています。

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // handle exception
    }
}

デフォルトでは、例外は単にログに記録されます。AsyncConfigurer または <task:annotation-driven/> XML エレメントを使用して、カスタム AsyncUncaughtExceptionHandler を定義できます。

7.4. task 名前空間

バージョン 3.0 の時点で、Spring には TaskExecutor および TaskScheduler インスタンスを構成するための XML 名前空間が含まれています。また、トリガーを使用してスケジュールされるタスクを構成する便利な方法も提供します。

7.4.1. 「スケジューラー」要素

次の要素は、指定されたスレッドプールサイズで ThreadPoolTaskScheduler インスタンスを作成します。

<task:scheduler id="scheduler" pool-size="10"/>

id 属性に指定された値は、プール内のスレッド名のプレフィックスとして使用されます。scheduler 要素は比較的簡単です。pool-size 属性を指定しない場合、デフォルトのスレッドプールには単一のスレッドしかありません。スケジューラの他の構成オプションはありません。

7.4.2. executor 要素

以下は、ThreadPoolTaskExecutor インスタンスを作成します。

<task:executor id="executor" pool-size="10"/>

前のセクションで示したスケジューラーと同様に、id 属性に指定された値は、プール内のスレッド名のプレフィックスとして使用されます。プールサイズに関する限り、executor 要素は scheduler 要素よりも多くの構成オプションをサポートしています。ひとつには、ThreadPoolTaskExecutor のスレッドプール自体がより構成可能です。エグゼキュータのスレッドプールは、単一のサイズではなく、コアと最大サイズに異なる値を設定できます。単一の値を指定すると、executor には固定サイズのスレッドプールがあります(コアサイズと最大サイズは同じです)。ただし、executor 要素の pool-size 属性は、min-max の形式の範囲も受け入れます。次の例では、5 の最小値と 25 の最大値を設定します。

<task:executor
        id="executorWithPoolSizeRange"
        pool-size="5-25"
        queue-capacity="100"/>

上記の構成では、queue-capacity 値も提供されています。スレッドプールの構成も、エグゼキューターのキュー容量を考慮して考慮する必要があります。プールサイズとキュー容量の関連の詳細については、ThreadPoolExecutor のドキュメントを参照してください。主なアイデアは、タスクが送信されると、アクティブなスレッドの数が現在コアサイズより少ない場合、エグゼキューターは最初に空きスレッドを使用しようとすることです。コアサイズに達した場合、その容量にまだ達していない限り、タスクはキューに追加されます。そのときだけ、キューの容量に達した場合、エグゼキューターはコアサイズを超える新しいスレッドを作成します。最大サイズにも達している場合、エグゼキューターはタスクを拒否します。

デフォルトでは、キューは無制限ですが、プールスレッドがすべてビジーである間に十分なタスクがキューに追加されると OutOfMemoryErrors につながる可能性があるため、これはめったに望ましい構成ではありません。さらに、キューが制限されていない場合、最大サイズはまったく効果がありません。executor は常にコアサイズを超えて新しいスレッドを作成する前にキューを試行するため、スレッドプールがコアサイズを超えて大きくなるには、キューに有限の容量が必要です(これは、使用時に固定サイズのプールのみが実用的な場合です無制限のキュー)。

上記のように、タスクが拒否された場合を考えてください。デフォルトでは、タスクが拒否されると、スレッドプールエグゼキューターは TaskRejectedException をスローします。ただし、拒否ポリシーは実際に構成可能です。AbortPolicy 実装であるデフォルトの拒否ポリシーを使用すると、例外がスローされます。重い負荷でいくつかのタスクをスキップできるアプリケーションでは、代わりに DiscardPolicy または DiscardOldestPolicy を構成できます。重い負荷で送信されたタスクを調整する必要があるアプリケーションに適した別のオプションは、CallerRunsPolicy です。このポリシーは、例外をスローしたりタスクを破棄したりする代わりに、submit メソッドを呼び出しているスレッドにタスク自体を実行させます。そのような発呼者は、そのタスクの実行中はビジーであり、他のタスクをすぐに送信できないという考えです。スレッドプールとキューの制限を維持しながら、受信負荷を調整する簡単な方法を提供します。通常、これにより、executor は処理中のタスクを「追いつく」ことができ、キュー、プール、またはその両方の容量を解放できます。executor 要素の rejection-policy 属性で使用可能な値の列挙から、これらのオプションのいずれかを選択できます。

次の例は、さまざまな動作を指定する多数の属性を持つ executor 要素を示しています。

<task:executor
        id="executorWithCallerRunsPolicy"
        pool-size="5-25"
        queue-capacity="100"
        rejection-policy="CALLER_RUNS"/>

最後に、keep-alive 設定は、スレッドが停止する前にアイドル状態を維持できる時間制限(秒単位)を決定します。現在プールにあるスレッドのコア数を超える場合、タスクを処理せずにこの時間待機した後、余分なスレッドは停止します。時間値がゼロの場合、タスクキューにフォローアップ作業が残っていないまま、タスクの実行直後に余分なスレッドが停止します。次の例では、keep-alive 値を 2 分に設定します。

<task:executor
        id="executorWithKeepAlive"
        pool-size="5-25"
        keep-alive="120"/>

7.4.3. 「スケジュールされたタスク」要素

Spring のタスク名前空間の最も強力な機能は、Spring アプリケーションコンテキスト内でスケジュールされるようにタスクを構成するためのサポートです。これは、メッセージ駆動型 POJO を構成するために JMS 名前空間によって提供されるものなど、Spring の他の「メソッド呼び出し側」と同様のアプローチに従います。基本的に、ref 属性は任意の Spring 管理対象オブジェクトを指すことができ、method 属性はそのオブジェクトで呼び出されるメソッドの名前を提供します。次のリストは、簡単な例を示しています。

<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="beanA" method="methodA" fixed-delay="5000"/>
</task:scheduled-tasks>

<task:scheduler id="myScheduler" pool-size="10"/>

スケジューラは外部要素によって参照され、個々のタスクにはトリガーメタデータの構成が含まれます。前の例では、そのメタデータは、各タスクの実行が完了してから待機するミリ秒数を示す固定遅延を持つ定期的なトリガーを定義しています。別のオプションは fixed-rate で、以前の実行にかかった時間に関係なく、メソッドを実行する頻度を示します。さらに、fixed-delay タスクと fixed-rate タスクの両方で、「initial-delay」パラメーターを指定して、メソッドの最初の実行までに待機するミリ秒数を指定できます。より細かく制御するには、代わりに cron 属性を指定できます。次の例は、これらの他のオプションを示しています。

<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="beanA" method="methodA" fixed-delay="5000" initial-delay="1000"/>
    <task:scheduled ref="beanB" method="methodB" fixed-rate="5000"/>
    <task:scheduled ref="beanC" method="methodC" cron="*/5 * * * * MON-FRI"/>
</task:scheduled-tasks>

<task:scheduler id="myScheduler" pool-size="10"/>

7.5. Quartz スケジューラーの使用

Quartz は、TriggerJob および JobDetail オブジェクトを使用して、あらゆる種類のジョブのスケジューリングを実現します。Quartz の背後にある基本概念については、https://www.quartz-scheduler.org/ (英語) を参照してください。Spring は、便宜上、Spring ベースのアプリケーション内で Quartz の使用を簡素化するいくつかのクラスを提供します。

7.5.1. JobDetailFactoryBean を使用する

Quartz JobDetail オブジェクトには、ジョブの実行に必要なすべての情報が含まれています。Spring は JobDetailFactoryBean を提供し、XML 構成のために Bean スタイルのプロパティを提供します。次の例を考えてみましょう。

<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="example.ExampleJob"/>
    <property name="jobDataAsMap">
        <map>
            <entry key="timeout" value="5"/>
        </map>
    </property>
</bean>

ジョブ詳細構成には、ジョブの実行に必要なすべての情報が含まれています(ExampleJob)。タイムアウトは、ジョブデータマップで指定されます。ジョブデータマップは JobExecutionContext を介して使用できます(実行時に渡されます)が、JobDetail はジョブインスタンスのプロパティにマップされたジョブデータからそのプロパティも取得します。次の例では、ExampleJob には timeout という名前の Bean プロパティが含まれ、JobDetail にはそれが自動的に適用されます。

package example;

public class ExampleJob extends QuartzJobBean {

    private int timeout;

    /**
     * Setter called after the ExampleJob is instantiated
     * with the value from the JobDetailFactoryBean (5)
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
        // do the actual work
    }

}

ジョブデータマップのすべての追加プロパティも利用できます。

name プロパティと group プロパティを使用して、ジョブの名前とグループをそれぞれ変更できます。デフォルトでは、ジョブの名前は JobDetailFactoryBean の Bean 名(上記の例では exampleJob)と一致します。

7.5.2. MethodInvokingJobDetailFactoryBean を使用する

多くの場合、特定のオブジェクトのメソッドを呼び出すだけで済みます。MethodInvokingJobDetailFactoryBean を使用すると、次の例に示すように、まさにこれを実行できます。

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="exampleBusinessObject"/>
    <property name="targetMethod" value="doIt"/>
</bean>

上記の例では、次の例に示すように、doIt メソッドが exampleBusinessObject メソッドで呼び出されます。

public class ExampleBusinessObject {

    // properties and collaborators

    public void doIt() {
        // do the actual work
    }
}
<bean id="exampleBusinessObject" class="examples.ExampleBusinessObject"/>

MethodInvokingJobDetailFactoryBean を使用することにより、メソッドを呼び出すだけの 1 行のジョブを作成する必要がなくなります。実際のビジネスオブジェクトを作成し、詳細オブジェクトを結び付けるだけです。

デフォルトでは、Quartz ジョブはステートレスであり、ジョブが互いに干渉する機能があります。同じ JobDetail に 2 つのトリガーを指定した場合、最初のジョブが完了する前に 2 番目のトリガーが開始する機能があります。JobDetail クラスが Stateful インターフェースを実装している場合、これは起こりません。2 番目のジョブは、最初のジョブが完了する前に開始されません。MethodInvokingJobDetailFactoryBean から生じるジョブを非並行にするには、次の例に示すように、concurrent フラグを false に設定します。

<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="exampleBusinessObject"/>
    <property name="targetMethod" value="doIt"/>
    <property name="concurrent" value="false"/>
</bean>
デフォルトでは、ジョブは並行して実行されます。

7.5.3. トリガーと SchedulerFactoryBean を使用してジョブを結び付ける

ジョブの詳細とジョブを作成しました。また、特定のオブジェクトでメソッドを呼び出すことができる便利な Bean を確認しました。もちろん、ジョブ自体をスケジュールする必要があります。これは、トリガーと SchedulerFactoryBean を使用して行われます。Quartz 内ではいくつかのトリガーを使用できます。Spring は、CronTriggerFactoryBean と SimpleTriggerFactoryBean の便利なデフォルトを備えた 2 つの Quartz FactoryBean 実装を提供します。

トリガーをスケジュールする必要があります。Spring は、プロパティとして設定されるトリガーを公開する SchedulerFactoryBean を提供します。SchedulerFactoryBean は、これらのトリガーを使用して実際のジョブをスケジュールします。

次のリストでは、SimpleTriggerFactoryBean と CronTriggerFactoryBean の両方を使用しています。

<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
    <!-- see the example of method invoking job above -->
    <property name="jobDetail" ref="jobDetail"/>
    <!-- 10 seconds -->
    <property name="startDelay" value="10000"/>
    <!-- repeat every 50 seconds -->
    <property name="repeatInterval" value="50000"/>
</bean>

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="exampleJob"/>
    <!-- run every morning at 6 AM -->
    <property name="cronExpression" value="0 0 6 * * ?"/>
</bean>

上記の例では、2 つのトリガーをセットアップします。1 つは 50 秒ごとに実行され、開始遅延は 10 秒で、もう 1 つは毎朝午前 6 時に実行されます。すべてをファイナライズするには、次の例に示すように、SchedulerFactoryBean をセットアップする必要があります。

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cronTrigger"/>
            <ref bean="simpleTrigger"/>
        </list>
    </property>
</bean>

SchedulerFactoryBean には、ジョブの詳細で使用されるカレンダー、Quartz をカスタマイズするためのプロパティなど、その他のプロパティを使用できます。詳細については、SchedulerFactoryBean(Javadoc) javadoc を参照してください。

8. キャッシュの抽象化

バージョン 3.1 以降、Spring Framework は既存の Spring アプリケーションに透過的にキャッシュを追加するためのサポートを提供します。トランザクションのサポートと同様に、キャッシングの抽象化により、コードへの影響を最小限に抑えながら、さまざまなキャッシングソリューションを一貫して使用できます。

Spring 4.1, 以降、JSR-107 アノテーションと、より多くのカスタマイズオプションのサポートにより、キャッシュの抽象化が大幅に拡張されました。

8.1. キャッシュの抽象化について

キャッシュとバッファ

「バッファ」と「キャッシュ」という用語は、同じ意味で使用される傾向があります。ただし、それらは異なるものを表していることに注意してください。従来、バッファは、高速エンティティと低速エンティティ間のデータの中間一時ストアとして使用されていました。1 つのパーティが他のパーティを待つ必要があるため(パフォーマンスに影響します)、バッファはデータのブロック全体を小さなチャンクではなく一度に移動できるようにすることでこれを軽減します。データは、バッファから 1 回だけ書き込まれ、読み取られます。さらに、バッファは、それを認識している少なくとも 1 つのパーティに表示されます。

一方、キャッシュは定義上、非表示であり、どちらの当事者もキャッシュが発生することを認識していません。また、パフォーマンスは向上しますが、同じデータを高速で複数回読み取らせることで改善されます。

バッファとキャッシュの違いの詳細については、こちらを参照してください (英語)

コアでは、キャッシュの抽象化は Java メソッドにキャッシングを適用するため、キャッシュで利用可能な情報に基づいて実行回数を減らします。つまり、ターゲットとなるメソッドが呼び出されるたびに、抽象化により、指定された引数に対してメソッドがすでに呼び出されているかどうかをチェックするキャッシュ動作が適用されます。呼び出された場合、実際のメソッドを呼び出さなくても、キャッシュされた結果が返されます。メソッドが呼び出されていない場合は呼び出され、結果がキャッシュされてユーザーに返されるため、次にメソッドが呼び出されたときに、キャッシュされた結果が返されます。このように、特定のパラメーターセットに対して(CPU または IO に制限された)高価なメソッドを 1 回だけ呼び出すことができ、その結果は、メソッドを実際に再度呼び出すことなく再利用できます。キャッシングロジックは、呼び出し側への干渉なしに透過的に適用されます。

このアプローチは、呼び出された回数に関係なく、特定の入力(または引数)に対して同じ出力(結果)を返すことが保証されているメソッドに対してのみ機能します。

キャッシュの抽象化は、キャッシュのコンテンツを更新したり、1 つまたはすべてのエントリを削除したりする機能など、他のキャッシュ関連の操作を提供します。これらは、アプリケーションの過程で変化する可能性のあるデータをキャッシュが処理する場合に役立ちます。

Spring Framework の他のサービスと同様に、キャッシングサービスは抽象化(キャッシュ実装ではない)であり、キャッシュデータを保存するために実際のストレージを使用する必要があります。つまり、抽象化によりキャッシングロジックを記述する必要がなくなりますが、実際のデータストアを提供します。この抽象化は、org.springframework.cache.Cache および org.springframework.cache.CacheManager インターフェースによって実現されます。

Spring は、その抽象化のいくつかの実装を提供します。JDK java.util.concurrent.ConcurrentMap ベースのキャッシュ、Ehcache 2.x (英語) 、Gemfire キャッシュ、Caffeine (GitHub) 、および JSR-107 準拠のキャッシュ(Ehcache 3.x など)です。他のキャッシュストアとプロバイダーのプラグインの詳細については、異なるバックエンドキャッシュのプラグインを参照してください。

キャッシング抽象化には、マルチスレッドおよびマルチプロセス環境向けの特別な処理はありません。そのような機能はキャッシュ実装によって処理されるためです。

マルチプロセス環境(つまり、複数のノードにデプロイされたアプリケーション)がある場合、それに応じてキャッシュプロバイダーを構成する必要があります。ユースケースによっては、複数のノード上の同じデータのコピーで十分な場合があります。ただし、アプリケーションの実行中にデータを変更する場合は、他の伝播メカニズムを有効にする必要があります。

特定のアイテムをキャッシュすることは、プログラムによるキャッシュのやり取りで見つかった一般的な get-if-not-found-then-proced-and-put-putally 最終的にコードブロックに直接相当します。ロックは適用されず、複数のスレッドが同じアイテムを同時にロードしようとする場合があります。同じことが追い出しにも当てはまります。複数のスレッドがデータを同時に更新または削除しようとしている場合、古いデータを使用する可能性があります。特定のキャッシュプロバイダーは、その領域で高度な機能を提供します。詳細については、キャッシュプロバイダーのドキュメントを参照してください。

キャッシュの抽象化を使用するには、次の 2 つの側面に注意する必要があります。

  • キャッシュ宣言 : キャッシュする必要があるメソッドとそのポリシーを特定します。

  • キャッシュ構成 : データが格納され、そこから読み取られるバッキングキャッシュ。

8.2. 宣言的なアノテーションベースのキャッシュ

キャッシュ宣言のために、Spring のキャッシュ抽象化は Java アノテーションのセットを提供します:

  • @Cacheable: キャッシュ作成をトリガーします。

  • @CacheEvict: キャッシュエビクションをトリガーします。

  • @CachePut: メソッドの実行を妨げることなくキャッシュを更新します。

  • @Caching: メソッドに適用される複数のキャッシュ操作を再グループ化します。

  • @CacheConfig: クラスレベルでいくつかの一般的なキャッシュ関連の設定を共有します。

8.2.1. @Cacheable アノテーション

名前が示すように、@Cacheable を使用して、キャッシュ可能なメソッド、つまり、結果がキャッシュに保存されるメソッドを区別できます。これにより、後続の呼び出し(同じ引数を使用)で、キャッシュ内の値は返されません。実際にメソッドを呼び出す必要があります。次の例に示すように、最も簡単な形式では、アノテーション宣言にはアノテーション付きメソッドに関連付けられたキャッシュの名前が必要です。

@Cacheable("books")
public Book findBook(ISBN isbn) {...}

上記のスニペットでは、findBook メソッドは books という名前のキャッシュに関連付けられています。メソッドが呼び出されるたびに、キャッシュがチェックされ、呼び出しがすでに実行されており、繰り返す必要がないかどうかが確認されます。ほとんどの場合、宣言されるキャッシュは 1 つだけですが、アノテーションでは複数の名前を指定できるため、複数のキャッシュが使用されます。この場合、メソッドを呼び出す前に各キャッシュがチェックされます。少なくとも 1 つのキャッシュがヒットした場合、関連する値が返されます。

キャッシュされたメソッドが実際に呼び出されなかった場合でも、値を含まない他のすべてのキャッシュも更新されます。

次の例では、findBook メソッドで @Cacheable を使用しています。

@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
デフォルトのキー生成

キャッシュは基本的にキーと値のストアであるため、キャッシュされたメソッドの各呼び出しは、キャッシュアクセスに適したキーに変換する必要があります。キャッシングの抽象化では、次のアルゴリズムに基づいた単純な KeyGenerator を使用します。

  • パラメーターが指定されていない場合は、SimpleKey.EMPTY を返します。

  • パラメーターが 1 つだけ指定されている場合、そのインスタンスを返します。

  • 複数のパラメーターが指定されている場合、すべてのパラメーターを含む SimpleKey を返します。

パラメーターが自然キーを持ち、有効な hashCode() および equals() メソッドを実装している限り、このアプローチはほとんどのユースケースでうまく機能します。そうでない場合は、戦略を変更する必要があります。

別のデフォルトキージェネレータを提供するには、org.springframework.cache.interceptor.KeyGenerator インターフェースを実装する必要があります。

Spring 4.0. のリリースで変更されたデフォルトの鍵生成戦略以前のバージョンの Spring は、複数の鍵パラメーターに対して、パラメーターの hashCode() のみを考慮し、equals() は考慮しなかった鍵生成戦略を使用していました。これにより、予期しないキーの衝突が発生する可能性があります(背景については SPR-10237 (英語) を参照してください)。新しい SimpleKeyGenerator は、このようなシナリオで複合キーを使用します。

以前のキー戦略を引き続き使用する場合は、非推奨の org.springframework.cache.interceptor.DefaultKeyGenerator クラスを構成するか、カスタムハッシュベースの KeyGenerator 実装を作成できます。

カスタムキー生成宣言

キャッシングは汎用的であるため、ターゲットメソッドには、キャッシュ構造の上に簡単にマッピングできないさまざまなシグネチャーが含まれている可能性が非常に高くなります。これは、ターゲットメソッドに複数の引数があり、そのうちいくつかのみがキャッシュに適している場合に明らかになります(残りはメソッドロジックによってのみ使用されます)。次の例を考えてみましょう。

@Cacheable("books")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

一見すると、2 つの boolean 引数は本の検索方法に影響を与えますが、キャッシュには使用できません。さらに、2 つのうち 1 つだけが重要で、もう 1 つは重要でない場合はどうでしょうか。

そのような場合、@Cacheable アノテーションを使用すると、key 属性を介してキーを生成する方法を指定できます。SpEL を使用して、目的の引数(またはネストされたプロパティ)を選択したり、操作を実行したり、コードを記述したりインターフェースを実装したりすることなく、任意のメソッドを呼び出すことができます。コードベースが大きくなるとメソッドのシグネチャーが大きく異なる傾向があるため、これはデフォルトのジェネレーターよりも推奨されるアプローチです。一部のメソッドではデフォルトの戦略が機能する場合がありますが、すべてのメソッドで機能することはほとんどありません。

次の例では、さまざまな SpEL 宣言を使用しています(SpEL に慣れていない場合は、自分の好みで Spring 式言語を参照してください)。

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

上記のスニペットは、特定の引数、そのプロパティの 1 つ、または任意の(静的)メソッドを選択するのがいかに簡単かを示しています。

キーの生成を担当するアルゴリズムが具体的すぎる場合、または共有する必要がある場合は、操作でカスタム keyGenerator を定義できます。これを行うには、次の例に示すように、使用する KeyGenerator Bean 実装の名前を指定します。

@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
key パラメーターと keyGenerator パラメーターは相互に排他的であり、両方を指定する操作は例外になります。
デフォルトのキャッシュ解決

キャッシング抽象化は、構成された CacheManager を使用して、操作レベルで定義されたキャッシュを取得する単純な CacheResolver を使用します。

別のデフォルトのキャッシュリゾルバーを提供するには、org.springframework.cache.interceptor.CacheResolver インターフェースを実装する必要があります。

カスタムキャッシュ解決

デフォルトのキャッシュ解決は、単一の CacheManager で動作し、複雑なキャッシュ解決の要件を持たないアプリケーションに適しています。

複数のキャッシュマネージャーで動作するアプリケーションの場合、次の例に示すように、各操作に使用する cacheManager を設定できます。

@Cacheable(cacheNames="books", cacheManager="anotherCacheManager") (1)
public Book findBook(ISBN isbn) {...}
1anotherCacheManager を指定します。

また、キー生成の置き換えと同様の方法で、CacheResolver を完全に置き換えることもできます。解決はすべてのキャッシュ操作に対してリクエストされ、実装が実行時引数に基づいて使用するキャッシュを実際に解決できるようにします。次の例は、CacheResolver を指定する方法を示しています。

@Cacheable(cacheResolver="runtimeCacheResolver") (1)
public Book findBook(ISBN isbn) {...}
1CacheResolver を指定します。

Spring 4.1, 以降、この特定の情報はアノテーションの内容に関係なく CacheResolver によって提供できるため、キャッシュアノテーションの value 属性は必須ではなくなりました。

key および keyGenerator と同様に、cacheManager および cacheResolver パラメーターは相互に排他的であり、両方を指定する操作は例外になります。カスタム CacheManager は CacheResolver 実装によって無視されるため。これはおそらく期待するものではありません。

同期キャッシング

マルチスレッド環境では、特定の操作が同じ引数に対して(通常は起動時に)同時に呼び出される場合があります。デフォルトでは、キャッシュの抽象化は何もロックせず、同じ値が数回計算され、キャッシュの目的を無効にします。

これらの特定のケースでは、sync 属性を使用して、基になるキャッシュプロバイダーに、値の計算中にキャッシュエントリをロックするように指示できます。その結果、値の計算でビジー状態にあるスレッドは 1 つだけですが、他のスレッドはキャッシュ内のエントリが更新されるまでブロックされます。次の例は、sync 属性の使用方法を示しています。

@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1sync 属性を使用します。
これはオプションの機能であり、お気に入りのキャッシュライブラリがサポートしていない場合があります。コアフレームワークによって提供されるすべての CacheManager 実装は、それをサポートします。詳細については、キャッシュプロバイダーのドキュメントを参照してください。
条件付きキャッシュ

時々、メソッドは常にキャッシュするのに適していない場合があります(たとえば、指定された引数に依存する場合があります)。キャッシュアノテーションは、true または false のいずれかに評価される SpEL 式を取る condition パラメーターを介して、このようなユースケースをサポートします。true の場合、メソッドはキャッシュされます。そうでない場合は、メソッドがキャッシュされていないかのように動作します(つまり、キャッシュ内の値や使用されている引数に関係なく、メソッドは毎回呼び出されます)。例:次のメソッドは、引数 name の長さが 32 より短い場合にのみキャッシュされます。

@Cacheable(cacheNames="book", condition="#name.length() < 32") (1)
public Book findBook(String name)
1@Cacheable に条件を設定します。

condition パラメーターに加えて、unless パラメーターを使用して、キャッシュへの値の追加を拒否できます。condition とは異なり、unless 式はメソッドが呼び出された後に評価されます。前の例をさらに詳しく説明するために、次の例のように、ペーパーバックの本だけをキャッシュしたい場合があります。

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") (1)
public Book findBook(String name)
1unless 属性を使用してハードバックをブロックします。

キャッシュの抽象化は java.util.Optional をサポートし、コンテンツが存在する場合にのみコンテンツをキャッシュされた値として使用します。#result は常にビジネスエンティティを参照し、サポートされるラッパーを参照しないため、前の例を次のように書き換えることができます。

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)

result はまだ Optional ではなく Book を参照していることに注意してください。null である可能性があるため、安全なナビゲーション演算子を使用する必要があります。

利用可能なキャッシング SpEL 評価コンテキスト

各 SpEL 式は、専用の context に対して評価されます。フレームワークは、組み込みパラメーターに加えて、引数名などの専用のキャッシュ関連メタデータを提供します。次の表は、キーおよび条件付き計算に使用できるように、コンテキストで使用できるようになっているアイテムを示しています。

表 11: SpEL の利用可能なメタデータをキャッシュする
名前 ロケーション 説明 サンプル

methodName

ルートオブジェクト

呼び出されるメソッドの名前

#root.methodName

method

ルートオブジェクト

呼び出されるメソッド

#root.method.name

target

ルートオブジェクト

呼び出されるターゲットオブジェクト

#root.target

targetClass

ルートオブジェクト

呼び出されるターゲットのクラス

#root.targetClass

args

ルートオブジェクト

ターゲットの呼び出しに使用される引数(配列として)

#root.args[0]

caches

ルートオブジェクト

現在のメソッドが実行されるキャッシュのコレクション

#root.caches[0].name

引数名

評価コンテキスト

メソッド引数の名前。名前が使用できない場合(おそらくデバッグ情報がないため)、引数名は #a<#arg> でも使用できます。#arg は引数インデックス(0 から始まる)を表します。

#iban または #a0 (別名として #p0 または #p<#arg> 表記も使用できます)。

result

評価コンテキスト

メソッド呼び出しの結果(キャッシュされる値)。unless 式、cache put 式(key を計算するため)、または cache evict 式(beforeInvocation が false の場合)でのみ使用可能です。サポートされているラッパー(Optional など)の場合、#result はラッパーではなく実際のオブジェクトを参照します。

#result

8.2.2. @CachePut アノテーション

メソッドの実行を妨げずにキャッシュを更新する必要がある場合は、@CachePut アノテーションを使用できます。つまり、メソッドが常に呼び出され、その結果が(@CachePut オプションに従って)キャッシュに配置されます。@Cacheable と同じオプションをサポートし、メソッドフローの最適化ではなく、キャッシュの生成に使用する必要があります。次の例では、@CachePut アノテーションを使用しています。

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
同じメソッドで @CachePut アノテーションと @Cacheable アノテーションを使用することは、動作が異なるため、一般的にはお勧めしません。後者では、キャッシュを使用してメソッドの呼び出しがスキップされますが、前者では、キャッシュの更新を実行するために呼び出しが強制されます。これは予期しない動作を引き起こし、特定のコーナーケース(相互に除外する条件を持つアノテーションなど)を除いて、そのような宣言は回避する必要があります。これらの条件は除外を確認するために事前に検証されるため、そのような条件は結果オブジェクト(つまり、#result 変数)に依存すべきではないことにも注意してください。

8.2.3. @CacheEvict アノテーション

キャッシュの抽象化により、キャッシュストアの取り込みだけでなく、追い出しも可能になります。このプロセスは、古いデータや未使用のデータをキャッシュから削除できます。@Cacheable とは対照的に、@CacheEvict はキャッシュエビクションを実行するメソッド(つまり、キャッシュからデータを削除するトリガーとして機能するメソッド)を区別します。兄弟と同様に、@CacheEvict はアクションの影響を受ける 1 つ以上のキャッシュを指定する必要があり、カスタムキャッシュとキー解決または条件を指定でき、キャッシュ全体のエビクションが必要かどうかを示す追加パラメーター(allEntries)を備えています。エントリの追い出し(キーに基づく)ではなく、実行されます。次の例では、books キャッシュからすべてのエントリを削除します。

@CacheEvict(cacheNames="books", allEntries=true) (1)
public void loadBooks(InputStream batch)
1allEntries 属性を使用して、キャッシュからすべてのエントリを削除します。

このオプションは、キャッシュ領域全体をクリアする必要がある場合に便利です。前の例に示すように、各エントリを削除するのではなく(非効率的であるため時間がかかります)、すべてのエントリが 1 回の操作で削除されます。フレームワークは適用されないため、このシナリオで指定されたキーを無視します(1 つのエントリだけでなく、キャッシュ全体が削除されます)。

beforeInvocation 属性を使用して、メソッドが呼び出される前(デフォルト)または呼び出される前にエビクションを実行するかどうかを指定することもできます。前者は、残りのアノテーションと同じセマンティクスを提供します。メソッドが正常に完了すると、キャッシュに対するアクション(この場合はエビクション)が実行されます。メソッドが実行されない(キャッシュされる可能性がある)か、例外がスローされた場合、エビクションは発生しません。後者(beforeInvocation=true)では、メソッドが呼び出される前に常にエビクションが発生します。これは、エビクションをメソッドの結果に関連付ける必要がない場合に役立ちます。

void メソッドは @CacheEvict で使用できることに注意してください。メソッドはトリガーとして機能するため、戻り値は無視されます(キャッシュと相互作用しないため)。これは、キャッシュにデータを追加したり、キャッシュ内のデータを更新したりする @Cacheable の場合とは異なり、結果を必要とします。

8.2.4. @Caching アノテーション

場合によっては、同じタイプの複数のアノテーション(@CacheEvict または @CachePut など)を指定する必要があります。たとえば、条件やキー式が異なるキャッシュ間で異なるためです。@Caching では、複数のネストされた @Cacheable@CachePut および @CacheEvict アノテーションを同じメソッドで使用できます。次の例では、2 つの @CacheEvict アノテーションを使用しています。

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

8.2.5. @CacheConfig アノテーション

これまで、キャッシュ操作には多くのカスタマイズオプションが用意されており、操作ごとにこれらのオプションを設定できることがわかりました。ただし、カスタマイズオプションの一部は、クラスのすべての操作に適用する場合、構成が面倒な場合があります。たとえば、クラスのすべてのキャッシュ操作に使用するキャッシュの名前を指定することは、単一のクラスレベルの定義に置き換えることができます。これが @CacheConfig の出番です。次の例では、@CacheConfig を使用してキャッシュの名前を設定します。

@CacheConfig("books") (1)
public class BookRepositoryImpl implements BookRepository {

    @Cacheable
    public Book findBook(ISBN isbn) {...}
}
1@CacheConfig を使用してキャッシュの名前を設定します。

@CacheConfig は、キャッシュ名、カスタム KeyGenerator、カスタム CacheManager、およびカスタム CacheResolver の共有を可能にするクラスレベルのアノテーションです。このアノテーションをクラスに配置しても、キャッシュ操作は有効になりません。

操作レベルのカスタマイズは、常に @CacheConfig のカスタマイズセットをオーバーライドします。これにより、キャッシュ操作ごとに 3 つのレベルのカスタマイズが可能になります。

  • グローバルに構成され、CacheManagerKeyGenerator で使用可能。

  • クラスレベルで、@CacheConfig を使用します。

  • 操作レベル。

8.2.6. キャッシングアノテーションの有効化

キャッシュアノテーションを宣言してもアクションは自動的にトリガーされませんが、Spring の多くの機能と同様に、この機能は宣言的に有効にする必要があります(つまり、キャッシュが原因であると思われる場合は、削除して無効にすることができます)コード内のすべてのアノテーションではなく、1 行の構成行のみ)。

アノテーションのキャッシュを有効にするには、@EnableCaching を @Configuration クラスの 1 つに追加します。

@Configuration
@EnableCaching
public class AppConfig {
}

または、XML 構成の場合、cache:annotation-driven 要素を使用できます。

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

        <cache:annotation-driven/>
</beans>

cache:annotation-driven 要素と @EnableCaching アノテーションの両方を使用すると、AOP を介してキャッシュ動作をアプリケーションに追加する方法に影響を与えるさまざまなオプションを指定できます。構成は、@Transactional の構成と意図的に類似しています。

キャッシングアノテーションを処理するためのデフォルトのアドバイスモードは proxy です。これにより、プロキシのみを介した呼び出しのインターセプトが可能になります。同じクラス内のローカル呼び出しは、そのように傍受することはできません。インターセプトのより高度なモードについては、コンパイル時またはロード時のウィービングと組み合わせて aspectj モードに切り替えることを検討してください。
CachingConfigurer の実装に必要な高度なカスタマイズ(Java 構成を使用)の詳細については、javadoc を参照してください。
表 12: キャッシュアノテーション設定
XML 属性 アノテーション属性 デフォルト 説明

cache-manager

なし (CachingConfigurer(Javadoc) javadoc を参照してください)

cacheManager

使用するキャッシュマネージャーの名前。デフォルトの CacheResolver は、このキャッシュマネージャーでバックグラウンドで初期化されます(設定されていない場合は cacheManager)。キャッシュ解決をよりきめ細かく管理するには、'cache-resolver' 属性の設定を検討してください。

cache-resolver

なし (CachingConfigurer(Javadoc) javadoc を参照してください)

構成された cacheManager を使用する SimpleCacheResolver

バッキングキャッシュの解決に使用される CacheResolver の Bean 名。この属性は必須ではなく、'cache-manager' 属性の代替としてのみ指定する必要があります。

key-generator

なし (CachingConfigurer(Javadoc) javadoc を参照してください)

SimpleKeyGenerator

使用するカスタムキージェネレーターの名前。

error-handler

なし (CachingConfigurer(Javadoc) javadoc を参照してください)

SimpleCacheErrorHandler

使用するカスタムキャッシュエラーハンドラーの名前。デフォルトでは、キャッシュ関連の操作中にスローされた例外はすべてクライアントでスローされます。

mode

mode

proxy

デフォルトモード(proxy)は、Spring の AOP フレームワークを使用して、プロキシ化されるアノテーション付き Bean を処理します(前述のプロキシセマンティクスに従い、プロキシ経由で受信するメソッド呼び出しにのみ適用されます)。代わりに、代替モード(aspectj)は、影響を受けるクラスを Spring の AspectJ キャッシングアスペクトと織り、ターゲットクラスのバイトコードを変更して、あらゆる種類のメソッド呼び出しに適用します。AspectJ ウィービングでは、ロード時ウィービング(またはコンパイル時ウィービング)を有効にするだけでなく、クラスパスに spring-aspects.jar が必要です。(ロード時ウィービングを設定する方法の詳細については、Spring の構成を参照してください。)

proxy-target-class

proxyTargetClass

false

プロキシモードのみに適用されます。@Cacheable または @CacheEvict アノテーションが付けられたクラスに対して作成されるキャッシュプロキシのタイプを制御します。proxy-target-class 属性が true に設定されている場合、クラスベースのプロキシが作成されます。proxy-target-class が false の場合、または属性が省略された場合、標準の JDK インターフェースベースのプロキシが作成されます。(さまざまなプロキシタイプの詳細な調査については、プロキシメカニズムを参照してください。)

order

order

Ordered.LOWEST_PRECEDENCE

@Cacheable または @CacheEvict アノテーションが付けられた Bean に適用されるキャッシュアドバイスの順序を定義します。(AOP アドバイスの順序付けに関連する規則の詳細については、アドバイスのオーダーを参照してください)順序付けが指定されていない場合、AOP サブシステムがアドバイスの順序を決定します。

<cache:annotation-driven/> は、@Cacheable/@CachePut/@CacheEvict/@Caching が定義されているのと同じアプリケーションコンテキストの Bean でのみ @Cacheable/@CachePut/@CacheEvict/@Caching を検索します。これは、<cache:annotation-driven/> を DispatcherServlet の WebApplicationContext に入れると、サービスではなく、コントローラーでのみ Bean をチェックすることを意味します。詳細については、MVC セクションを参照してください。
メソッドの可視性とキャッシュアノテーション

プロキシを使用する場合、キャッシュのアノテーションは、パブリック可視性を持つメソッドにのみ適用する必要があります。これらのアノテーションで protected メソッド、プライベートなメソッド、またはパッケージ private メソッドにアノテーションを付けた場合、エラーは発生しませんが、アノテーション付きメソッドは構成されたキャッシュ設定を示しません。バイトコード自体を変更するため、非 public メソッドにアノテーションを付ける必要がある場合は、AspectJ(このセクションの残りを参照)の使用を検討してください。

Spring は、インターフェースにアノテーションを付けるのではなく、@Cache* アノテーションを使用して、具象クラス(および具象クラスのメソッド)のみにアノテーションを付けることをお勧めします。インターフェース(またはインターフェースメソッド)に @Cache* アノテーションを配置することは確かにできますが、これは、インターフェースベースのプロキシを使用する場合に期待どおりにのみ機能します。Java アノテーションがインターフェースから継承されないという事実は、クラスベースのプロキシ(proxy-target-class="true")またはウィービングベースのアスペクト(mode="aspectj")を使用する場合、プロキシ設定およびウィービングインフラストラクチャによってキャッシュ設定が認識されず、オブジェクトがキャッシングプロキシにラップされていません。
プロキシモード(デフォルト)では、プロキシを介して受信する外部メソッド呼び出しのみがインターセプトされます。これは、呼び出されたメソッドが @Cacheable でマークされている場合でも、自己呼び出し(実際には、ターゲットオブジェクトの別のメソッドを呼び出すターゲットオブジェクト内のメソッド)が実行時に実際のキャッシュにつながらないことを意味します。この場合、aspectj モードの使用を検討してください。また、期待される動作を提供するためにプロキシを完全に初期化する必要があるため、初期化コード(つまり @PostConstruct)でこの機能に依存しないでください。

8.2.7. カスタムアノテーションの使用

カスタムアノテーションと AspectJ

この機能はプロキシベースのアプローチでのみ機能しますが、AspectJ を使用することで少し手間をかけて有効にできます。

spring-aspects モジュールは、標準のアノテーションのみのアスペクトを定義します。独自のアノテーションを定義した場合は、それらの側面も定義する必要があります。例については、AnnotationCacheAspect を確認してください。

キャッシュの抽象化により、独自のアノテーションを使用して、どのメソッドがキャッシュの挿入または削除をトリガーするかを特定できます。これは、キーまたは条件が指定されている場合、または外部インポート(org.springframework)がコードベースで許可されていない場合に特に便利なキャッシュアノテーション宣言を複製する必要がないため、テンプレートメカニズムとして非常に便利です。他のステレオタイプアノテーションと同様に、@Cacheable@CachePut@CacheEvict および @CacheConfig をメタアノテーション(つまり、他のアノテーションにアノテーションを付けることができるアノテーション)として使用できます。次の例では、一般的な @Cacheable 宣言を独自のカスタムアノテーションに置き換えます。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames="books", key="#isbn")
public @interface SlowService {
}

前の例では、独自の SlowService アノテーションを定義しています。これには @Cacheable のアノテーションが付けられています。これで、次のコードを置き換えることができます。

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

次の例は、前述のコードを置き換えることができるカスタムアノテーションを示しています。

@SlowService
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@SlowService は Spring アノテーションではありませんが、コンテナーは実行時に自動的に宣言を取得し、その意味を理解します。前述のように、アノテーション駆動型の動作を有効にする必要があることに注意してください。

8.3. JCache(JSR-107)アノテーション

バージョン 4.1 以降、Spring のキャッシング抽象化は、JCache 標準アノテーションである @CacheResult@CachePut@CacheRemove、および @CacheRemoveAll、および @CacheDefaults@CacheKey、および @CacheValue コンパニオンを完全にサポートしています。キャッシュストアを JSR-107 に移行しなくても、これらのアノテーションを使用できます。内部実装は Spring のキャッシュ抽象化を使用し、仕様に準拠したデフォルトの CacheResolver および KeyGenerator 実装を提供します。つまり、Spring のキャッシュ抽象化を既に使用している場合、キャッシュストレージ(または構成)を変更することなく、これらの標準アノテーションに切り替えることができます。

8.3.1. 機能の概要

Spring のキャッシュアノテーションに精通している人のために、次の表は Spring アノテーションと JSR-107 のアノテーションの主な違いを説明しています。

表 13: Spring と JSR-107 キャッシングアノテーション
SpringJSR-107 リマーク

@Cacheable

@CacheResult

かなり似ています。@CacheResult は特定の例外をキャッシュし、キャッシュの内容に関係なくメソッドの実行を強制できます。

@CachePut

@CachePut

Spring はメソッド呼び出しの結果でキャッシュを更新しますが、JCache は @CacheValue アノテーションが付けられた引数としてそれを渡す必要があります。この違いにより、JCache では実際のメソッド呼び出しの前後にキャッシュを更新できます。

@CacheEvict

@CacheRemove

かなり似ています。@CacheRemove は、メソッド呼び出しの結果が例外となった場合の条件付きエビクションをサポートします。

@CacheEvict(allEntries=true)

@CacheRemoveAll

@CacheRemove を参照してください。

@CacheConfig

@CacheDefaults

同様の方法で、同じ概念を構成できます。

JCache の概念は javax.cache.annotation.CacheResolver です。これは、JCache が単一のキャッシュのみをサポートすることを除いて、Spring の CacheResolver インターフェースと同一です。デフォルトでは、単純な実装は、アノテーションで宣言された名前に基づいて使用するキャッシュを取得します。アノテーションにキャッシュ名が指定されていない場合、デフォルトが自動的に生成されることに注意してください。詳細については、@CacheResult#cacheName() の javadoc を参照してください。

CacheResolver インスタンスは CacheResolverFactory によって取得されます。次の例に示すように、キャッシュ操作ごとにファクトリをカスタマイズできます。

@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class) (1)
public Book findBook(ISBN isbn)
1 この操作のためにファクトリをカスタマイズします。
すべての参照クラスについて、Spring は指定されたタイプの Bean を見つけようとします。複数の一致が存在する場合、新しいインスタンスが作成され、依存関係注入などの通常の Bean ライフサイクルコールバックを使用できます。

キーは、Spring の KeyGenerator と同じ目的を果たす javax.cache.annotation.CacheKeyGenerator によって生成されます。デフォルトでは、少なくとも 1 つのパラメーターに @CacheKey アノテーションが付けられていない限り、すべてのメソッド引数が考慮されます。これは、Spring のカスタムキー生成宣言に似ています。たとえば、以下は同一の操作です。1 つは Spring の抽象化を使用し、もう 1 つは JCache を使用します。

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@CacheResult(cacheName="books")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed)

CacheResolverFactory を指定する方法と同様に、操作で CacheKeyResolver を指定することもできます。

JCache は、アノテーション付きメソッドによってスローされた例外を管理できます。これにより、キャッシュの更新を防ぐことができますが、メソッドを再度呼び出す代わりに、失敗のインジケータとして例外をキャッシュすることもできます。ISBN の構造が無効な場合、InvalidIsbnNotFoundException がスローされると仮定します。これは永続的な障害です(このようなパラメーターで本を取得することはできません)。次の例では、例外をキャッシュして、同じ無効な ISBN でさらに呼び出した場合、メソッドを再度呼び出すのではなく、キャッシュされた例外を直接スローします。

@CacheResult(cacheName="books", exceptionCacheName="failures"
            cachedExceptions = InvalidIsbnNotFoundException.class)
public Book findBook(ISBN isbn)

8.3.2. JSR-107 サポートの有効化

Spring の宣言的アノテーションサポートと一緒に JSR-107 サポートを有効にするために、特別なことを何もする必要はありません。JSR-107 API と spring-context-support モジュールの両方がクラスパスに存在する場合、@EnableCaching と cache:annotation-driven エレメントの両方が自動的に JCache サポートを有効にします。

ユースケースに応じて、選択は基本的にあなた次第です。一部で JSR-107 API を使用し、他で Spring 独自のアノテーションを使用することで、サービスを組み合わせて使用することもできます。ただし、これらのサービスが同じキャッシュに影響を与える場合は、一貫性のある同一のキー生成実装を使用する必要があります。

8.4. 宣言的な XML ベースのキャッシュ

アノテーションがオプションではない場合(おそらくソースへのアクセス権がないか、外部コードがないため)、宣言キャッシュに XML を使用できます。キャッシングのメソッドにアノテーションを付ける代わりに、ターゲットメソッドとキャッシングディレクティブを外部で指定できます(宣言的なトランザクション管理アドバイスと同様)。前のセクションの例は、次の例に変換できます。

<!-- the service we want to make cacheable -->
<bean id="bookService" class="x.y.service.DefaultBookService"/>

<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
    <cache:caching cache="books">
        <cache:cacheable method="findBook" key="#isbn"/>
        <cache:cache-evict method="loadBooks" all-entries="true"/>
    </cache:caching>
</cache:advice>

<!-- apply the cacheable behavior to all BookService interfaces -->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
</aop:config>

<!-- cache manager definition omitted -->

上記の構成では、bookService はキャッシュ可能になっています。適用するキャッシングセマンティクスは cache:advice 定義にカプセル化されます。これにより、findBooks メソッドがデータをキャッシュに入れるために使用され、loadBooks メソッドがデータを排除するために使用されます。両方の定義は、books キャッシュに対して機能します。

aop:config 定義は、AspectJ ポイントカット式を使用して、プログラム内の適切なポイントにキャッシュアドバイスを適用します(詳細については、Spring によるアスペクト指向プログラミングを参照してください)。前の例では、BookService のすべてのメソッドが考慮され、それらにキャッシュアドバイスが適用されます。

宣言型 XML キャッシングはすべてのアノテーションベースのモデルをサポートしているため、この 2 つの間を移動するのは非常に簡単です。さらに、両方を同じアプリケーション内で使用できます。XML ベースのアプローチは、ターゲットコードには影響しません。ただし、本質的に冗長です。キャッシングの対象となるオーバーロードされたメソッドを持つクラスを処理する場合、method 引数は適切な判別子ではないため、適切なメソッドの識別には余分な労力がかかります。これらの場合、AspectJ ポイントカットを使用してターゲットメソッドを選択し、適切なキャッシュ機能を適用できます。ただし、XML を使用すると、パッケージまたはグループまたはインターフェース全体のキャッシング(AspectJ ポイントカットによる)を適用し、テンプレートのような定義を作成することが容易になります(前の例で cache:definitionscache を介してターゲットキャッシュを定義したように)属性)。

8.5. キャッシュストレージの構成

キャッシュ抽象化は、いくつかのストレージ統合オプションを提供します。使用するには、適切な CacheManager (Cache インスタンスを制御および管理し、これらをストレージに取得するために使用できるエンティティ)を宣言する必要があります。

8.5.1. JDK ConcurrentMap ベースのキャッシュ

JDK ベースの Cache 実装は、org.springframework.cache.concurrent パッケージにあります。ConcurrentHashMap をバッキング Cache ストアとして使用できます。次の例は、2 つのキャッシュを構成する方法を示しています。

<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
        </set>
    </property>
</bean>

上記のスニペットは、SimpleCacheManager を使用して、default および books という名前の 2 つのネストされた ConcurrentMapCache インスタンスの CacheManager を作成します。名前は各キャッシュに直接設定されることに注意してください。

キャッシュはアプリケーションによって作成されるため、そのライフサイクルにバインドされているため、基本的なユースケース、テスト、または単純なアプリケーションに適しています。キャッシュは適切に拡張され、非常に高速ですが、管理、永続化機能、またはエビクション契約は提供されません。

8.5.2. ehcache ベースのキャッシュ

Ehcache 3.x は JSR-107 に完全に準拠しており、専用のサポートは必要ありません。

Ehcache 2.x 実装は org.springframework.cache.ehcache パッケージに含まれています。繰り返しますが、それを使用するには、適切な CacheManager を宣言する必要があります。次の例は、その方法を示しています。

<bean id="cacheManager"
        class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>

<!-- EhCache library setup -->
<bean id="ehcache"
        class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>

このセットアップでは、Spring IoC 内の ehcache ライブラリを(ehcache Bean を介して)ブートストラップし、専用の CacheManager 実装に接続します。Ehcache 固有の設定全体が ehcache.xml から読み取られることに注意してください。

8.5.3. Caffeine キャッシュ

Caffeine は Guava のキャッシュを Java 8 で書き換えたもため、その実装は org.springframework.cache.caffeine パッケージにあり、Caffeine のいくつかの機能へのアクセスを提供します。

次の例では、オンデマンドでキャッシュを作成する CacheManager を構成します。

<bean id="cacheManager"
        class="org.springframework.cache.caffeine.CaffeineCacheManager"/>

明示的に使用するキャッシュを提供することもできます。その場合、マネージャーのみが利用可能になります。次の例は、その方法を示しています。

<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
    <property name="caches">
        <set>
            <value>default</value>
            <value>books</value>
        </set>
    </property>
</bean>

Caffeine CacheManager は、カスタム Caffeine および CacheLoader もサポートしています。それらの詳細については、Caffeine のドキュメント (GitHub) を参照してください。

8.5.4. GemFire ベースのキャッシュ

GemFire は、メモリ指向、ディスクバックアップ、弾性スケーラブル、継続的に利用可能、アクティブ(組み込みのパターンベースのサブスクリプション通知を使用)、グローバルに複製されたデータベースであり、完全な機能を備えたエッジキャッシングを提供します。GemFire を CacheManager (およびそれ以上)として使用する方法の詳細については、Spring Data GemFire リファレンスドキュメント (英語) を参照してください。

8.5.5. JSR-107 キャッシュ

Spring のキャッシュ抽象化では、JSR-107 準拠のキャッシュも使用できます。JCache 実装は org.springframework.cache.jcache パッケージにあります。

繰り返しますが、これを使用するには、適切な CacheManager を宣言する必要があります。次の例は、その方法を示しています。

<bean id="cacheManager"
        class="org.springframework.cache.jcache.JCacheCacheManager"
        p:cache-manager-ref="jCacheManager"/>

<!-- JSR-107 cache manager setup  -->
<bean id="jCacheManager" .../>

8.5.6. バッキングストアなしでキャッシュを処理する

場合によっては、環境を切り替えたりテストを行ったりするときに、実際のバッキングキャッシュを構成せずにキャッシュ宣言を行うことがあります。これは無効な構成であるため、キャッシュインフラストラクチャが適切なストアを見つけることができないため、実行時に例外がスローされます。このような状況では、キャッシュ宣言を削除するのは(退屈な作業になる可能性があります)、キャッシュを実行しない単純なダミーキャッシュを接続できます。つまり、キャッシュされたメソッドが毎回呼び出されるようになります。次の例は、その方法を示しています。

<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
    <property name="cacheManagers">
        <list>
            <ref bean="jdkCache"/>
            <ref bean="gemfireCache"/>
        </list>
    </property>
    <property name="fallbackToNoOpCache" value="true"/>
</bean>

前述のチェーン複数の CacheManager インスタンスの CompositeCacheManager は、fallbackToNoOpCache フラグを介して、構成されたキャッシュマネージャーで処理されないすべての定義に対して何もしないキャッシュを追加します。つまり、jdkCache または gemfireCache のいずれにも見つからないすべてのキャッシュ定義(例の前半で構成)は、情報を格納しない no-op キャッシュによって処理され、ターゲットメソッドが毎回呼び出されます。

8.6. 異なるバックエンドキャッシュのプラグイン

明らかに、バッキングストアとして使用できるキャッシング製品がたくさんあります。プラグインするには、CacheManager および Cache 実装を提供する必要があります。残念ながら、代わりに使用できる標準はありません。実際には、クラスは、ehcache クラスが行うように、ストレージ API の上にキャッシング抽象化フレームワークをマッピングする単純なアダプター (英語) になる傾向があるため、これは難しいように聞こえるかもしれません。ほとんどの CacheManager クラスは、org.springframework.cache.support パッケージのクラスを使用できます(AbstractCacheManager は、ボイラープレートコードを処理し、実際のマッピングのみを完了させます)。やがて、Spring との統合を提供するライブラリがこの小さな構成ギャップを埋めることができることを願っています。

8.7. TTL/TTI / エビクションポリシー / XXX 機能を設定するにはどうすればよいですか?

キャッシュプロバイダーから直接。キャッシュの抽象化は抽象化であり、キャッシュの実装ではありません。使用するソリューションは、他のソリューションではサポートされないさまざまなデータポリシーやさまざまなトポロジをサポートする場合があります(たとえば、JDK ConcurrentHashMap- バッキングサポートがないため、キャッシュの抽象化でそれを使用できません)。このような機能は、バッキングキャッシュ(構成時)またはネイティブ API を介して直接制御する必要があります。

9. 付録

9.1. XML スキーマ

付録のこのパートには、統合テクノロジーに関連する XML スキーマがリストされています。

9.1.1. jee スキーマ

jee エレメントは、JNDI オブジェクトの検索や EJB 参照の定義など、Java EE(Java Enterprise エディション)構成に関連する課題を処理します。

jee スキーマの要素を使用するには、Spring XML 構成ファイルの先頭に次のプリアンブルが必要です。次のスニペットのテキストは正しいスキーマを参照しているため、jee 名前空間の要素を使用できます。

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

    <!-- bean definitions here -->

</beans>
<jee:jndi-lookup/> (シンプル)

次の例は、JNDI を使用して、jee スキーマなしでデータソースをルックアップする方法を示しています。

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="jdbc/MyDataSource"/>
</bean>
<bean id="userDao" class="com.foo.JdbcUserDao">
    <!-- Spring will do the cast automatically (as usual) -->
    <property name="dataSource" ref="dataSource"/>
</bean>

次の例は、JNDI を使用して jee スキーマでデータソースをルックアップする方法を示しています。

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDataSource"/>

<bean id="userDao" class="com.foo.JdbcUserDao">
    <!-- Spring will do the cast automatically (as usual) -->
    <property name="dataSource" ref="dataSource"/>
</bean>
<jee:jndi-lookup/> (単一の JNDI 環境設定)

次の例は、JNDI を使用して jee なしで環境変数を検索する方法を示しています。

<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="jdbc/MyDataSource"/>
    <property name="jndiEnvironment">
        <props>
            <prop key="ping">pong</prop>
        </props>
    </property>
</bean>

次の例は、JNDI を使用して jee で環境変数を検索する方法を示しています。

<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
    <jee:environment>ping=pong</jee:environment>
</jee:jndi-lookup>
<jee:jndi-lookup/> (複数の JNDI 環境設定を使用)

次の例は、JNDI を使用して jee なしで複数の環境変数を検索する方法を示しています。

<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="jdbc/MyDataSource"/>
    <property name="jndiEnvironment">
        <props>
            <prop key="sing">song</prop>
            <prop key="ping">pong</prop>
        </props>
    </property>
</bean>

次の例は、JNDI を使用して jee で複数の環境変数を検索する方法を示しています。

<jee:jndi-lookup id="simple" jndi-name="jdbc/MyDataSource">
    <!-- newline-separated, key-value pairs for the environment (standard Properties format) -->
    <jee:environment>
        sing=song
        ping=pong
    </jee:environment>
</jee:jndi-lookup>
<jee:jndi-lookup/> (複合)

次の例は、JNDI を使用して、jee を使用せずにデータソースとさまざまなプロパティを検索する方法を示しています。

<bean id="simple" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="jdbc/MyDataSource"/>
    <property name="cache" value="true"/>
    <property name="resourceRef" value="true"/>
    <property name="lookupOnStartup" value="false"/>
    <property name="expectedType" value="com.myapp.DefaultThing"/>
    <property name="proxyInterface" value="com.myapp.Thing"/>
</bean>

次の例は、JNDI を使用して、jee でデータソースとさまざまなプロパティを検索する方法を示しています。

<jee:jndi-lookup id="simple"
        jndi-name="jdbc/MyDataSource"
        cache="true"
        resource-ref="true"
        lookup-on-startup="false"
        expected-type="com.myapp.DefaultThing"
        proxy-interface="com.myapp.Thing"/>
<jee:local-slsb/> (シンプル)

<jee:local-slsb/> 要素は、ローカル EJB ステートレスセッション Bean への参照を構成します。

次の例は、jee なしでローカル EJB ステートレスセッション Bean への参照を構成する方法を示しています。

<bean id="simple"
        class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="ejb/RentalServiceBean"/>
    <property name="businessInterface" value="com.foo.service.RentalService"/>
</bean>

次の例は、ローカル EJB ステートレスセッション Bean への参照を jee で構成する方法を示しています。

<jee:local-slsb id="simpleSlsb" jndi-name="ejb/RentalServiceBean"
        business-interface="com.foo.service.RentalService"/>
<jee:local-slsb/> (複合)

<jee:local-slsb/> 要素は、ローカル EJB ステートレスセッション Bean への参照を構成します。

次の例は、ローカル EJB ステートレスセッション Bean への参照と jee なしの多数のプロパティを構成する方法を示しています。

<bean id="complexLocalEjb"
        class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="ejb/RentalServiceBean"/>
    <property name="businessInterface" value="com.example.service.RentalService"/>
    <property name="cacheHome" value="true"/>
    <property name="lookupHomeOnStartup" value="true"/>
    <property name="resourceRef" value="true"/>
</bean>

次の例は、ローカル EJB ステートレスセッション Bean への参照と jee を使用したいくつかのプロパティの設定方法を示しています。

<jee:local-slsb id="complexLocalEjb"
        jndi-name="ejb/RentalServiceBean"
        business-interface="com.foo.service.RentalService"
        cache-home="true"
        lookup-home-on-startup="true"
        resource-ref="true">
<jee:remote-slsb/>

<jee:remote-slsb/> 要素は、remote EJB ステートレスセッション Bean への参照を構成します。

次の例は、jee なしでリモート EJB ステートレスセッション Bean への参照を構成する方法を示しています。

<bean id="complexRemoteEjb"
        class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
    <property name="jndiName" value="ejb/MyRemoteBean"/>
    <property name="businessInterface" value="com.foo.service.RentalService"/>
    <property name="cacheHome" value="true"/>
    <property name="lookupHomeOnStartup" value="true"/>
    <property name="resourceRef" value="true"/>
    <property name="homeInterface" value="com.foo.service.RentalService"/>
    <property name="refreshHomeOnConnectFailure" value="true"/>
</bean>

次の例は、jee を使用してリモート EJB ステートレスセッション Bean への参照を構成する方法を示しています。

<jee:remote-slsb id="complexRemoteEjb"
        jndi-name="ejb/MyRemoteBean"
        business-interface="com.foo.service.RentalService"
        cache-home="true"
        lookup-home-on-startup="true"
        resource-ref="true"
        home-interface="com.foo.service.RentalService"
        refresh-home-on-connect-failure="true">

9.1.2. jms スキーマ

jms 要素は、Spring のメッセージリスナコンテナーなどの JMS 関連の Bean の構成を処理します。これらの要素は、JMS 名前空間のサポートというタイトルの JMS の章のセクションで詳しく説明されています。このサポートと jms 要素自体の詳細については、その章を参照してください。

完全を期すために、jms スキーマの要素を使用するには、Spring XML 構成ファイルの先頭に次のプリアンブルが必要です。次のスニペットのテキストは正しいスキーマを参照しているため、jms 名前空間の要素を使用できます。

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

    <!-- bean definitions here -->

</beans>

9.1.3. <context:mbean-export/> を使用する

この要素については、アノテーションベースの MBean エクスポートの構成で詳しく説明しています。

9.1.4. cache スキーマ

cache 要素を使用して、Spring の @CacheEvict@CachePut および @Caching アノテーションのサポートを有効にできます。また、宣言型の XML ベースのキャッシュもサポートしています。詳細については、キャッシングアノテーションの有効化および宣言的な XML ベースのキャッシュを参照してください。

cache スキーマの要素を使用するには、Spring XML 構成ファイルの先頭に次のプリアンブルが必要です。次のスニペットのテキストは正しいスキーマを参照しているため、cache 名前空間の要素を使用できます。

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

    <!-- bean definitions here -->

</beans>