リファレンスドキュメントのこの部分では、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の HessianProxyFactoryBeanHessianServiceExporterを使用することにより、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>

In the latter case, you should define a corresponding servlet for this exporter in web.xml , with the same end result: The exporter gets mapped to the request path at /remoting/AccountService . Note that the servlet name needs to match the bean name of the target exporter. The following example shows how to do so:

<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ファクトリまたはアプリケーションコンテキストを作成し、次の例に示すように、SimpleObjectAccountService を使用してアカウントを管理する次の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/(英語) を参照してください。

Be aware of vulnerabilities due to unsafe Java deserialization: Manipulated input streams can lead to unwanted code execution on the server during the deserialization step. As a consequence, do not expose HTTP invoker endpoints to untrusted clients. Rather, expose them only between your own services. In general, we strongly recommend using any other message format (such as JSON) instead.

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サービスへのアクセス

In addition to stock support for JAX-WS in Spring Core, the Spring portfolio also features Spring Web Services(英語) , which is a solution for contract-first, document-driven web services — highly recommended for building modern, future-proof web services.

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

Spring provides a convenient base class for JAX-WS servlet endpoint implementations: SpringBeanAutowiringSupport。To expose our AccountService , we extend Spring’s SpringBeanAutowiringSupport class and implement our business logic here, usually delegating the call to the business layer. We use Spring’s @Autowired annotation to express such dependencies on Spring-managed beans. The following example shows our class that extends 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サービスプロキシ、つまり LocalJaxWsServiceFactoryBeanJaxWsPortProxyFactoryBeanを作成する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>
1 serviceInterface は、クライアントが使用するビジネスインターフェースです。

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 などのアノテーションを付ける必要があるため、わずかに単純化されています。これは、JAX-WSエンドポイントアーティファクトとしてプレーンJavaインターフェースと実装クラスを(簡単に)使用できないことを意味します。最初にそれに応じてアノテーションを付ける必要があります。これらの要件の詳細については、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リモーティング(英語) セクションを参照してください。

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

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

ターゲットによって実装されるすべてのインターフェースを備えたプロキシを提供することは、通常、ローカルのケースでは問題になりません。ただし、リモートサービスをエクスポートする場合は、リモートの使用を目的とした特定の操作を使用して、特定のサービスインターフェースを公開する必要があります。内部コールバックインターフェースに加えて、ターゲットは複数のビジネスインターフェースを実装する場合があり、そのうちの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の時点で、ノンブロッキングのリアクティブ WebClient は、RestTemplate の最新の代替手段となり、同期シナリオと非同期シナリオ、およびストリーミングシナリオの両方を効率的にサポートします。 RestTemplate は将来のバージョンで非推奨となり、今後主要な新機能は追加されません。

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 HttpURLConnectionPATCHをサポートしていませんが、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());

Each ClientHttpRequestFactory exposes configuration options specific to the underlying HTTP client library — for example, for credentials, connection pooling, and other details.

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"

RestTemplateuriTemplateHandler プロパティを使用して、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/plainContent-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-streamContent-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 実装。 DOMSource, SAXSourceおよび 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 の場合は、ファイル拡張子に基づいて決定されます。必要に応じて、MediaTypeHttpEntity ラッパーを明示的に提供できます。

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

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

MultiValueMap に少なくとも1つの非String 値が含まれている場合、Content-TypeFormHttpMessageConverterによって multipart/form-data に設定されます。 MultiValueMapString 値がある場合、Content-Typeapplication/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スタイル)。

メモ: For EJB 3 Session Beans, you can effectively use a JndiObjectFactoryBean / <jee:jndi-lookup> as well, since fully usable component references are exposed for plain JNDI lookups there. Defining explicit <jee:local-slsb> or <jee:remote-slsb> lookups provides consistent and more explicit EJB access configuration.

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セッションを提供し、ProducerCallbackSessionMessageProducer のペアを公開します。

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値の一貫した管理を実現するには、ブールプロパティ isExplicitQosEnabledtrueに設定して、JmsTemplate が独自のQOS値を使用できるように明確に有効にする必要があります。

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

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

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

3.1.2. 接続

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

EJB内でJMSを使用する場合、ベンダーは、宣言的なトランザクション管理に参加し、接続とセッションのプーリングを実行できるように、JMSインターフェースの実装を提供します。この実装を使用するには、通常、Java EEコンテナーでは、JMS接続ファクトリをEJBまたはサーブレットデプロイ記述子内の 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を使用する

CachingConnectionFactorySingleConnectionFactory の機能を継承し、Session, MessageProducerおよび 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仕様の警告を無視し、メソッド TopicSession createTopic(String topicName) または QueueSession createQueue(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トランザクションでラップします( DefaultMessageListenerContainerJtaTransactionManagerで構成することにより)(データベース操作などを含む)。
デフォルトの 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 クラスなど、プロバイダー固有のプーリングアダプターの使用を検討してください。

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

JMS APIを使用して Connectionから Session を作成する場合、管理されたトランザクション環境と管理されていないトランザクション環境でコードを再利用すると混乱する可能性があります。これは、JMS APIには Sessionを作成するファクトリメソッドが1つしかなく、トランザクションモードと確認応答モードの値が必要です。管理された環境では、これらの値の設定は環境のトランザクションインフラストラクチャの責任であるため、これらの値はベンダーのJMS接続へのラッパーによって無視されます。管理されていない環境で JmsTemplate を使用する場合、プロパティ sessionTransacted および sessionAcknowledgeModeを使用してこれらの値を指定できます。 JmsTemplatePlatformTransactionManager を使用する場合、テンプレートには常にトランザクション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)は、StringTextMessage, byte[]BytesMesssage、および java.util.MapMapMessageの間の変換をサポートしています。コンバーターを使用することにより、ユーザーとアプリケーションコードは、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 ペアを公開します。 JmsTemplateexecute() メソッドは、これらのコールバックメソッドを実行します。

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>

messageListenerTextMessage以外のタイプの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...
}

DefaultResponsiveTextMessageDelegateMessageListenerAdapterと組み合わせて使用する場合、'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>

The specified WorkManager can also point to an environment-specific thread pool — typically through a SimpleTaskWorkManager instance’s asyncTaskExecutor property. Consider defining a shared thread pool for all your ResourceAdapter instances if you happen to use multiple adapters.

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

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

Spring also provides a generic JCA message endpoint manager that is not tied to JMS: org.springframework.jca.endpoint.GenericMessageEndpointManager。This component allows for using any message listener type (such as a CCI MessageListener ) and any provider-specific ActivationSpec object. See your JCA provider’s documentation to find out about the actual capabilities of your connector, and see the GenericMessageEndpointManager (Javadoc) javadoc for the Spring-specific configuration details.

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.Destination myDestinationで利用可能な場合は常に、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スキーマの参照。

The namespace consists of three top-level elements: <annotation-driven/>, <listener-container/> and <jca-listener-container/> . <annotation-driven/> enables the use of annotation-driven listener endpoints. <listener-container/> and <jca-listener-container/> define shared listener container configuration and can contain <listener/> child elements. The following example shows a basic configuration for two listeners:

<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/> 要素は、いくつかのオプション属性も受け入れます。これにより、さまざまな戦略( taskExecutordestinationResolverなど)のカスタマイズ、および基本的な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

このリスナーコンテナーのタイプ。使用可能なオプションは default, simple, default102または 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

The JMS destination type for this listener: queue, topic, durableTopic, sharedTopic , or sharedDurableTopic . This potentially enables the pubSubDomain, subscriptionDurable and subscriptionShared properties of the container. The default is queue (which disables those three properties).

response-destination-type

The JMS destination type for responses: queue or topic . The default is the value of the destination-type attribute.

client-id

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

cache

The cache level for JMS resources: none, connection, session, consumer , or auto . By default ( auto ), the cache level is effectively consumer , unless an external transaction manager has been specified — in which case, the effective default will be none (assuming Java EE-style transaction management, where the given ConnectionFactory is an XA-aware pool).

acknowledge

The native JMS acknowledge mode: auto, client, dups-ok , or transacted . A value of transacted activates a locally transacted Session . As an alternative, you can specify the transaction-manager attribute, described later in table. The default is 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

The JMS destination type for this listener: queue, topic, durableTopic, sharedTopic . or sharedDurableTopic . This potentially enables the pubSubDomain, subscriptionDurable , and subscriptionShared properties of the container. The default is queue (which disables those three properties).

response-destination-type

The JMS destination type for responses: queue or topic . The default is the value of the destination-type attribute.

client-id

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

acknowledge

The native JMS acknowledge mode: auto, client, dups-ok , or transacted . A value of transacted activates a locally transacted Session . As an alternative, you can specify the transaction-manager attribute described later. The default is 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 に伝えます。デフォルト構成では、beans Map の各エントリーのキーは、対応するエントリー値によって参照されるBeanの ObjectName として使用されます。Beanの ObjectName インスタンスの制御に従って、この動作を変更できます。

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

MBeanExporterLifecycle 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つだけ使用されるほとんどの環境で機能します。ただし、複数のインスタンスが存在する場合、エクスポーターは間違ったサーバーを選択する可能性があります。このような場合、次の例に示すように、MBeanServer agentId を使用して、使用するインスタンスを示す必要があります。

<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 MBeanExporterObjectName bean:name=testBean1を使用して MBeanMBeanServer に登録しようとするシナリオを考えます。 MBean インスタンスが同じ ObjectNameにすでに登録されている場合、デフォルトの動作は失敗します(そして InstanceAlreadyExistsExceptionをスローします)。

MBeanMBeanServerに登録されたときに何が起こるかを正確に制御できます。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 インターフェースの使用

背後で、MBeanExporterorg.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

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

ManagedResource, ManagedAttribute, ManagedOperationまたは 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が渡されないことに注意してください。ただし、JmxTestBeanManagedResource 属性でマークされており、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ごとに異なるインターフェースを使用することをお勧めします。この場合、InterfaceBasedMBeanInfoAssemblerinterfaceMappings プロパティを介して 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ごとにメソッドの公開を制御するには、MethodNameMBeanInfoAssemblermethodMappings プロパティを使用して、Bean名をメソッド名のリストにマッピングします。

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

バックグラウンドでは、MBeanExporterObjectNamingStrategy の実装に委譲して、登録する各Beanの ObjectName インスタンスを取得します。デフォルトでは、デフォルトの実装である KeyNamingStrategybeans Map のキーを ObjectNameとして使用します。さらに、KeyNamingStrategybeans Map のキーを Properties ファイルのエントリにマッピングして、ObjectNameを解決できます。 KeyNamingStrategyに加えて、Springは、IdentityNamingStrategy (BeanのJVM IDに基づいて ObjectName を構築する)と MetadataNamingStrategyObjectNameを取得するためにソースレベルのメタデータを使用する)の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=testBean1ObjectName が与えられます。

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>

If no objectName has been provided for the ManagedResource attribute, an ObjectName is created with the following format: [fully-qualified-package-name]:type=[short-classname],name=[bean-name]. For example, the generated ObjectName for the following bean would be com.example:type=MyClass,name=myBean :

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

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

If you prefer to use the annotation-based approach to define your management interfaces, a convenience subclass of MBeanExporter is available: AnnotationMBeanExporter。When defining an instance of this subclass, you no longer need the namingStrategy, assembler , and attributeSource configuration, since it always uses standard Java annotation-based metadata (autodetection is always enabled as well). In fact, rather than defining an MBeanExporter bean, an even simpler syntax is supported by the @EnableMBeanExport @Configuration annotation, as the following example shows:

@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"/>

デフォルトでは、ConnectorServerFactoryBeanservice: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はその ObjectNameMBeanServer にコネクターを自動的に登録します。次の例は、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対応 MBeanServerMBeanServerConnection を作成するには、次の例に示すように、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=testBeanObjectName に登録された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 は、NotificationListenerObjectName (または 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 provides a specification to standardize access to enterprise information systems (EIS): the JCA (Java EE Connector Architecture). This specification is divided into two different parts:

  • コネクタプロバイダーが実装する必要がある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でラップする必要があります。専用の ConnectionSpecconnectionSpec プロパティで構成できます(内部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(..) メソッドは、使用する ConnectionFactoryRecordFactory に対応する 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 {
        // ...
    }

}

Unless the outputRecordCreator property is set on the template (see the following section), every method calls the corresponding execute method of the CCI Interaction with two parameters: InteractionSpec and an input Record . It receives an output Record as its return value.

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 メソッドを提供します。内部的に、このクラスは、渡された ConnectionFactoryCciTemplate インスタンスを作成し、サブクラスの具体的なデータアクセス実装に公開します。次の例は、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(..) メソッドのみをサポートする場合(つまり、適切な出力レコードを返す代わりに、目的の出力レコードを渡す必要がある場合)、CciTemplateoutputRecordCreator プロパティを次のように設定できます。応答が受信されたときにJCAコネクタによって入力される出力レコードを自動的に生成します。このレコードは、テンプレートの呼び出し元に返されます。

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

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. CciTemplate Interaction の概要

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

表9: インタラクション実行メソッドの使用
CciTemplate メソッドのシグネチャー CciTemplate outputRecordCreator プロパティ 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を使用すると、使用される ConnectionCciTemplateによって管理および終了されますが、コールバックの実装は、接続で作成された対話を管理する必要があります。

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

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アクセスのモデリング

The org.springframework.jca.cci.object package contains support classes that let you access the EIS in a different style: through reusable operation objects, analogous to Spring’s JDBC operation objects (see the データアクセスの章のJDBCセクション ). This usually encapsulates the CCI API. An application-level input object is passed to the operation object, so it can construct the input record and then convert the received record data to an application-level output object and return it.

このアプローチは、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 メソッドのシグネチャー MappingRecordOperation outputRecordCreator プロパティ 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 抽象化の結果であり、実際の実行戦略からトランザクションの境界を切り離します。必要に応じて JtaTransactionManagerCciLocalTransactionManager を切り替えて、トランザクションの境界をそのまま維持できます。

Springのトランザクション機能の詳細については、トランザクション管理を参照してください。

6. メール

このセクションでは、Spring Frameworkを使用してメールを送信する方法について説明します。

ライブラリの依存関係

Spring Frameworkのメールライブラリを使用するには、次のJARがアプリケーションのクラスパスに存在する必要があります。

This library is freely available on the web — for example, in Maven Central as com.sun.mail:javax.mail .

Spring Frameworkは、メールを送信するための有用なユーティリティライブラリを提供します。このライブラリは、基礎となるメールシステムの詳細からユーザーを保護し、クライアントに代わって低レベルのリソース処理を行います。

org.springframework.mail パッケージは、Spring Frameworkのメールサポートのルートレベルパッケージです。メールを送信するための中心的なインターフェースは、MailSender インターフェースです。 fromto (その他多数)などの単純なメールのプロパティをカプセル化する単純な値オブジェクトは、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/(英語) )によるスケジューリングをサポートする統合クラスも備えています。 FactoryBeanTimer または 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);
}

最も単純な方法は、RunnableDateのみを使用する schedule という名前の方法です。これにより、指定した時間後にタスクが1回実行されます。他のすべての方法では、タスクを繰り返し実行するようにスケジュールできます。固定レートおよび固定遅延メソッドは、単純な定期的な実行用ですが、Trigger を受け入れるメソッドははるかに柔軟です。

7.2.1. Trigger インターフェース

Trigger インターフェースは、Spring 3.0の時点ではまだ正式に実装されていなかったJSR-236に本質的に触発されています。 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 execute periodically
}

固定レートの実行が必要な場合は、アノテーション内で指定されたプロパティ名を変更できます。次のメソッドは、5秒ごとに呼び出されます(各呼び出しの連続する開始時間の間で測定されます)。

@Scheduled(fixedRate=5000)
public void doSomething() {
    // something that should execute periodically
}

固定遅延タスクと固定レートタスクの場合、次の fixedRate の例に示すように、メソッドの最初の実行まで待機するミリ秒数を指定することにより、初期遅延を指定できます。

@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
    // something that should execute periodically
}

単純な定期的なスケジューリングでは表現が不十分な場合は、cron式を提供できます。例:以下は平日にのみ実行されます:

@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
    // something that should execute 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 executed asynchronously
}

@Scheduled アノテーションが付けられたメソッドとは異なり、これらのメソッドは、コンテナーによって管理されているスケジュールされたタスクからではなく、実行時に呼び出し元によって「通常」の方法で呼び出されるため、引数を期待できます。例:次のコードは、@Async アノテーションの正当なアプリケーションです。

@Async
void doSomething(String s) {
    // this will be executed asynchronously
}

値を返すメソッドでさえ非同期に呼び出すことができます。ただし、そのようなメソッドには Future -typed戻り値が必要です。これにより、非同期実行の利点が得られるため、呼び出し元は、Futureget() を呼び出す前に他のタスクを実行できます。次の例は、値を返すメソッドで @Async を使用する方法を示しています。

@Async
Future<String> returnSomething(int i) {
    // this will be executed 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 executed 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は、Trigger, Jobおよび 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は、CronTriggerFactoryBeanSimpleTriggerFactoryBeanの便利なデフォルトを備えた2つのQuartz FactoryBean 実装を提供します。

トリガーをスケジュールする必要があります。Springは、プロパティとして設定されるトリガーを公開する SchedulerFactoryBean を提供します。 SchedulerFactoryBean は、これらのトリガーを使用して実際のジョブをスケジュールします。

次のリストでは、SimpleTriggerFactoryBeanCronTriggerFactoryBeanの両方を使用しています。

<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つまたはすべてのエントリを削除したりする機能など、他のキャッシュ関連の操作を提供します。これらは、アプリケーションの過程で変化する可能性のあるデータをキャッシュが処理する場合に役立ちます。

As with other services in the Spring Framework, the caching service is an abstraction (not a cache implementation) and requires the use of actual storage to store the cache data — that is, the abstraction frees you from having to write the caching logic but does not provide the actual data store. This abstraction is materialized by the org.springframework.cache.Cache and org.springframework.cache.CacheManager interfaces.

Spring provides いくつかの実装 of that abstraction: JDK java.util.concurrent.ConcurrentMap based caches, ehcache 2.x(英語) , Gemfire cache, Caffeine(GitHub) , and JSR-107 compliant caches (such as Ehcache 3.x). See 異なるバックエンドキャッシュのプラグイン for more information on plugging in other cache stores and providers.

キャッシング抽象化には、マルチスレッドおよびマルチプロセス環境向けの特別な処理はありません。そのような機能はキャッシュ実装によって処理されるためです。

マルチプロセス環境(つまり、複数のノードにデプロイされたアプリケーション)がある場合、それに応じてキャッシュプロバイダーを構成する必要があります。ユースケースによっては、複数のノード上の同じデータのコピーで十分な場合があります。ただし、アプリケーションの実行中にデータを変更する場合は、他の伝播メカニズムを有効にする必要があります。

特定のアイテムをキャッシュすることは、プログラムによるキャッシュのやり取りで見つかった一般的なget-if-not-found-then-proced-and-put-putally最終的にコードブロックに直接相当します。ロックは適用されず、複数のスレッドが同じアイテムを同時にロードしようとする場合があります。同じことが立ち退きにも当てはまります。複数のスレッドがデータを同時に更新または削除しようとしている場合、古いデータを使用する可能性があります。特定のキャッシュプロバイダーは、その領域で高度な機能を提供します。詳細については、キャッシュプロバイダーのドキュメントを参照してください。

キャッシュの抽象化を使用するには、次の2つの側面に注意する必要があります。

  • Caching declaration: Identify the methods that need to be cached and their policy.

  • Cache configuration: The backing cache where the data is stored and from which it is read.

8.2. 宣言的なアノテーションベースのキャッシュ

キャッシュ宣言のために、Springのキャッシュ抽象化はJavaアノテーションのセットを提供します:

  • @Cacheable : キャッシュ作成をトリガーします。

  • @CacheEvict : キャッシュエビクションをトリガーします。

  • @CachePut : メソッドの実行を妨げることなくキャッシュを更新します。

  • @Caching : メソッドに適用される複数のキャッシュ操作を再グループ化します。

  • @CacheConfig : クラスレベルでいくつかの一般的なキャッシュ関連の設定を共有します。

8.2.1. @Cacheable アノテーション

As the name implies, you can use @Cacheable to demarcate methods that are cacheable — that is, methods for which the result is stored in the cache so that, on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method. In its simplest form, the annotation declaration requires the name of the cache associated with the annotated method, as the following example shows:

@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の以前のバージョンでは、複数のキーパラメーターについて、equals()ではなく hashCode() のみを考慮したキー生成戦略を使用していました。これにより、予期しないキーの衝突が発生する可能性があります(背景については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) {...}
1 anotherCacheManagerを指定します。

また、キー生成の置き換えと同様の方法で、CacheResolver を完全に置き換えることもできます。解決はすべてのキャッシュ操作に対して要求され、実装が実行時引数に基づいて使用するキャッシュを実際に解決できるようにします。次の例は、CacheResolverを指定する方法を示しています。

@Cacheable(cacheResolver="runtimeCacheResolver") (1)
public Book findBook(ISBN isbn) {...}
1 CacheResolverを指定します。

Spring 4.1以降、キャッシュアノテーションの value 属性は必須ではなくなりました。これは、アノテーションの内容に関係なく、この特定の情報が CacheResolver によって提供されるためです。

key および keyGeneratorと同様に、cacheManager および cacheResolver パラメーターは相互に排他的であり、両方を指定する操作は例外になります。カスタム CacheManagerCacheResolver 実装によって無視されるため。これはおそらく期待するものではありません。

同期キャッシング

マルチスレッド環境では、特定の操作が同じ引数に対して(通常は起動時に)同時に呼び出される場合があります。デフォルトでは、キャッシュの抽象化は何もロックせず、同じ値が数回計算され、キャッシュの目的を無効にします。

これらの特定のケースでは、sync 属性を使用して、基になるキャッシュプロバイダーに、値の計算中にキャッシュエントリをロックするように指示できます。その結果、値の計算でビジー状態にあるスレッドは1つだけですが、他のスレッドはキャッシュ内のエントリが更新されるまでブロックされます。次の例は、sync 属性の使用方法を示しています。

@Cacheable(cacheNames="foos", sync=true) (1)
public Foo executeExpensiveOperation(String id) {...}
1 sync 属性を使用します。
これはオプションの機能であり、お気に入りのキャッシュライブラリがサポートしていない場合があります。コアフレームワークによって提供されるすべての 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)
1 unless 属性を使用してハードバックをブロックします。

キャッシュの抽象化は 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 式( beforeInvocationfalseの場合)でのみ使用可能です。サポートされているラッパー( 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)
1 allEntries 属性を使用して、キャッシュからすべてのエントリを削除します。

このオプションは、キャッシュ領域全体をクリアする必要がある場合に便利です。前の例に示すように、各エントリを削除するのではなく(非効率的であるため時間がかかります)、すべてのエントリが1回の操作で削除されます。フレームワークは適用されないため、このシナリオで指定されたキーを無視します(1つのエントリだけでなく、キャッシュ全体が削除されます)。

また、beforeInvocation 属性を使用して、メソッドの実行後(デフォルト)または前にエビクションを実行するかどうかを指定できます。前者は、他のアノテーションと同じセマンティクスを提供します。メソッドが正常に完了すると、キャッシュに対するアクション(この場合はエビクション)が実行されます。メソッドが実行されない場合(キャッシュされる可能性があるため)、または例外がスローされる場合、エビクションは発生しません。後者(beforeInvocation=true)を使用すると、メソッドが呼び出される前に常にエビクションが発生します。これは、立ち退きをメソッドの結果に関連付ける必要がない場合に役立ちます。

void メソッドは @CacheEvict で使用できることに注意してください-メソッドはトリガーとして機能するため、戻り値は無視されます(キャッシュと相互作用しないため)。これは、キャッシュにデータを追加または更新する @Cacheable では当てはまらないため、結果が必要です。

8.2.4. @Caching アノテーション

Sometimes, multiple annotations of the same type (such as @CacheEvict or @CachePut ) need to be specified — for example, because the condition or the key expression is different between different caches. @Caching lets multiple nested @Cacheable, @CachePut , and @CacheEvict annotations be used on the same method. The following example uses two @CacheEvict annotations:

@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つのレベルのカスタマイズが可能になります。

  • グローバルに構成され、CacheManager, KeyGeneratorで使用可能。

  • クラスレベルで、@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-classfalse の場合、または属性が省略された場合、標準の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/>DispatcherServletWebApplicationContext に入れると、サービスではなく、コントローラーでのみ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 vs. JSR-107 caching annotations
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 モジュールの両方がクラスパスに存在する場合、@EnableCachingcache: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:definitions cache を介してターゲットキャッシュを定義したように)属性)。

8.5. キャッシュストレージの構成

キャッシュ抽象化は、いくつかのストレージ統合オプションを提供します。使用するには、適切な CacheManagerCache インスタンスを制御および管理し、これらをストレージに取得するために使用できるエンティティ)を宣言する必要があります。

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>

Unofficial Translation by spring.pleiades.io. See the original content.