リファレンスドキュメントのこのパートでは、Spring Framework と多数のテクノロジーとの統合について説明します。
1. REST エンドポイント
Spring Framework には、REST エンドポイントを呼び出すための 2 つの選択肢があります。
RestTemplate
: 同期テンプレートメソッド API を備えた元の Spring REST クライアント。WebClient : 同期および非同期の両方のシナリオとストリーミングのシナリオをサポートする、ノンブロッキングのリアクティブな代替手段。
5.0 の時点で、RestTemplate はメンテナンスモードになっており、今後は変更およびバグのマイナーリクエストのみが受け入れられます。最新の API を提供し、同期、非同期、ストリーミングのシナリオをサポートする WebClient の使用を検討してください。 |
1.1. RestTemplate
RestTemplate
は、HTTP クライアントライブラリを介した高レベルの API を提供します。REST エンドポイントを 1 行で簡単に呼び出すことができます。オーバーロードされたメソッドの次のグループを公開します。
メソッドグループ | 説明 |
---|---|
| GET を介して表現を取得します。 |
| GET を使用して |
| HEAD を使用して、リソースのすべてのヘッダーを取得します。 |
| POST を使用して新しいリソースを作成し、レスポンスから |
| POST を使用して新しいリソースを作成し、レスポンスから表現を返します。 |
| POST を使用して新しいリソースを作成し、レスポンスから表現を返します。 |
| PUT を使用してリソースを作成または更新します。 |
| PATCH を使用してリソースを更新し、レスポンスから表現を返します。JDK |
| DELETE を使用して、指定された URI のリソースを削除します。 |
| ALLOW を使用して、リソースに許可された HTTP メソッドを取得します。 |
| 必要に応じて柔軟性を高める、前述のメソッドのより一般化された(そしてあまり意見のない)バージョン。 これらのメソッドでは、 |
| コールバックインターフェースを介したリクエストの準備とレスポンスの抽出を完全に制御して、リクエストを実行する最も一般的な方法。 |
1.1.1. 初期化
デフォルトのコンストラクターは、java.net.HttpURLConnection
を使用してリクエストを実行します。ClientHttpRequestFactory
の実装により、異なる HTTP ライブラリに切り替えることができます。以下の組み込みサポートがあります。
Apache HttpComponents
Netty
OkHttp
例: Apache HttpComponents に切り替えるには、次を使用できます。
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
各 ClientHttpRequestFactory
は、基礎となる HTTP クライアントライブラリに固有の構成オプションを公開します。たとえば、資格情報、接続プーリング、その他の詳細などです。
HTTP リクエストの java.net 実装は、エラー(401 など)を表すレスポンスのステータスにアクセスするときに例外を発生させる可能性があることに注意してください。これが課題になる場合は、別の HTTP クライアントライブラリに切り替えてください。 |
URI
RestTemplate
メソッドの多くは、String
変数引数として、または Map<String,String>
として URI テンプレートおよび URI テンプレート変数を受け入れます。
次の例では、String
変数引数を使用しています。
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
次の例では、Map<String, String>
を使用しています。
Map<String, String> vars = Collections.singletonMap("hotel", "42");
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
次の例に示すように、URI テンプレートは自動的にエンコードされることに注意してください。
restTemplate.getForObject("https://example.com/hotel list", String.class);
// Results in request to "https://example.com/hotel%20list"
RestTemplate
の uriTemplateHandler
プロパティを使用して、URI のエンコード方法をカスタマイズできます。または、java.net.URI
を準備して、URI
を受け入れる RestTemplate
メソッドの 1 つに渡すことができます。
URI の操作とエンコードの詳細については、URI リンクを参照してください。
ヘッダー
次の例に示すように、exchange()
メソッドを使用してリクエストヘッダーを指定できます。
String uriTemplate = "https://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
RequestEntity<Void> requestEntity = RequestEntity.get(uri)
.header(("MyRequestHeader", "MyValue")
.build();
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
ResponseEntity
を返す多くの RestTemplate
メソッドバリアントを介してレスポンスヘッダーを取得できます。
1.1.2. 本文
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
は、オプションの変換ライブラリが存在するかどうかを判断するのに役立つクラスパスチェックに応じて、すべての組み込みメッセージコンバーターを登録します。明示的に使用するようにメッセージコンバーターを設定することもできます。
1.1.3. メッセージ変換
spring-web
モジュールには、InputStream
および OutputStream
を介した HTTP リクエストおよびレスポンスの本体の読み取りおよび書き込み用の HttpMessageConverter
契約が含まれています。HttpMessageConverter
インスタンスは、クライアント側(たとえば、RestTemplate
)およびサーバー側(たとえば、Spring MVCREST コントローラー)で使用されます。
メインメディア(MIME)タイプの具体的な実装はフレームワークで提供され、デフォルトでクライアント側では RestTemplate
に、サーバー側では RequestMethodHandlerAdapter
に登録されます(メッセージコンバーターの構成を参照)。
HttpMessageConverter
の実装については、次のセクションで説明します。すべてのコンバーターで、デフォルトのメディア型が使用されますが、supportedMediaTypes
Bean プロパティを設定することでそれをオーバーライドできます。次の表は、各実装について説明しています。
MessageConverter | 説明 |
---|---|
| HTTP リクエストおよびレスポンスから |
| HTTP リクエストとレスポンスからフォームデータを読み書きできる |
| HTTP リクエストとレスポンスからバイト配列を読み書きできる |
|
|
| Jackson の |
| Jackson XML: GitHub (英語) 拡張機能の |
| HTTP リクエストおよびレスポンスから |
| HTTP リクエストおよびレスポンスから |
1.1.4. Jackson JSON ビュー
次の例に示すように、Jackson JSON ビュー (英語) を指定して、オブジェクトプロパティのサブセットのみを直列化できます。
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
RequestEntity<MappingJacksonValue> requestEntity =
RequestEntity.post(new URI("https://example.com/user")).body(value);
ResponseEntity<String> response = template.exchange(requestEntity, String.class);
マルチパート
マルチパートデータを送信するには、値がパーツコンテンツ用の Object
、ファイルパーツ用の Resource
、またはヘッダー付きパーツコンテンツ用の HttpEntity
である MultiValueMap<String, Object>
を提供する必要があります。例:
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("fieldPart", "fieldValue");
parts.add("filePart", new FileSystemResource("...logo.png"));
parts.add("jsonPart", new Person("Jason"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
parts.add("xmlPart", new HttpEntity<>(myBean, headers));
ほとんどの場合、各パーツに Content-Type
を指定する必要はありません。コンテンツタイプは、直列化するために選択された HttpMessageConverter
に基づいて自動的に決定されます。Resource
の場合は、ファイル拡張子に基づいて決定されます。必要に応じて、MediaType
に HttpEntity
ラッパーを明示的に提供できます。
MultiValueMap
の準備ができたら、以下に示すように RestTemplate
に渡すことができます。
MultiValueMap<String, Object> parts = ...;
template.postForObject("https://example.com/upload", parts, Void.class);
MultiValueMap
に少なくとも 1 つの非 String
値が含まれている場合、Content-Type
は FormHttpMessageConverter
によって multipart/form-data
に設定されます。MultiValueMap
に String
値がある場合、Content-Type
は application/x-www-form-urlencoded
にデフォルト設定されます。必要に応じて、Content-Type
を明示的に設定することもできます。
1.2. AsyncRestTemplate
を使用する (非推奨)
AsyncRestTemplate
は非推奨です。AsyncRestTemplate
の使用を検討するすべてのユースケースでは、代わりに WebClient を使用してください。
2. リモート処理と Web サービス
Spring は、さまざまなテクノロジーによるリモート処理のサポートを提供します。リモーティングのサポートにより、Java インターフェースおよびオブジェクトを入出力として実装したリモート対応サービスの開発が容易になります。現在、Spring は次のリモートテクノロジーをサポートしています。
Java Web サービス : Spring は、JAX-WS を介して Web サービスのリモートサポートを提供します。
AMQP : 基盤となるプロトコルとしての AMQP を介したリモート処理は、別の Spring AMQP プロジェクトによってサポートされています。
Spring Framework 5.3 の時点で、いくつかのリモート技術のサポートは、セキュリティ上の理由と幅広い業界サポートのために非推奨になりました。サポートするインフラストラクチャは、次のメジャーリリースのために Spring Framework から削除されます。 |
次のリモーティングテクノロジーは非推奨になり、置き換えられません。
リモートメソッドの呼び出し (RMI) :
RmiProxyFactoryBean
およびRmiServiceExporter
を使用することにより、Spring は従来の RMI(java.rmi.Remote
インターフェースおよびjava.rmi.RemoteException
を使用)と RMI インボーカー(Java インターフェースを使用)による透過的なリモーティングの両方をサポートします。Spring HTTP インボーカー (非推奨) : Spring は、HTTP を介した Java 直列化を可能にする特別なリモーティング戦略を提供し、(RMI 呼び出し側が行うように)Java インターフェースをサポートします。対応するサポートクラスは
HttpInvokerProxyFactoryBean
およびHttpInvokerServiceExporter
です。Hessian : Spring の
HessianProxyFactoryBean
とHessianServiceExporter
を使用することにより、Caucho が提供する軽量のバイナリ HTTP ベースのプロトコルを介してサービスを透過的に公開できます。JMS (非推奨) : 基になるプロトコルとしての JMS を介したリモート処理は、
spring-jms
モジュールのJmsInvokerServiceExporter
およびJmsInvokerProxyFactoryBean
クラスを通じてサポートされます。
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 をプロトコルとして使用する例を続けます。
2.1. AMQP
基になるプロトコルとしての AMQP を介したリモート処理は、Spring AMQP プロジェクトでサポートされています。詳細については、Spring AMQP リファレンスの Spring リモーティング (英語) セクションを参照してください。
リモートインターフェースには自動検出は実装されていません 実装されたインターフェースの自動検出がリモートインターフェースで発生しない主な理由は、リモート呼び出し元に対してあまりにも多くのドアを開くことを避けるためです。ターゲットオブジェクトは、 ターゲットによって実装されるすべてのインターフェースを備えたプロキシを提供することは、通常、ローカルのケースでは問題になりません。ただし、リモートサービスをエクスポートする場合は、リモートの使用を目的とした特定の操作を使用して、特定のサービスインターフェースを公開する必要があります。内部コールバックインターフェースに加えて、ターゲットは複数のビジネスインターフェースを実装する場合があり、そのうちの 1 つだけがリモートの公開を目的としています。これらの理由により、このようなサービスインターフェースを指定する必要があります。 これは、構成の利便性と内部メソッドの偶発的な露出のリスクとのトレードオフです。常にサービスインターフェースを指定することは、それほど労力を必要とせず、特定のメソッドの制御された公開に関して安全な側になります。 |
2.2. テクノロジーを選択する際の考慮事項
ここで紹介するすべてのテクノロジーには欠点があります。テクノロジーを選択するときは、ニーズ、公開するサービス、ネットワーク経由で送信するオブジェクトを慎重に検討する必要があります。
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 は、サードパーティまたはカスタムソリューションのプラグインに適したフックのみを提供します。
2.3. Java Web サービス
Spring は、標準の Java Web サービス API を完全にサポートしています。
JAX-WS を使用した Web サービスの公開
JAX-WS を使用した Web サービスへのアクセス
Spring コアでの JAX-WS のストックサポートに加えて、Spring ポートフォリオは、契約先優先のドキュメント駆動型 Web サービスのソリューションである Spring Web Services (英語) も備えています。
2.3.1. JAX-WS を使用してサーブレットベースの Web サービスを公開する
Spring は、JAX-WS サーブレットエンドポイント実装の便利な基本クラス SpringBeanAutowiringSupport
を提供します。AccountService
を公開するには、Spring の SpringBeanAutowiringSupport
クラスを継承し、ここにビジネスロジックを実装します。通常は、呼び出しをビジネスレイヤーに委譲します。Spring の @Autowired
アノテーションを使用して、Spring 管理 Bean へのそのような依存関係を表現します。次の例は、SpringBeanAutowiringSupport
を継承するクラスを示しています。
/**
* JAX-WS compliant AccountService implementation that simply delegates
* to the AccountService implementation in the root web application context.
*
* This wrapper class is necessary because JAX-WS requires working with dedicated
* endpoint classes. If an existing service needs to be exported, a wrapper that
* extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through
* the @Autowired annotation) is the simplest JAX-WS compliant way.
*
* This is the class registered with the server-side JAX-WS implementation.
* In the case of a Java EE server, this would simply be defined as a servlet
* in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting
* accordingly. The servlet name usually needs to match the specified WS service name.
*
* The web service engine manages the lifecycle of instances of this class.
* Spring bean references will just be wired in here.
*/
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
@WebService(serviceName="AccountService")
public class AccountServiceEndpoint extends SpringBeanAutowiringSupport {
@Autowired
private AccountService biz;
@WebMethod
public void insertAccount(Account acc) {
biz.insertAccount(acc);
}
@WebMethod
public Account[] getAccounts(String name) {
return biz.getAccounts(name);
}
}
AccountServiceEndpoint
は、Spring の機能にアクセスできるように、Spring コンテキストと同じ Web アプリケーションで実行する必要があります。これは、JAX-WS サーブレットエンドポイントデプロイの標準契約を使用する、Java EE 環境のデフォルトです。詳細については、さまざまな Java EE Web サービスチュートリアルを参照してください。
2.3.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);
}
}
2.3.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/ (英語) を参照してください。
2.3.4. JAX-WS を使用した Web サービスへのアクセス
Spring は、JAX-WS Web サービスプロキシ、つまり LocalJaxWsServiceFactoryBean
と JaxWsPortProxyFactoryBean
を作成する 2 つのファクトリ Bean を提供します。前者は、作業対象の JAX-WS サービスクラスのみを返すことができます。後者は、ビジネスサービスインターフェースを実装するプロキシを返すことができる本格的なバージョンです。次の例では、JaxWsPortProxyFactoryBean
を使用して AccountService
エンドポイントのプロキシを作成します(再び)。
<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="example.AccountService"/> (1)
<property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/>
<property name="namespaceUri" value="https://example/"/>
<property name="serviceName" value="AccountService"/>
<property name="portName" value="AccountServiceEndpointPort"/>
</bean>
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 などのアノテーションを付ける必要があるため、上記はわずかに単純化されています。これは、プレーン Java インターフェースと実装クラスを JAX-WS エンドポイントアーティファクトとして(簡単に)使用できないことを意味します。最初にそれに応じてアノテーションを付ける必要があります。これらの要件の詳細については、JAX-WS のドキュメントを確認してください。 |
2.4. RMI (非推奨)
Spring Framework 5.3 の時点で、RMI サポートは非推奨になり、置き換えられることはありません。 |
Spring の RMI のサポートを使用すると、RMI インフラストラクチャを通じてサービスを透過的に公開できます。これをセットアップすると、基本的にリモート EJB に似た構成になりますが、セキュリティコンテキスト伝播またはリモートトランザクション伝播の標準サポートがないという事実を除きます。Spring は、RMI インボーカーを使用する場合、このような追加の呼び出しコンテキストのフックを提供するため、たとえば、セキュリティフレームワークまたはカスタムセキュリティ資格情報をプラグインできます。
2.4.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)。これは、サービスとの通信に匿名ポートが使用されることを意味します。 |
2.4.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
を使用してリンクします。
2.5. Hessian を使用して、HTTP を介してリモートでサービスを呼び出す (非推奨)
Spring Framework 5.3 の時点で、Hessian のサポートは非推奨になり、置き換えられることはありません。 |
Hessian は、バイナリ HTTP ベースのリモートプロトコルを提供します。Caucho によって開発されており、https://www.caucho.com/ (英語) で Hessian 自体に関する詳細情報を見つけることができます。
2.5.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 名と一致する必要があります。
2.5.2. HessianServiceExporter
を使用して Bean を公開する
次の例に示すように、新しく作成された remoting-servlet.xml
というアプリケーションコンテキストで、HessianServiceExporter
を作成してサービスをエクスポートします。
<bean id="accountService" class="example.AccountServiceImpl">
<!-- any additional properties, maybe a DAO? -->
</bean>
<bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
これで、クライアントでサービスにリンクする準備ができました。(リクエスト URL をサービスにマップするために)明示的なハンドラーマッピングが指定されていないため、BeanNameUrlHandlerMapping
を使用します。サービスは、含まれる DispatcherServlet
インスタンスのマッピング内の Bean 名で示された URL でエクスポートされます(前に定義したとおり): https://HOST:8080/remoting/AccountService (英語)
または、次の例に示すように、ルートアプリケーションコンテキスト(たとえば WEB-INF/applicationContext.xml
)で HessianServiceExporter
を作成できます。
<bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="accountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
後者の場合、このエクスポーターに対応するサーブレットを web.xml
で定義する必要があります。同じ結果が得られます。エクスポーターは /remoting/AccountService
でリクエストパスにマップされます。サーブレット名は、ターゲットエクスポーターの Bean 名と一致する必要があることに注意してください。次の例は、その方法を示しています。
<servlet>
<servlet-name>accountExporter</servlet-name>
<servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>accountExporter</servlet-name>
<url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>
2.5.3. クライアント上のサービスへのリンク
HessianProxyFactoryBean
を使用することにより、クライアントでサービスにリンクできます。RMI の例と同じ原則が適用されます。別の Bean ファクトリまたはアプリケーションコンテキストを作成し、次の例に示すように、SimpleObject
が AccountService
を使用してアカウントを管理する次の Bean にメンションします。
<bean class="example.SimpleObject">
<property name="accountService" ref="accountService"/>
</bean>
<bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl" value="https://remotehost:8080/remoting/AccountService"/>
<property name="serviceInterface" value="example.AccountService"/>
</bean>
2.5.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 プロジェクトを参照してください。 |
2.6. Spring HTTP インボーカー (非推奨)
Spring Framework 5.3 の時点で、HTTP Invoker のサポートは非推奨になり、置き換えられることはありません。 |
Hessian とは対照的に、Spring HTTP インボーカーは、独自のスリムな直列化メカニズムを使用する軽量プロトコルであり、標準の Java 直列化メカニズムを使用して HTTP を介してサービスを公開します。引数と戻り値の型が、Hessian が使用する直列化メカニズムを使用して直列化できない複雑な型である場合、これは大きな利点があります(リモーティングテクノロジーを選択する際の考慮事項については、次のセクションを参照してください)。
内部では、Spring は JDK または Apache HttpComponents
が提供する標準機能を使用して HTTP 呼び出しを実行します。より高度で使いやすい機能が必要な場合は、後者を使用してください。詳細については、hc.apache.org/httpcomponents-client-ga/ (英語) を参照してください。
安全でない Java の逆直列化による脆弱性に注意してください。操作された入力ストリームは、逆直列化ステップ中にサーバー上で望ましくないコード実行を引き起こす可能性があります。結果として、HTTP 呼び出し側エンドポイントを信頼できないクライアントに公開しないでください。むしろ、独自のサービス間でのみ公開します。一般的に、代わりに他のメッセージ形式(JSON など)を使用することを強くお勧めします。 Java の直列化によるセキュリティの脆弱性が心配な場合は、元々 JDK 9 用に開発されましたが、その間に JDK 8, 7 および 6 にバックポートされた、コア JVM レベルでの汎用直列化フィルターメカニズムを検討してください。 https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_a (英語) および https://openjdk.java.net/jeps/290 (英語) を参照してください。 |
2.6.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>
2.6.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>
2.7. JMS (非推奨)
Spring Framework 5.3 の時点で、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>
2.7.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");
}
}
2.7.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));
}
}
3. エンタープライズ 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)にアクセスするときに特定の値を提供するため、このトピックについて説明することから始めます。
3.1. EJB へのアクセス
このセクションでは、EJB にアクセスする方法について説明します。
3.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 ルックアップ、または手書きのビジネスデリゲートでメソッドを複製する必要はありません。
3.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
を調べたい場合があります。
3.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
を宣言することを妨げるものは何もありません。
3.1.4. EJB 2.x SLSB と EJB 3 SLSB へのアクセス
Spring を介した EJB 2.x セッション Bean および EJB 3 セッション Bean へのアクセスは、ほとんど透過的です。<jee:local-slsb>
および <jee:remote-slsb>
機能を含む Spring の EJB アクセサーは、実行時に実際のコンポーネントに透過的に適応します。見つかった場合はホームインターフェースを処理し(EJB 2.x スタイル)、ホームインターフェースが使用できない場合は(EJB 3 スタイル)直接コンポーネント呼び出しを実行します。
メモ: EJB 3 セッション Bean の場合、完全に使用可能なコンポーネント参照がプレーン JNDI ルックアップ用に公開されているため、JndiObjectFactoryBean
/ <jee:jndi-lookup>
も効果的に使用できます。明示的な <jee:local-slsb>
または <jee:remote-slsb>
ルックアップを定義すると、一貫性のある、より明示的な EJB アクセス構成が提供されます。
4. 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 規則に準拠しているため、対応する構成でこのようなシナリオをサポートします。ただし、これは移行シナリオでのみ考慮してください。 |
4.1. Spring JMS の使用
このセクションでは、Spring の JMS コンポーネントの使用方法について説明します。
4.1.1. JmsTemplate
を使用する
JmsTemplate
クラスは、JMS コアパッケージの中心的なクラスです。メッセージの送信または同期受信時にリソースの作成と解放を処理するため、JMS の使用が簡単になります。
JmsTemplate
を使用するコードは、明確に定義された高レベルの契約を提供するコールバックインターフェースを実装するだけで済みます。MessageCreator
コールバックインターフェースは、JmsTemplate
の呼び出しコードによって提供される Session
が与えられると、メッセージを作成します。JMS API のより複雑な使用を可能にするために、SessionCallback
は JMS セッションを提供し、ProducerCallback
は Session
と MessageProducer
のペアを公開します。
JMS API は、2 つの型の送信メソッドを公開します。1 つは配信モード、優先度、有効期間を Quality of Service(QOS)パラメーターとして使用する方法、もう 1 つは QOS パラメーターを使用せずデフォルト値を使用する方法です。JmsTemplate
には多くの送信メソッドがあるため、QOS パラメーターの設定は Bean プロパティとして公開されており、送信メソッドの数の重複を避けています。同様に、同期受信呼び出しのタイムアウト値は、setReceiveTimeout
プロパティを使用して設定されます。
一部の JMS プロバイダーでは、ConnectionFactory
の構成を介して管理上のデフォルトの QOS 値を設定できます。これには、MessageProducer
インスタンスの send
メソッド(send(Destination destination, Message message)
)の呼び出しが、JMS 仕様で指定されているものとは異なる QOS デフォルト値を使用するという効果があります。QOS 値の一貫した管理を実現するには、ブールプロパティ isExplicitQosEnabled
を true
に設定して、JmsTemplate
が独自の QOS 値を使用できるように明確に有効にする必要があります。
便宜上、JmsTemplate
は、操作の一部として作成された一時キューでメッセージの送信と応答の待機を可能にする基本的なリクエスト / 応答操作も公開します。
JmsTemplate クラスのインスタンスは、一度設定されるとスレッドセーフです。これは重要です。なぜなら、JmsTemplate の単一のインスタンスを構成してから、この共有参照を複数の共同作業者に安全に注入できるからです。明確にするために、JmsTemplate はステートフルであり、ConnectionFactory への参照を維持しますが、この状態は会話状態ではありません。 |
Spring Framework 4.1 以降、JmsMessagingTemplate
は JmsTemplate
の上に構築され、メッセージングの抽象化、つまり org.springframework.messaging.Message
との統合を提供します。これにより、一般的な方法で送信するメッセージを作成できます。
4.1.2. 接続
JmsTemplate
には、ConnectionFactory
への参照が必要です。ConnectionFactory
は JMS 仕様の一部であり、JMS を操作するためのエントリポイントとして機能します。クライアントアプリケーションがファクトリとして使用して JMS プロバイダーとの接続を作成し、SSL 構成オプションなど、ベンダー固有のさまざまな構成パラメーターをカプセル化します。
EJB 内で JMS を使用する場合、ベンダーは、宣言的なトランザクション管理に参加し、接続とセッションのプーリングを実行できるように、JMS インターフェースの実装を提供します。この実装を使用するために、Java EE コンテナーでは通常、EJB またはサーブレットデプロイ記述子内で JMS 接続ファクトリを resource-ref
として宣言する必要があります。EJB 内の JmsTemplate
でこれらの機能を確実に使用するには、クライアントアプリケーションが ConnectionFactory
の管理された実装を参照することを確認する必要があります。
メッセージングリソースのキャッシュ
標準 API には、多くの中間オブジェクトの作成が含まれます。メッセージを送信するには、次の「API」ウォークが実行されます。
ConnectionFactory->Connection->Session->MessageProducer->send
ConnectionFactory
操作と Send
操作の間で、3 つの中間オブジェクトが作成および破棄されます。リソースの使用を最適化し、パフォーマンスを向上させるために、Spring は ConnectionFactory
の 2 つの実装を提供します。
SingleConnectionFactory
を使用する
Spring は、すべての createConnection()
呼び出しで同じ Connection
を返し、close()
への呼び出しを無視する ConnectionFactory
インターフェース SingleConnectionFactory
の実装を提供します。これは、テスト環境およびスタンドアロン環境で役立ちます。そのため、同じ接続を任意の数のトランザクションにまたがる複数の JmsTemplate
呼び出しに使用できます。SingleConnectionFactory
は、通常 JNDI から提供される標準 ConnectionFactory
への参照を取ります。
CachingConnectionFactory
を使用する
CachingConnectionFactory
は、SingleConnectionFactory
の機能を継承し、Session
、MessageProducer
、MessageConsumer
インスタンスのキャッシュを追加します。初期キャッシュサイズは 1
に設定されています。sessionCacheSize
プロパティを使用して、キャッシュされるセッションの数を増やすことができます。セッションは確認応答モードに基づいてキャッシュされるため、実際にキャッシュされるセッションの数はその数よりも多いことに注意してください。sessionCacheSize
が 1 に設定されている場合、最大 4 つのキャッシュセッションインスタンス(確認応答モードごとに 1 つ)が存在する可能性があります。MessageProducer
および MessageConsumer
インスタンスは、所有するセッション内でキャッシュされ、キャッシュ時にプロデューサーとコンシューマーの固有のプロパティも考慮に入れます。MessageProducers は、宛先に基づいてキャッシュされます。MessageConsumers は、宛先、セレクター、noLocal 配信フラグ、および永続サブスクリプション名(永続コンシューマーを作成する場合)で構成されるキーに基づいてキャッシュされます。
4.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
を構成することもできます。デフォルトの宛先は、特定の宛先を参照しない送信および受信操作です。
4.1.4. メッセージリスナコンテナー
EJB における JMS メッセージの最も一般的な用途の 1 つは、メッセージ駆動型 Bean(MDB)を駆動することです。Spring は、ユーザーを EJB コンテナーに関連付けない方法でメッセージ駆動型 POJO(MDP)を作成するソリューションを提供します。(Spring の MDP サポートの詳細については、非同期受信: メッセージ駆動型 POJO を参照してください)Spring Framework 4.1 以降、エンドポイントメソッドには @JmsListener
でアノテーションを付けることができます。詳細については、アノテーション駆動型のリスナーエンドポイントを参照してください。
メッセージリスナコンテナーは、JMS メッセージキューからメッセージを受信し、そこに挿入される MessageListener
を駆動するために使用されます。リスナーコンテナーは、メッセージ受信のすべてのスレッド化を処理し、処理のためにリスナーにディスパッチします。メッセージリスナコンテナーは、MDP とメッセージングプロバイダ間の仲介者であり、メッセージの受信登録、トランザクションへの参加、リソースの取得と解放、例外変換などを処理します。これにより、メッセージの受信(および場合によっては応答)に関連する(場合によっては複雑な)ビジネスロジックを記述でき、JMS インフラストラクチャに関する定型的な問題をフレームワークに委譲できます。
Spring にパッケージ化された 2 つの標準 JMS メッセージリスナーコンテナーがあり、それぞれに専用の機能セットがあります。
SimpleMessageListenerContainer
を使用する
このメッセージリスナコンテナーは、2 つの標準フレーバのうち単純なものです。起動時に一定数の JMS セッションとコンシューマーを作成し、標準の JMS MessageConsumer.setMessageListener()
メソッドを使用してリスナーを登録し、リスナーコールバックを実行するために JMS プロバイダーに任せます。このバリアントでは、ランタイムの要求に動的に適応したり、外部管理トランザクションに参加したりすることはできません。互換性に関しては、スタンドアロン JMS 仕様の精神に非常に近いままですが、一般的に Java EE の JMS 制限と互換性がありません。
SimpleMessageListenerContainer は外部管理トランザクションへの参加を許可しませんが、ネイティブ JMS トランザクションをサポートします。この機能を有効にするには、sessionTransacted フラグを true に切り替えるか、XML 名前空間で acknowledge 属性を transacted に設定します。リスナーからスローされた例外はロールバックにつながり、メッセージが再配信されます。または、CLIENT_ACKNOWLEDGE モードの使用を検討してください。CLIENT_ACKNOWLEDGE モードは、例外の場合にも再配信を提供しますが、トランザクション Session インスタンスを使用しないため、トランザクションプロトコルに他の Session 操作(レスポンスメッセージの送信など)は含まれません。 |
デフォルトの AUTO_ACKNOWLEDGE モードは、適切な信頼性の保証を提供しません。リスナーの実行が失敗すると(プロバイダーがリスナー呼び出し後に各メッセージを自動的に確認するため、プロバイダーに例外が伝播されないため)、またはリスナーコンテナーがシャットダウンすると(acceptMessagesWhileStopping フラグを設定することでこれを構成できます)メッセージは失われます。信頼性が必要な場合は、トランザクションセッションを必ず使用してください(たとえば、信頼性の高いキュー処理や永続的なトピックサブスクリプションなど)。 |
DefaultMessageListenerContainer
を使用する
ほとんどの場合、このメッセージリスナコンテナーが使用されます。SimpleMessageListenerContainer
とは対照的に、このコンテナーバリアントは、実行時の要求に動的に適応させることができ、外部で管理されたトランザクションに参加できます。JtaTransactionManager
で構成された場合、受信した各メッセージは XA トランザクションに登録されます。その結果、処理は XA トランザクションのセマンティクスを利用できます。このリスナーコンテナーは、JMS プロバイダーの低い要件、高度な機能(外部管理トランザクションへの参加など)、および Java EE 環境との互換性のバランスが取れています。
コンテナーのキャッシュレベルをカスタマイズできます。キャッシュが有効になっていない場合、メッセージの受信ごとに新しい接続と新しいセッションが作成されることに注意してください。これを高負荷の非永続サブスクリプションと組み合わせると、メッセージが失われる可能性があります。そのような場合は、適切なキャッシュレベルを使用してください。
このコンテナーには、ブローカーがダウンしたときに回復可能な機能もあります。デフォルトでは、簡単な BackOff
実装は 5 秒ごとに再試行します。より詳細な回復オプションのために、カスタム BackOff
実装を指定できます。例については、api-spring-framework/util/backoff/ExponentialBackOff.html [ExponentialBackOff
] を参照してください。
兄弟( SimpleMessageListenerContainer )と同様に、DefaultMessageListenerContainer はネイティブ JMS トランザクションをサポートし、確認モードのカスタマイズを可能にします。シナリオに適している場合、これは外部管理トランザクションよりも強くお勧めします。つまり、JVM が死んだ場合に時折重複するメッセージで生きることができる場合です。ビジネスロジックのカスタム重複メッセージ検出手順は、そのような状況をカバーできます。たとえば、ビジネスエンティティの存在チェックまたはプロトコルテーブルチェックの形式です。そのような取り決めは、代替手段よりもはるかに効率的です。JMS メッセージの受信とメッセージリスナーのビジネスロジックの実行をカバーするために、処理全体を XA トランザクションでラップします(DefaultMessageListenerContainer を JtaTransactionManager で構成することにより)(データベース操作などを含む)。 |
デフォルトの AUTO_ACKNOWLEDGE モードは、適切な信頼性の保証を提供しません。リスナーの実行が失敗すると(プロバイダーがリスナー呼び出し後に各メッセージを自動的に確認するため、プロバイダーに例外が伝播されないため)、またはリスナーコンテナーがシャットダウンすると(acceptMessagesWhileStopping フラグを設定することでこれを構成できます)メッセージは失われます。信頼性が必要な場合は、トランザクションセッションを必ず使用してください(たとえば、信頼性の高いキュー処理や永続的なトピックサブスクリプションなど)。 |
4.1.5. トランザクション管理
Spring は、単一の JMS ConnectionFactory
のトランザクションを管理する JmsTransactionManager
を提供します。これにより、データアクセスの章のトランザクション管理セクションに従って、JMS アプリケーションは Spring のマネージドトランザクション機能を活用できます。JmsTransactionManager
はローカルリソーストランザクションを実行し、指定された ConnectionFactory
からの JMS 接続 / セッションペアをスレッドにバインドします。JmsTemplate
は、このようなトランザクションリソースを自動的に検出し、それに応じて動作します。
Java EE 環境では、ConnectionFactory
は接続およびセッションインスタンスをプールするため、これらのリソースはトランザクション全体で効率的に再利用されます。スタンドアロン環境では、Spring の SingleConnectionFactory
を使用すると、各 JMS Connection
が共有され、各トランザクションは独自の独立した Session
を持ちます。または、ActiveMQ の PooledConnectionFactory
クラスなど、プロバイダー固有のプーリングアダプターの使用を検討してください。
JmsTemplate
を JtaTransactionManager
および XA 対応 JMS ConnectionFactory
とともに使用して、分散トランザクションを実行することもできます。これには、JTA トランザクションマネージャーと、適切に XA が構成された ConnectionFactory を使用する必要があることに注意してください。(Java EE サーバーまたは JMS プロバイダーのドキュメントを確認してください。)
JMS API を使用して Connection
から Session
を作成する場合、管理されたトランザクション環境と管理されていないトランザクション環境でコードを再利用すると混乱する可能性があります。これは、JMS API には Session
を作成するファクトリメソッドが 1 つしかなく、トランザクションモードと確認応答モードの値が必要です。管理された環境では、これらの値の設定は環境のトランザクションインフラストラクチャの責任であるため、これらの値はベンダーの JMS 接続へのラッパーによって無視されます。管理されていない環境で JmsTemplate
を使用する場合、プロパティ sessionTransacted
および sessionAcknowledgeMode
を使用してこれらの値を指定できます。JmsTemplate
で PlatformTransactionManager
を使用する場合、テンプレートには常にトランザクション JMS JMS Session
が与えられます。
4.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)
はその宛先にメッセージを送信します。
4.2.1. メッセージコンバーターの使用
ドメインモデルオブジェクトの送信を容易にするために、JmsTemplate
には、メッセージのデータコンテンツの引数として Java オブジェクトを受け取るさまざまな send メソッドがあります。JmsTemplate
のオーバーロードされたメソッド convertAndSend()
および receiveAndConvert()
メソッドは、変換プロセスを MessageConverter
インターフェースのインスタンスに委譲します。このインターフェースは、Java オブジェクトと JMS メッセージの間で変換するための単純な契約を定義します。デフォルトの実装(SimpleMessageConverter
)は、String
と TextMessage
、byte[]
、BytesMesssage
の間、および java.util.Map
と MapMessage
の間の変換をサポートします。コンバーターを使用することにより、ユーザーとアプリケーションコードは、JMS を介して送受信されるビジネスオブジェクトに焦点を当てることができ、JMS メッセージとしてどのように表現されるかについての詳細を気にする必要はありません。
現在、サンドボックスには MapMessageConverter
が含まれています。MapMessageConverter
は、リフレクションを使用して JavaBean と MapMessage
を変換します。自分で実装できる他の一般的な実装の選択肢は、既存の XML マーシャリングパッケージ(JAXB や XStream など)を使用してオブジェクトを表す TextMessage
を作成するコンバーターです。
コンバータークラス内に一般的にカプセル化できないメッセージのプロパティ、ヘッダー、本文の設定に対応するために、MessagePostProcessor
インターフェースでは、変換後、送信前にメッセージにアクセスできます。次の例は、java.util.Map
がメッセージに変換された後にメッセージヘッダーとプロパティを変更する方法を示しています。
public void sendWithConversion() {
Map map = new HashMap();
map.put("Name", "Mark");
map.put("Age", new Integer(47));
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
public Message postProcessMessage(Message message) throws JMSException {
message.setIntProperty("AccountID", 1234);
message.setJMSCorrelationID("123-00001");
return message;
}
});
}
これにより、次の形式のメッセージが生成されます。
MapMessage={ Header={ ... standard headers ... CorrelationID={123-00001} } Properties={ AccountID={Integer:1234} } Fields={ Name={String:Mark} Age={Integer:47} } }
4.3. メッセージを受信する
これは、Spring で JMS を使用してメッセージを受信する方法を説明しています。
4.3.1. 同期受信
通常、JMS は非同期処理に関連付けられていますが、メッセージを同期的に消費できます。オーバーロードされた receive(..)
メソッドは、この機能を提供します。同期受信中、呼び出しスレッドはメッセージが利用可能になるまでブロックします。これは、呼び出しスレッドが潜在的に無期限にブロックされる可能性があるため、危険な操作になる可能性があります。receiveTimeout
プロパティは、メッセージの待機をあきらめるまで受信者が待機する時間を指定します。
4.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 を参照してください。
4.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
インターフェースを使用する場合、スローされた例外を処理するのはクライアントコードの責任です。
4.3.4. MessageListenerAdapter
を使用する
MessageListenerAdapter
クラスは、Spring の非同期メッセージングサポートの最後のコンポーネントです。簡単に言えば、ほぼすべてのクラスを MDP として公開できます(ただし、いくつかの制約があります)。
次のインターフェース定義を検討してください。
public interface MessageDelegate {
void handleMessage(String message);
void handleMessage(Map message);
void handleMessage(byte[] message);
void handleMessage(Serializable message);
}
インターフェースは MessageListener
インターフェースも SessionAwareMessageListener
インターフェースも拡張しませんが、MessageListenerAdapter
クラスを使用することにより、MDP として使用できることに注意してください。また、受信および処理できるさまざまな Message
タイプの内容に従って、さまざまなメッセージ処理メソッドがどのように強く型付けされているかに注意してください。
次に、MessageDelegate
インターフェースの以下の実装を検討してください。
public class DefaultMessageDelegate implements MessageDelegate {
// implementation elided for clarity...
}
特に、MessageDelegate
インターフェースの前の実装(DefaultMessageDelegate
クラス)に JMS 依存関係がまったくないことに注意してください。これは、次の構成を介して MDP を作成できる POJO です。
<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="jmsexample.DefaultMessageDelegate"/>
</constructor-arg>
</bean>
<!-- and this is the message listener container... -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
</bean>
次の例は、JMS TextMessage
メッセージの受信のみを処理できる別の MDP を示しています。メッセージ処理メソッドが実際に receive
と呼ばれることに注意してください(MessageListenerAdapter
のメッセージ処理メソッドの名前はデフォルトで handleMessage
になります)が、構成可能です(このセクションで後述します)。また、receive(..)
メソッドが JMS TextMessage
メッセージのみを受信して応答するように厳密に入力されていることに注意してください。次のリストは、TextMessageDelegate
インターフェースの定義を示しています。
public interface TextMessageDelegate {
void receive(TextMessage message);
}
次のリストは、TextMessageDelegate
インターフェースを実装するクラスを示しています。
public class DefaultTextMessageDelegate implements TextMessageDelegate {
// implementation elided for clarity...
}
アテンダント MessageListenerAdapter
の構成は次のようになります。
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="jmsexample.DefaultTextMessageDelegate"/>
</constructor-arg>
<property name="defaultListenerMethod" value="receive"/>
<!-- we don't want automatic message context extraction -->
<property name="messageConverter">
<null/>
</property>
</bean>
messageListener
が TextMessage
以外のタイプの JMS Message
を受信した場合、IllegalStateException
がスローされる(その後、飲み込まれる)ことに注意してください。MessageListenerAdapter
クラスのもう 1 つの機能は、ハンドラーメソッドが非 void 値を返す場合に、自動的にレスポンス Message
を送り返す機能です。次のインターフェースとクラスを検討してください。
public interface ResponsiveTextMessageDelegate {
// notice the return type...
String receive(TextMessage message);
}
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
// implementation elided for clarity...
}
DefaultResponsiveTextMessageDelegate
を MessageListenerAdapter
と組み合わせて使用する場合、'receive(..)'
メソッドの実行から返される null 以外の値は(デフォルト構成で) TextMessage
に変換されます。結果の TextMessage
は、元の Message
の JMS Reply-To
プロパティで定義された Destination
(存在する場合)または MessageListenerAdapter
に設定されたデフォルト Destination
(設定されている場合)に送信されます。Destination
が見つからない場合、InvalidDestinationException
がスローされます(この例外は飲み込まれず、呼び出しスタックに伝播することに注意してください)。
4.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 | トランザクションマネージャー。 |
4.4. JCA メッセージエンドポイントのサポート
バージョン 2.5 から、Spring は JCA ベースの MessageListener
コンテナーのサポートも提供します。JmsMessageEndpointManager
は、プロバイダーの ResourceAdapter
クラス名から ActivationSpec
クラス名を自動的に決定しようとします。次の例に示すように、通常 Spring の汎用 JmsActivationSpecConfig
を提供することができます。
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
<property name="resourceAdapter" ref="resourceAdapter"/>
<property name="activationSpecConfig">
<bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
<property name="destinationName" value="myQueue"/>
</bean>
</property>
<property name="messageListener" ref="myMessageListener"/>
</bean>
または、特定の ActivationSpec
オブジェクトで JmsMessageEndpointManager
をセットアップできます。ActivationSpec
オブジェクトは、(<jee:jndi-lookup>
を使用して)JNDI ルックアップから取得することもできます。次の例は、その方法を示しています。
<bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
<property name="resourceAdapter" ref="resourceAdapter"/>
<property name="activationSpec">
<bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
<property name="destination" value="myQueue"/>
<property name="destinationType" value="javax.jms.Queue"/>
</bean>
</property>
<property name="messageListener" ref="myMessageListener"/>
</bean>
Spring の ResourceAdapterFactoryBean
を使用して、次の例に示すように、ターゲット ResourceAdapter
をローカルに構成できます。
<bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
<property name="resourceAdapter">
<bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
<property name="serverUrl" value="tcp://localhost:61616"/>
</bean>
</property>
<property name="workManager">
<bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
</property>
</bean>
指定された WorkManager
は、通常は SimpleTaskWorkManager
インスタンスの asyncTaskExecutor
プロパティを介して、環境固有のスレッドプールを指すこともできます。複数のアダプターを使用する場合は、すべての ResourceAdapter
インスタンスに共有スレッドプールを定義することを検討してください。
一部の環境(WebLogic 9 以上など)では、代わりに(<jee:jndi-lookup>
を使用して)JNDI から ResourceAdapter
オブジェクト全体を取得できます。Spring ベースのメッセージリスナーは、サーバーがホストする ResourceAdapter
と対話できます。サーバーがホストする ResourceAdapter
もサーバーの組み込み WorkManager
を使用します。
詳細については、JmsMessageEndpointManager
(Javadoc) 、JmsActivationSpecConfig
(Javadoc) 、ResourceAdapterFactoryBean
(Javadoc) の javadoc を参照してください。
Spring は、JMS に関連付けられていない汎用 JCA メッセージエンドポイントマネージャー org.springframework.jca.endpoint.GenericMessageEndpointManager
も提供します。このコンポーネントを使用すると、任意のメッセージリスナー型(JMS MessageListener
など)および任意のプロバイダー固有の ActivationSpec
オブジェクトを使用できます。コネクターの実際の機能については、JCA プロバイダーのドキュメントを参照してください。また、Spring 固有の構成の詳細については、GenericMessageEndpointManager
(Javadoc) javadoc を参照してください。
JCA ベースのメッセージエンドポイント管理は、EJB 2.1 メッセージ駆動型 Bean に非常に似ています。同じ基になるリソースプロバイダー契約を使用します。EJB 2.1 MDB と同様に、Spring コンテキストでも JCA プロバイダーでサポートされている任意のメッセージリスナーインターフェースを使用できます。それでも、Spring は、JMS の明示的な「便利な」サポートを提供します。これは、JMS が JCA エンドポイント管理契約で使用される最も一般的なエンドポイント API であるためです。 |
4.5. アノテーション駆動型のリスナーエンドポイント
メッセージを非同期で受信する最も簡単な方法は、アノテーション付きのリスナーエンドポイントインフラストラクチャを使用することです。簡単に言えば、管理対象の Bean のメソッドを JMS リスナーエンドポイントとして公開できます。次の例は、その使用方法を示しています。
@Component
public class MyService {
@JmsListener(destination = "myDestination")
public void processOrder(String data) { ... }
}
前の例の考え方は、メッセージが javax.jms.Destination
myDestination
で使用可能であるときはいつでも、それに応じて processOrder
メソッドが呼び出されるということです(この場合、JMS メッセージの内容で、MessageListenerAdapter
が提供するものと同様です)。
アノテーション付きエンドポイントインフラストラクチャは、JmsListenerContainerFactory
を使用して、アノテーション付きメソッドごとに背後でメッセージリスナーコンテナーを作成します。このようなコンテナーは、アプリケーションコンテキストに対して登録されていませんが、JmsListenerEndpointRegistry
Bean を使用して管理目的で簡単に見つけることができます。
@JmsListener は Java 8 の繰り返し可能なアノテーションであるため、@JmsListener 宣言を追加することにより、複数の JMS 宛先を同じメソッドに関連付けることができます。 |
4.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>
4.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
を介してエンドポイントのみをプログラムで登録できることに注意してください。
4.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;
}
}
4.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;
}
}
4.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>
1 | JMS スキーマの参照。 |
名前空間は、<annotation-driven/>
、<listener-container/>
、<jca-listener-container/>
という 3 つのトップレベル要素で構成されています。<annotation-driven/>
は、アノテーション駆動型リスナーエンドポイントの使用を可能にし ます。<listener-container/>
および <jca-listener-container/>
は、共有リスナーコンテナー構成を定義し、<listener/>
子要素を含めることができます。次の例は、2 つのリスナーの基本的な構成を示しています。
<jms:listener-container>
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
<jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
</jms:listener-container>
上記の例は、MessageListenerAdapter
を使用するに示すように、2 つの異なるリスナーコンテナー Bean 定義と 2 つの異なる MessageListenerAdapter
Bean 定義を作成することと同等です。前の例に示した属性に加えて、listener
要素にはいくつかのオプションのものを含めることができます。次の表に、使用可能なすべての属性を示します。
属性 | 説明 |
---|---|
| ホスティングリスナーコンテナーの Bean 名。指定しない場合、Bean 名が自動的に生成されます。 |
|
|
| ハンドラーオブジェクトの Bean 名。 |
| 呼び出すハンドラーメソッドの名前。 |
| レスポンスメッセージを送信するデフォルトのレスポンス宛先の名前。これは、 |
| 永続サブスクリプションの名前(ある場合)。 |
| このリスナーのオプションのメッセージセレクター。 |
| このリスナーに対して開始する同時セッションまたはコンシューマーの数。この値は、最大数を示す単純な数値(たとえば、 |
<listener-container/>
要素は、いくつかのオプション属性も受け入れます。これにより、さまざまな戦略(taskExecutor
や destinationResolver
など)のカスタマイズ、および基本的な JMS 設定とリソース参照が可能になります。これらの属性を使用することにより、名前空間の利便性を活用しながら、高度にカスタマイズされたリスナーコンテナーを定義できます。
次の例に示すように、Bean の id
を指定して factory-id
属性を介して公開することにより、そのような設定を JmsListenerContainerFactory
として自動的に公開できます。
<jms:listener-container connection-factory="myConnectionFactory"
task-executor="myTaskExecutor"
destination-resolver="myDestinationResolver"
transaction-manager="myTransactionManager"
concurrency="10">
<jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
<jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
</jms:listener-container>
次の表に、使用可能なすべての属性を示します。個々のプロパティの詳細については、AbstractMessageListenerContainer
(Javadoc) のクラスレベルの javadoc およびその具象サブクラスを参照してください。javadoc では、トランザクションの選択とメッセージの再配信シナリオについても説明しています。
属性 | 説明 |
---|---|
| このリスナーコンテナーのタイプ。使用可能なオプションは |
| 完全修飾クラス名としてのカスタムリスナーコンテナー実装クラス。デフォルトは、 |
| 指定された |
| JMS |
| JMS リスナー呼び出し側の Spring |
| JMS |
| JMS メッセージをリスナーメソッドの引数に変換するための |
|
|
| このリスナーの JMS 宛先タイプ: |
| レスポンスの JMS 宛先タイプ: |
| このリスナーコンテナーの JMS クライアント ID。永続サブスクリプションを使用する場合は、指定する必要があります。 |
| JMS リソースのキャッシュレベル: |
| ネイティブ JMS 確認モード: |
| 外部 |
| リスナーごとに開始する同時セッションまたはコンシューマーの数。最大数を示す単純な数値(たとえば、 |
| 単一セッションにロードするメッセージの最大数。この数を増やすと、同時消費者が枯渇する可能性があることに注意してください。 |
| 受信呼び出しに使用するタイムアウト(ミリ秒)。デフォルトは |
| 回復試行の間隔を計算するために使用する |
| 回復試行の間隔をミリ秒単位で指定します。指定した間隔で |
| このコンテナーが開始および停止するライフサイクルフェーズ。値が低いほど、このコンテナーは早く起動し、遅く停止します。デフォルトは |
次の例に示すように、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 バリアントで使用可能な構成オプションを示しています。
属性 | 説明 |
---|---|
| 指定された |
| JCA |
|
|
| JMS |
| JMS メッセージをリスナーメソッドの引数に変換するための |
| このリスナーの JMS 宛先タイプ: |
| レスポンスの JMS 宛先タイプ: |
| このリスナーコンテナーの JMS クライアント ID。永続サブスクリプションを使用する場合は指定する必要があります。 |
| ネイティブ JMS 確認モード: |
| 受信メッセージごとに XA トランザクションを開始するための Spring |
| リスナーごとに開始する同時セッションまたはコンシューマーの数。最大数を示す単純な数値(たとえば |
| 単一セッションにロードするメッセージの最大数。この数を増やすと、同時消費者が枯渇する可能性があることに注意してください。 |
5. JMX
Spring の JMX(Java Management Extensions)サポートは、Spring アプリケーションを JMX インフラストラクチャに簡単かつ透過的に統合できる機能を提供します。
具体的には、Spring の JMX サポートは 4 つのコア機能を提供します。
Spring Bean の JMX MBean としての自動登録。
Bean の管理インターフェースを制御するための柔軟なメカニズム。
リモート、JSR-160 コネクターを介した MBean の宣言的な公開。
ローカルおよびリモート MBean リソースの単純なプロキシ。
これらの機能は、アプリケーションコンポーネントを Spring または JMX インターフェースおよびクラスに結合することなく機能するように設計されています。実際、ほとんどの場合、Spring JMX 機能を利用するために、アプリケーションクラスは Spring または JMX を認識する必要はありません。
5.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
クラスから継承されたものを除く)は操作として公開されます。
MBeanExporter は Lifecycle Bean です(起動とシャットダウンのコールバックを参照)。デフォルトでは、MBean はアプリケーションのライフサイクル中にできるだけ遅くエクスポートされます。autoStartup フラグを設定することにより、エクスポートが行われる phase を構成したり、自動登録を無効にしたりできます。 |
5.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 実装が必要です。
5.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>
5.1.3. 遅延初期化された MBean
遅延初期化用に構成された MBeanExporter
を使用して Bean を構成する場合、MBeanExporter
はこの契約を破らず、Bean のインスタンス化を回避します。代わりに、プロキシを MBeanServer
に登録し、プロキシで最初の呼び出しが発生するまでコンテナーから Bean の取得を延期します。
5.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
インスタンスの制御で詳述されているように、この動作をオーバーライドできます。
5.1.5. 登録動作の制御
Spring MBeanExporter
が ObjectName
bean:name=testBean1
を使用して MBean
を MBeanServer
に登録しようとするシナリオを考えてみます。MBean
インスタンスが同じ ObjectName
ですでに登録されている場合、デフォルトの動作は失敗します(そして InstanceAlreadyExistsException
をスローします)。
MBean
が MBeanServer
に登録されたときに何が起こるかを正確に制御できます。Spring の JMX サポートでは、MBean
が同じ ObjectName
にすでに登録されていることが登録プロセスで検出された場合、3 つの異なる登録動作により登録動作を制御できます。次の表は、これらの登録動作をまとめたものです。
登録動作 | 説明 |
---|---|
| これがデフォルトの登録動作です。 |
|
|
|
|
上記の表の値は、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>
5.2. Bean の管理インターフェースの制御
前のセクションの例では、Bean の管理インターフェースをほとんど制御できませんでした。エクスポートされた各 Bean の public
プロパティとメソッドはすべて、それぞれ JMX 属性と操作として公開されます。エクスポートされた Bean のどのプロパティとメソッドが実際に JMX 属性と操作として公開されるかをきめ細かく制御するために、Spring JMX は Bean の管理インターフェースを制御するための包括的で拡張可能なメカニズムを提供します。
5.2.1. MBeanInfoAssembler
インターフェースの使用
背後で、MBeanExporter
は org.springframework.jmx.export.assembler.MBeanInfoAssembler
インターフェースの実装に委譲します。org.springframework.jmx.export.assembler.MBeanInfoAssembler
インターフェースは、公開される各 Bean の管理インターフェースを定義するロールを果たします。デフォルトの実装 org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler
は、すべてのパブリックプロパティとメソッドを公開する管理インターフェースを定義します(前のセクションの例で見たように)。Spring は、MBeanInfoAssembler
インターフェースの 2 つの追加の実装を提供します。これにより、ソースレベルのメタデータまたは任意のインターフェースを使用して、生成された管理インターフェースを制御できます。
5.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 のメタデータ駆動型管理インターフェースを利用するために必要なことはこれだけです。
5.2.3. ソースレベルのメタデータ型
次の表は、Spring JMX で使用できるソースレベルのメタデータタイプを示しています。
目的 | アノテーション | アノテーション型 |
---|---|---|
|
| クラス |
メソッドを JMX 操作としてマークします。 |
| メソッド |
getter または setter を JMX 属性の半分としてマークします。 |
| メソッド (getter および setter のみ) |
操作パラメーターの説明を定義します。 |
| メソッド |
次の表に、これらのソースレベルのメタデータタイプで使用できる構成パラメーターを示します。
パラメーター | 説明 | 適用先 |
---|---|---|
| マネージリソースの |
|
| リソース、属性、または操作のわかりやすい説明を設定します。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 操作パラメーターの表示名を設定します。 |
|
| 操作パラメーターのインデックスを設定します。 |
|
5.2.4. AutodetectCapableMBeanInfoAssembler
インターフェースの使用
構成をさらに簡素化するために、Spring には AutodetectCapableMBeanInfoAssembler
インターフェースが含まれています。これは、MBeanInfoAssembler
インターフェースを継承して、MBean リソースの自動検出のサポートを追加します。AutodetectCapableMBeanInfoAssembler
のインスタンスを使用して MBeanExporter
を構成する場合、JMX に公開するために Bean を含めることに「投票」することができます。
AutodetectCapableMBeanInfo
インターフェースの唯一の実装は MetadataMBeanInfoAssembler
であり、ManagedResource
属性でマークされた Bean を含めるために投票します。この場合のデフォルトのアプローチは、Bean 名を ObjectName
として使用することです。これにより、次のような構成になります。
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<!-- notice how no 'beans' are explicitly configured here -->
<property name="autodetect" value="true"/>
<property name="assembler" ref="assembler"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
<property name="attributeSource">
<bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
</property>
</bean>
</beans>
上記の構成では、MBeanExporter
に Bean が渡されないことに注意してください。ただし、JmxTestBean
は ManagedResource
属性でマークされており、MetadataMBeanInfoAssembler
がこれを検出し、それを含めるために投票するため、依然として登録されています。このアプローチの唯一の課題は、JmxTestBean
の名前にビジネス上の意味があることです。 Bean の ObjectName
インスタンスの制御で定義されている ObjectName
作成のデフォルト動作を変更することにより、この課題に対処できます。
5.2.5. Java インターフェースを使用した管理インターフェースの定義
MetadataMBeanInfoAssembler
に加えて、Spring には InterfaceBasedMBeanInfoAssembler
も含まれています。これにより、インターフェースのコレクションで定義されたメソッドのセットに基づいて公開されるメソッドとプロパティを制限できます。
MBean を公開する標準的なメカニズムはインターフェースと単純な命名スキームを使用することですが、InterfaceBasedMBeanInfoAssembler
は命名規則の必要性を削除し、複数のインターフェースを使用できるようにし、Bean が MBean インターフェースを実装する必要性を削除することにより、この機能を継承します。
前に示した JmxTestBean
クラスの管理インターフェースを定義するために使用される次のインターフェースを検討してください。
public interface IJmxTestBean {
public int add(int x, int y);
public long myOperation();
public int getAge();
public void setAge(int age);
public void setName(String name);
public String getName();
}
このインターフェースは、JMX MBean の操作と属性として公開されるメソッドとプロパティを定義します。次のコードは、このインターフェースを管理インターフェースの定義として使用するように Spring JMX を構成する方法を示しています。
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean5" value-ref="testBean"/>
</map>
</property>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
<property name="managedInterfaces">
<value>org.springframework.jmx.IJmxTestBean</value>
</property>
</bean>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
上記の例では、InterfaceBasedMBeanInfoAssembler
は、Bean の管理インターフェースを構築するときに IJmxTestBean
インターフェースを使用するように構成されています。InterfaceBasedMBeanInfoAssembler
によって処理される Bean は、JMX 管理インターフェースの生成に使用されるインターフェースを実装する必要がないことを理解することが重要です。
上記の場合、IJmxTestBean
インターフェースを使用して、すべての Bean のすべての管理インターフェースを構築します。多くの場合、これは望ましい動作ではないため、Bean ごとに異なるインターフェースを使用することをお勧めします。この場合、InterfaceBasedMBeanInfoAssembler
に interfaceMappings
プロパティを介して Properties
インスタンスを渡すことができます。各エントリのキーは Bean 名で、各エントリの値はその Bean に使用するインターフェース名のコンマ区切りリストです。
managedInterfaces
または interfaceMappings
プロパティのいずれかで管理インターフェースが指定されていない場合、InterfaceBasedMBeanInfoAssembler
は Bean に反映し、その Bean によって実装されたすべてのインターフェースを使用して管理インターフェースを作成します。
5.2.6. MethodNameBasedMBeanInfoAssembler
を使用する
MethodNameBasedMBeanInfoAssembler
では、JMX に属性および操作として公開されるメソッド名のリストを指定できます。次のコードはサンプル構成を示しています。
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean5" value-ref="testBean"/>
</map>
</property>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
<property name="managedMethods">
<value>add,myOperation,getName,setName,getAge</value>
</property>
</bean>
</property>
</bean>
前の例では、add
メソッドと myOperation
メソッドが JMX 操作として公開され、getName()
、setName(String)
、getAge()
が JMX 属性の適切な半分として公開されていることがわかります。上記のコードでは、メソッドマッピングは JMX に公開されている Bean に適用されます。Bean ごとにメソッドの公開を制御するには、MethodNameMBeanInfoAssembler
の methodMappings
プロパティを使用して、Bean 名をメソッド名のリストにマップできます。
5.3. Bean の ObjectName
インスタンスの制御
バックグラウンドでは、MBeanExporter
は ObjectNamingStrategy
の実装に委譲して、登録する各 Bean の ObjectName
インスタンスを取得します。デフォルトでは、デフォルトの実装である KeyNamingStrategy
は、beans
Map
のキーを ObjectName
として使用します。さらに、KeyNamingStrategy
は、beans
Map
のキーを Properties
ファイル(または複数のファイル)のエントリにマップして、ObjectName
を解決することができます。KeyNamingStrategy
に加えて、Spring は 2 つの追加の ObjectNamingStrategy
実装を提供します。IdentityNamingStrategy
(Bean の JVM ID に基づいて ObjectName
を構築します)と MetadataNamingStrategy
(ソースレベルのメタデータを使用して ObjectName
を取得します)です。
5.3.1. プロパティからの ObjectName
インスタンスの読み取り
独自の KeyNamingStrategy
インスタンスを構成し、Bean キーを使用するのではなく、Properties
インスタンスから ObjectName
インスタンスを読み取るように構成できます。KeyNamingStrategy
は、Bean キーに対応するキーを使用して、Properties
のエントリを見つけようとします。エントリが見つからない場合、または Properties
インスタンスが null
の場合、Bean キー自体が使用されます。
次のコードは、KeyNamingStrategy
のサンプル構成を示しています。
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="testBean" value-ref="testBean"/>
</map>
</property>
<property name="namingStrategy" ref="namingStrategy"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy">
<property name="mappings">
<props>
<prop key="testBean">bean:name=testBean1</prop>
</props>
</property>
<property name="mappingLocations">
<value>names1.properties,names2.properties</value>
</property>
</bean>
</beans>
上記の例では、KeyNamingStrategy
のインスタンスを、マッピングプロパティで定義された Properties
インスタンスと、マッピングプロパティで定義されたパスにあるプロパティファイルからマージされた Properties
インスタンスで構成します。この構成では、Bean キーに対応するキーを持つ Properties
インスタンスのエントリであるため、testBean
Bean には bean:name=testBean1
の ObjectName
が与えられます。
Properties
インスタンスにエントリが見つからない場合、Bean キー名が ObjectName
として使用されます。
5.3.2. MetadataNamingStrategy
を使用する
MetadataNamingStrategy
は、各 Bean の ManagedResource
属性の objectName
プロパティを使用して、ObjectName
を作成します。次のコードは、MetadataNamingStrategy
の構成を示しています。
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="testBean" value-ref="testBean"/>
</map>
</property>
<property name="namingStrategy" ref="namingStrategy"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
<property name="attributeSource" ref="attributeSource"/>
</bean>
<bean id="attributeSource"
class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
</beans>
objectName
が ManagedResource
属性に提供されていない場合、ObjectName
は次の形式で作成されます: [ 完全修飾パッケージ名 ]:type = [short-classname]、name = [bean-name] 例: 次の Bean に対して生成される ObjectName
は com.example:type=MyClass,name=myBean
になります。
<bean id="myBean" class="com.example.MyClass"/>
5.3.3. アノテーションベースの MBean エクスポートの構成
アノテーションベースのアプローチを使用して管理インターフェースを定義する場合は、MBeanExporter
の便利なサブクラス AnnotationMBeanExporter
を使用できます。このサブクラスのインスタンスを定義する場合、namingStrategy
、assembler
、attributeSource
構成は常に標準の Java アノテーションベースのメタデータを使用するため、必要ありません(自動検出も常に有効になっています)。実際、次の例に示すように、MBeanExporter
Bean を定義するのではなく、さらに単純な構文が @EnableMBeanExport
@Configuration
アノテーションによってサポートされています。
@Configuration
@EnableMBeanExport
public class AppConfig {
}
XML ベースの構成を好む場合、<context:mbean-export/>
要素は同じ目的を果たし、次のリストに表示されます。
<context:mbean-export/>
必要に応じて、特定の MBean server
への参照を提供できます。defaultDomain
属性(AnnotationMBeanExporter
のプロパティ)は、生成された MBean ObjectName
ドメインの代替値を受け入れます。これは、次の例に示すように、前の MetadataNamingStrategy セクションで説明したように、完全修飾パッケージ名の代わりに使用されます。
@EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
@Configuration
ContextConfiguration {
}
次の例は、前述のアノテーションベースの例に相当する XML を示しています。
<context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
Bean クラスで JMX アノテーションの自動検出と組み合わせてインターフェースベースの AOP プロキシを使用しないでください。インターフェースベースのプロキシは、ターゲットクラスを「非表示」にします。これにより、JMX 管理のリソースアノテーションも非表示になります。その場合はターゲットクラスプロキシを使用する必要があります(<aop:config/> 、<tx:annotation-driven/> などで「proxy-target-class」フラグを設定することにより)。そうしないと、JMXBean が起動時にサイレントに無視される可能性があります。 |
5.4. JSR-160 コネクターの使用
リモートアクセスの場合、Spring JMX モジュールは、サーバー側とクライアント側の両方のコネクターを作成するために、org.springframework.jmx.support
パッケージ内に 2 つの FactoryBean
実装を提供します。
5.4.1. サーバー側のコネクター
Spring JMX で JSR-160 JMXConnectorServer
を作成、開始、公開するには、次の構成を使用できます。
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>
デフォルトでは、ConnectorServerFactoryBean
は service:jmx:jmxmp://localhost:9875
にバインドされた JMXConnectorServer
を作成します。serverConnector
Bean は、ローカルホスト、ポート 9875 の JMXMP プロトコルを介して、クライアントにローカル MBeanServer
を公開します。JMXMP プロトコルは、JSR 160 仕様でオプションとしてマークされていることに注意してください。現在、メインのオープンソース JMX 実装である MX4J および JDK で提供されている実装は、JMXMP をサポートしていません。
別の URL を指定し、JMXConnectorServer
自体を MBeanServer
に登録するには、次の例に示すように、それぞれ serviceUrl
および ObjectName
プロパティを使用できます。
<bean id="serverConnector"
class="org.springframework.jmx.support.ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=rmi"/>
<property name="serviceUrl"
value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
</bean>
ObjectName
プロパティが設定されている場合、Spring はその ObjectName
の MBeanServer
にコネクターを自動的に登録します。次の例は、JMXConnector
を作成するときに ConnectorServerFactoryBean
に渡すことができるパラメーターの完全なセットを示しています。
<bean id="serverConnector"
class="org.springframework.jmx.support.ConnectorServerFactoryBean">
<property name="objectName" value="connector:name=iiop"/>
<property name="serviceUrl"
value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
<property name="threaded" value="true"/>
<property name="daemon" value="true"/>
<property name="environment">
<map>
<entry key="someKey" value="someValue"/>
</map>
</property>
</bean>
RMI ベースのコネクターを使用する場合、名前の登録を完了するには、ルックアップサービス(tnameserv
または rmiregistry
)を開始する必要があることに注意してください。Spring を使用して RMI を介してリモートサービスをエクスポートする場合、Spring はすでに RMI レジストリを構築しています。そうでない場合は、次の構成スニペットを使用してレジストリを簡単に開始できます。
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
<property name="port" value="1099"/>
</bean>
5.4.2. クライアント側のコネクター
リモート JSR-160 対応 MBeanServer
に MBeanServerConnection
を作成するには、次の例に示すように、MBeanServerConnectionFactoryBean
を使用できます。
<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
<property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>
5.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 の公式ドキュメントを参照してください。
5.5. プロキシを介した MBean へのアクセス
Spring JMX を使用すると、ローカルまたはリモート MBeanServer
に登録されている MBean に呼び出しを再ルーティングするプロキシを作成できます。これらのプロキシは、MBean とやり取りできる標準の Java インターフェースを提供します。次のコードは、ローカル MBeanServer
で実行されている MBean のプロキシを構成する方法を示しています。
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="bean:name=testBean"/>
<property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
</bean>
上記の例では、bean:name=testBean
の ObjectName
に登録された MBean に対してプロキシが作成されていることがわかります。プロキシが実装するインターフェースのセットは proxyInterfaces
プロパティによって制御され、これらのインターフェースのメソッドとプロパティを MBean の操作と属性にマッピングするルールは、InterfaceBasedMBeanInfoAssembler
で使用されるルールと同じです。
MBeanProxyFactoryBean
は、MBeanServerConnection
を介してアクセス可能な任意の MBean へのプロキシを作成できます。デフォルトでは、ローカル MBeanServer
が検索および使用されますが、これをオーバーライドして、リモート MBeans を指すプロキシに対応するためにリモート MBeanServer
を指す MBeanServerConnection
を提供できます。
<bean id="clientConnector"
class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
<property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/>
</bean>
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="bean:name=testBean"/>
<property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
<property name="server" ref="clientConnector"/>
</bean>
前の例では、MBeanServerConnectionFactoryBean
を使用するリモートマシンを指す MBeanServerConnection
を作成します。この MBeanServerConnection
は、server
プロパティを介して MBeanProxyFactoryBean
に渡されます。作成されたプロキシは、この MBeanServerConnection
を介してすべての呼び出しを MBeanServer
に転送します。
5.6. 通知
Spring の JMX 製品には、JMX 通知の包括的なサポートが含まれています。
5.6.1. 通知用のリスナーの登録
Spring の JMX サポートにより、任意の数の NotificationListeners
を任意の数の MBean に簡単に登録できます(これには、Spring の MBeanExporter
によってエクスポートされた MBean および他のメカニズムを通じて登録された MBean が含まれます)。例: ターゲット MBean の属性が変更されるたびに(Notification
を介して)情報を受け取りたいシナリオを考えます。次の例では、コンソールに通知を書き込みます。
package com.example;
import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
public class ConsoleLoggingNotificationListener
implements NotificationListener, NotificationFilter {
public void handleNotification(Notification notification, Object handback) {
System.out.println(notification);
System.out.println(handback);
}
public boolean isNotificationEnabled(Notification notification) {
return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
}
}
次の例では、ConsoleLoggingNotificationListener
(前の例で定義)を notificationListenerMappings
に追加します。
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListenerMappings">
<map>
<entry key="bean:name=testBean1">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
上記の構成を使用すると、ターゲット MBean(bean:name=testBean1
)から JMX Notification
がブロードキャストされるたびに、notificationListenerMappings
プロパティを介してリスナーとして登録された ConsoleLoggingNotificationListener
Bean に通知されます。ConsoleLoggingNotificationListener
Bean は、Notification
に応じて適切と判断したアクションを実行できます。
次の例に示すように、エクスポートされた Bean とリスナー間のリンクとして、ストレート Bean 名を使用することもできます。
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListenerMappings">
<map>
<entry key="testBean">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
取り囲む MBeanExporter
がエクスポートするすべての Bean に対して単一の NotificationListener
インスタンスを登録する場合、次の例に示すように、notificationListenerMappings
プロパティマップのエントリのキーとして特別なワイルドカード(*
)を使用できます。
<property name="notificationListenerMappings">
<map>
<entry key="*">
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</entry>
</map>
</property>
逆を行う(つまり、MBean に対して多数の個別のリスナーを登録する)必要がある場合は、代わりに(notificationListenerMappings
プロパティよりも) notificationListeners
リストプロパティを使用する必要があります。今回は、単一の MBean に対して NotificationListener
を構成する代わりに、NotificationListenerBean
インスタンスを構成します。NotificationListenerBean
は、NotificationListener
と ObjectName
(または ObjectNames
)をカプセル化し、それが MBeanServer
で登録されます。NotificationListenerBean
は、NotificationFilter
や高度な JMX 通知シナリオで使用できる任意のハンドバックオブジェクトなど、他の多くのプロパティもカプセル化します。
NotificationListenerBean
インスタンスを使用する場合の構成は、次の例に示すように、以前に提示されたものと大きく変わりません。
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="notificationListeners">
<list>
<bean class="org.springframework.jmx.export.NotificationListenerBean">
<constructor-arg>
<bean class="com.example.ConsoleLoggingNotificationListener"/>
</constructor-arg>
<property name="mappedObjectNames">
<list>
<value>bean:name=testBean1</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
上記の例は、最初の通知の例と同等です。それでは、Notification
が発生するたびにハンドバックオブジェクトを与えたいと考え、また NotificationFilter
を供給することで余分な Notifications
を除外したいとします。次の例は、これらのゴールを達成します。
<beans>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean1"/>
<entry key="bean:name=testBean2" value-ref="testBean2"/>
</map>
</property>
<property name="notificationListeners">
<list>
<bean class="org.springframework.jmx.export.NotificationListenerBean">
<constructor-arg ref="customerNotificationListener"/>
<property name="mappedObjectNames">
<list>
<!-- handles notifications from two distinct MBeans -->
<value>bean:name=testBean1</value>
<value>bean:name=testBean2</value>
</list>
</property>
<property name="handback">
<bean class="java.lang.String">
<constructor-arg value="This could be anything..."/>
</bean>
</property>
<property name="notificationFilter" ref="customerNotificationListener"/>
</bean>
</list>
</property>
</bean>
<!-- implements both the NotificationListener and NotificationFilter interfaces -->
<bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>
<bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
<bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="ANOTHER TEST"/>
<property name="age" value="200"/>
</bean>
</beans>
(ハンドバックオブジェクトとは何か、実際には NotificationFilter
とは何かについての詳細は、「JMX 通知モデル」というタイトルの JMX 仕様(1.2)のセクションを参照してください)
5.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 の両方への結合を受け入れることができる場合は、そうしてください。
5.7. その他のリソース
このセクションには、JMX に関するその他のリソースへのリンクが含まれています。
Oracle の JMX ホームページ: Oracle (英語) 。
JMX 仕様 (英語) (JSR-000003)。
JMX リモート API 仕様 (英語) (JSR-000160)。
MX4J ホームページ (英語) 。(MX4J は、さまざまな JMX 仕様のオープンソース実装です。)
6. メール
このセクションでは、Spring Framework を使用してメールを送信する方法について説明します。
Spring Framework は、メールを送信するための有用なユーティリティライブラリを提供します。このライブラリは、基礎となるメールシステムの詳細からユーザーを保護し、クライアントに代わって低レベルのリソース処理を行います。
org.springframework.mail
パッケージは、Spring Framework のメールサポートのルートレベルパッケージです。メールを送信するための中心的なインターフェースは、MailSender
インターフェースです。from
や to
(その他多数)などの単純なメールのプロパティをカプセル化する単純な値オブジェクトは、SimpleMailMessage
クラスです。このパッケージには、チェック例外の階層も含まれており、下位レベルのメールシステム例外よりも高いレベルの抽象化を提供します。ルート例外は MailException
です。リッチメールの例外階層の詳細については、 javadoc を参照してください。
org.springframework.mail.javamail.JavaMailSender
インターフェースは、MIME メッセージのサポートなどの特殊な JavaMail 機能を(継承元の) MailSender
インターフェースに追加します。JavaMailSender
は、MimeMessage
を準備するための org.springframework.mail.javamail.MimeMessagePreparator
と呼ばれるコールバックインターフェースも提供します。
6.1. 使用方法
次の例に示すように、OrderManager
というビジネスインターフェースがあると仮定します。
public interface OrderManager {
void placeOrder(Order order);
}
さらに、オーダー番号付きのメールメッセージを生成し、関連するオーダーを行った顧客に送信する必要があるという要件があると仮定します。
6.1.1. MailSender
および SimpleMailMessage
の基本的な使用箇所
次の例は、MailSender
および SimpleMailMessage
を使用して、誰かがオーダーしたときにメールを送信する方法を示しています。
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
public class SimpleOrderManager implements OrderManager {
private MailSender mailSender;
private SimpleMailMessage templateMessage;
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
}
public void setTemplateMessage(SimpleMailMessage templateMessage) {
this.templateMessage = templateMessage;
}
public void placeOrder(Order order) {
// Do the business calculations...
// Call the collaborators to persist the order...
// Create a thread safe "copy" of the template message and customize it
SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
msg.setTo(order.getCustomer().getEmailAddress());
msg.setText(
"Dear " + order.getCustomer().getFirstName()
+ order.getCustomer().getLastName()
+ ", thank you for placing order. Your order number is "
+ order.getOrderNumber());
try{
this.mailSender.send(msg);
}
catch (MailException ex) {
// simply log it and go on...
System.err.println(ex.getMessage());
}
}
}
次の例は、前述のコードの Bean 定義を示しています。
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="mail.mycompany.example"/>
</bean>
<!-- this is a template message that we can pre-load with default state -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
<property name="from" value="[email protected] (英語) "/>
<property name="subject" value="Your order"/>
</bean>
<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
<property name="mailSender" ref="mailSender"/>
<property name="templateMessage" ref="templateMessage"/>
</bean>
6.1.2. JavaMailSender
および MimeMessagePreparator
の使用
このセクションでは、MimeMessagePreparator
コールバックインターフェースを使用する OrderManager
の別の実装について説明します。次の例では、mailSender
プロパティのタイプは JavaMailSender
であるため、JavaMail MimeMessage
クラスを使用できます。
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessagePreparator;
public class SimpleOrderManager implements OrderManager {
private JavaMailSender mailSender;
public void setMailSender(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
public void placeOrder(final Order order) {
// Do the business calculations...
// Call the collaborators to persist the order...
MimeMessagePreparator preparator = new MimeMessagePreparator() {
public void prepare(MimeMessage mimeMessage) throws Exception {
mimeMessage.setRecipient(Message.RecipientType.TO,
new InternetAddress(order.getCustomer().getEmailAddress()));
mimeMessage.setFrom(new InternetAddress("[email protected] (英語) "));
mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
order.getCustomer().getLastName() + ", thanks for your order. " +
"Your order number is " + order.getOrderNumber() + ".");
}
};
try {
this.mailSender.send(preparator);
}
catch (MailException ex) {
// simply log it and go on...
System.err.println(ex.getMessage());
}
}
}
メールコードは分野横断的な問題であり、カスタム Spring AOP アスペクトにリファクタリングする候補になる可能性があります。これは、OrderManager ターゲットの適切なジョインポイントで実行できます。 |
Spring Framework のメールサポートは、標準の JavaMail 実装に付属しています。詳細については、関連する javadoc を参照してください。
6.2. JavaMail MimeMessageHelper
の使用
JavaMail メッセージを処理するときに非常に便利なクラスは org.springframework.mail.javamail.MimeMessageHelper
です。これにより、冗長な JavaMail API を使用する必要がなくなります。MimeMessageHelper
を使用すると、次の例に示すように、MimeMessage
を簡単に作成できます。
// of course you would use DI in any real-world cases
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setTo("[email protected] (英語) ");
helper.setText("Thank you for ordering!");
sender.send(message);
6.2.1. 添付ファイルとインラインリソースの送信
マルチパートメールメッセージでは、添付ファイルとインラインリソースの両方を使用できます。インラインリソースの例には、メッセージで使用したいが添付ファイルとして表示したくないイメージまたはスタイルシートが含まれます。
添付
次の例は、MimeMessageHelper
を使用して、単一の JPEG イメージが添付されたメールを送信する方法を示しています。
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected] (英語) ");
helper.setText("Check out this image!");
// let's attach the infamous windows Sample file (this time copied to c:/)
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addAttachment("CoolImage.jpg", file);
sender.send(message);
インラインリソース
次の例は、MimeMessageHelper
を使用して、インラインイメージを含むメールを送信する方法を示しています。
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");
MimeMessage message = sender.createMimeMessage();
// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[email protected] (英語) ");
// use the true flag to indicate the text included is HTML
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);
// let's include the infamous windows Sample file (this time copied to c:/)
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addInline("identifier1234", res);
sender.send(message);
インラインリソースは、指定された Content-ID (上記の例では identifier1234 )を使用して MimeMessage に追加されます。テキストとリソースを追加する順序は非常に重要です。最初にテキストを追加してから、リソースを追加してください。逆にそれをやっていると、うまくいきません。 |
6.2.2. テンプレートライブラリを使用してメールコンテンツを作成する
前のセクションで示した例のコードは、message.setText(..)
などのメソッド呼び出しを使用して、メールメッセージのコンテンツを明示的に作成しました。これは単純なケースでは問題ありませんが、前述の例のコンテキストでは問題ありません。この例では、API の基本を説明することを目的としています。
ただし、一般的なエンタープライズアプリケーションでは、多くの場合、開発者は前述のアプローチを使用してメールメッセージのコンテンツを作成しません。
Java コードで HTML ベースのメールコンテンツを作成するのは面倒でエラーが発生しやすくなります。
表示ロジックとビジネスロジックの間には明確な分離はありません。
メールコンテンツの表示構造を変更するには、Java コードの記述、再コンパイル、再デプロイなどが必要です。
通常、これらの課題に対処するために取られるアプローチは、テンプレートライブラリ(FreeMarker など)を使用してメールコンテンツの表示構造を定義することです。これにより、コードは、メールテンプレートでレンダリングされるデータの作成とメールの送信のみに任されます。メールメッセージの内容がやや複雑になる場合は間違いなくベストプラクティスであり、Spring Framework の FreeMarker のサポートクラスを使用すると、非常に簡単になります。
7. タスクの実行とスケジューリング
Spring Framework は、それぞれ TaskExecutor
および TaskScheduler
インターフェースを使用したタスクの非同期実行およびスケジューリングの抽象化を提供します。Spring は、アプリケーションサーバー環境内の CommonJ へのスレッドプールまたは委譲をサポートするこれらのインターフェースの実装も備えています。最終的に、共通インターフェースの背後にあるこれらの実装を使用することで、Java SE 5、Java SE 6、Java EE 環境の違いが抽象化されます。
Spring は、Timer
(1.3 以降の JDK の一部)および Quartz スケジューラー( https://www.quartz-scheduler.org/ (英語) )によるスケジューリングをサポートする統合クラスも備えています。FactoryBean
と Timer
または Trigger
インスタンスへのオプションの参照をそれぞれ使用することにより、これらのスケジューラーを両方ともセットアップできます。さらに、Quartz スケジューラと Timer
の両方の便利なクラスを使用して、既存のターゲットオブジェクトのメソッドを呼び出すことができます(通常の MethodInvokingFactoryBean
操作に類似)。
7.1. Spring TaskExecutor
の抽象化
エグゼキュータは、スレッドプールの概念の JDK 名です。「executor」の命名は、基礎となる実装が実際にプールであるという保証がないという事実によるものです。エグゼキュータはシングルスレッドでも同期でもかまいません。Spring の抽象化により、Java SE 環境と Java EE 環境の間で実装の詳細が隠されます。
Spring の TaskExecutor
インターフェースは、java.util.concurrent.Executor
インターフェースと同一です。実際、元々、存在する主な理由は、スレッドプールを使用するときに Java 5 の必要性を抽象化することでした。インターフェースには、スレッドプールのセマンティクスと設定に基づいて実行するタスクを受け入れる単一のメソッド(execute(Runnable task)
)があります。
TaskExecutor
はもともと、他の Spring コンポーネントに、必要に応じてスレッドプーリングの抽象化を提供するために作成されました。ApplicationEventMulticaster
、JMS の AbstractMessageListenerContainer
、Quartz 統合などのコンポーネントはすべて、TaskExecutor
抽象化を使用してスレッドをプールします。ただし、Bean がスレッドプーリング動作を必要とする場合、この抽象化を独自のニーズに使用することもできます。
7.1.1. TaskExecutor
タイプ
Spring には、TaskExecutor
の事前構築済み実装が多数含まれています。おそらく、自分で実装する必要はないはずです。Spring が提供するバリアントは次のとおりです。
SyncTaskExecutor
: この実装では、呼び出しを非同期で実行しません。代わりに、各呼び出しは呼び出しスレッドで行われます。これは主に、単純なテストケースなど、マルチスレッドが不要な状況で使用されます。SimpleAsyncTaskExecutor
: この実装は、スレッドを再利用しません。むしろ、呼び出しごとに新しいスレッドを起動します。ただし、スロットが解放されるまで、制限を超える呼び出しをブロックする同時実行制限はサポートします。真のプーリングを探している場合は、このリストの後半のThreadPoolTaskExecutor
を参照してください。ConcurrentTaskExecutor
: この実装は、java.util.concurrent.Executor
インスタンス用のアダプターです。Executor
構成パラメーターを Bean プロパティとして公開する代替手段(ThreadPoolTaskExecutor
)があります。ConcurrentTaskExecutor
を直接使用する必要はほとんどありません。ただし、ThreadPoolTaskExecutor
がニーズに対して十分な柔軟性がない場合は、ConcurrentTaskExecutor
が代替です。ThreadPoolTaskExecutor
: この実装が最も一般的に使用されます。java.util.concurrent.ThreadPoolExecutor
を構成するための Bean プロパティを公開し、TaskExecutor
にラップします。別の種類のjava.util.concurrent.Executor
に適応する必要がある場合は、代わりにConcurrentTaskExecutor
を使用することをお勧めします。WorkManagerTaskExecutor
: この実装は、そのバッキングサービスプロバイダーとして CommonJWorkManager
を使用し、Spring アプリケーションコンテキスト内の WebLogic または WebSphere で CommonJ ベースのスレッドプール統合をセットアップするための中心的な便利なクラスです。DefaultManagedTaskExecutor
: この実装では、JSR-236 互換のランタイム環境(Java EE 7+ アプリケーションサーバーなど)で JNDI が取得したManagedExecutorService
を使用し、そのために CommonJ WorkManager を置き換えます。
7.1.2. TaskExecutor
を使用する
Spring の TaskExecutor
実装は、単純な JavaBeans として使用されます。次の例では、ThreadPoolTaskExecutor
を使用して一連のメッセージを非同期に出力する Bean を定義します。
import org.springframework.core.task.TaskExecutor;
public class TaskExecutorExample {
private class MessagePrinterTask implements Runnable {
private String message;
public MessagePrinterTask(String message) {
this.message = message;
}
public void run() {
System.out.println(message);
}
}
private TaskExecutor taskExecutor;
public TaskExecutorExample(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void printMessages() {
for(int i = 0; i < 25; i++) {
taskExecutor.execute(new MessagePrinterTask("Message" + i));
}
}
}
ご覧のように、プールからスレッドを取得して自分で実行するのではなく、Runnable
をキューに追加します。次に、TaskExecutor
はその内部ルールを使用して、タスクが実行されるタイミングを決定します。
TaskExecutor
が使用するルールを構成するには、簡単な Bean プロパティを公開します。
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5"/>
<property name="maxPoolSize" value="10"/>
<property name="queueCapacity" value="25"/>
</bean>
<bean id="taskExecutorExample" class="TaskExecutorExample">
<constructor-arg ref="taskExecutor"/>
</bean>
7.2. Spring TaskScheduler
の抽象化
TaskExecutor
抽象化に加えて、Spring 3.0 は、将来のある時点で実行するタスクをスケジュールするためのさまざまな方法を備えた TaskScheduler
を導入しました。次のリストは、TaskScheduler
インターフェース定義を示しています。
public interface TaskScheduler {
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture schedule(Runnable task, Date startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, long period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay);
}
最も単純な方法は、Runnable
と Date
のみを使用する schedule
という名前の方法です。これにより、指定した時間後にタスクが 1 回実行されます。他のすべての方法では、タスクを繰り返し実行するようにスケジュールできます。固定レートおよび固定遅延メソッドは、単純な定期的な実行用ですが、Trigger
を受け入れるメソッドははるかに柔軟です。
7.2.1. Trigger
インターフェース
Trigger
インターフェースは基本的に、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 run periodically
}
固定レートの実行が必要な場合は、アノテーション内で指定されたプロパティ名を変更できます。次のメソッドは、5 秒ごとに呼び出されます(各呼び出しの連続する開始時間の間で測定されます)。
@Scheduled(fixedRate=5000)
public void doSomething() {
// something that should run periodically
}
固定遅延タスクと固定レートタスクの場合、次の fixedRate
の例に示すように、メソッドの最初の実行まで待機するミリ秒数を指定することにより、初期遅延を指定できます。
@Scheduled(initialDelay=1000, fixedRate=5000)
public void doSomething() {
// something that should run periodically
}
単純な定期的なスケジューリングでは表現力が不十分な場合は、 cron 式を指定できます。次の例は平日のみ実行されます。
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should run on weekdays only
}
zone 属性を使用して、cron 式が解決されるタイムゾーンを指定することもできます。 |
スケジュールするメソッドには void リターンが必要であり、引数を期待してはならないことに注意してください。メソッドがアプリケーションコンテキストから他のオブジェクトと対話する必要がある場合、それらは通常、依存性注入によって提供されます。
Spring Framework 4.3 以降、 実行時に同じ |
7.3.3. @Async
アノテーション
メソッドに @Async
アノテーションを付けて、そのメソッドの呼び出しが非同期に行われるようにすることができます。つまり、呼び出し元は呼び出し時にすぐに戻りますが、メソッドの実際の実行は Spring TaskExecutor
に送信されたタスクで発生します。最も単純なケースでは、次の例に示すように、void
を返すメソッドにアノテーションを適用できます。
@Async
void doSomething() {
// this will be run asynchronously
}
@Scheduled
アノテーションが付けられたメソッドとは異なり、これらのメソッドは、コンテナーによって管理されているスケジュールされたタスクからではなく、実行時に呼び出し元によって「通常」の方法で呼び出されるため、引数を期待できます。例: 次のコードは、@Async
アノテーションの正当なアプリケーションです。
@Async
void doSomething(String s) {
// this will be run asynchronously
}
値を返すメソッドでさえ非同期に呼び出すことができます。ただし、そのようなメソッドには Future
-typed 戻り値が必要です。これにより、非同期実行の利点が得られるため、呼び出し元は、Future
で get()
を呼び出す前に他のタスクを実行できます。次の例は、値を返すメソッドで @Async
を使用する方法を示しています。
@Async
Future<String> returnSomething(int i) {
// this will be run asynchronously
}
@Async メソッドは、通常の java.util.concurrent.Future 戻り値型を宣言するだけでなく、Spring の org.springframework.util.concurrent.ListenableFuture を宣言することもできます。Spring 4.2 の時点では、JDK 8 の java.util.concurrent.CompletableFuture を使用して、非同期タスクとのやり取りを増やしたり、追加の処理ステップを使用して即座に構成したりできます。 |
@Async
を @PostConstruct
などのライフサイクルコールバックと組み合わせて使用することはできません。Spring Bean を非同期的に初期化するには、現在、次の例に示すように、個別の初期化 Spring Bean を使用して、ターゲットで @Async
アノテーション付きメソッドを呼び出す必要があります。
public class SampleBeanImpl implements SampleBean {
@Async
void doSomething() {
// ...
}
}
public class SampleBeanInitializer {
private final SampleBean bean;
public SampleBeanInitializer(SampleBean bean) {
this.bean = bean;
}
@PostConstruct
public void initialize() {
bean.doSomething();
}
}
@Async に相当する直接的な XML はありません。そのようなメソッドは、非同期的に実行するために外部で再宣言するのではなく、最初は非同期実行用に設計する必要があるためです。ただし、Spring AOP を使用して Spring の AsyncExecutionInterceptor をカスタムポイントカットと組み合わせて手動でセットアップできます。 |
7.3.4. @Async
によるエグゼキューター修飾
デフォルトでは、メソッドで @Async
を指定する場合、使用されるエグゼキューターは、非同期サポートを有効にするときに構成されたエグゼキューター、つまり XML または AsyncConfigurer
実装を使用している場合は「アノテーション駆動型」要素です。ただし、特定のメソッドの実行時にデフォルト以外のエグゼキューターを使用する必要があることを示す必要がある場合は、@Async
アノテーションの value
属性を使用できます。次の例は、その方法を示しています。
@Async("otherExecutor")
void doSomething(String s) {
// this will be run asynchronously by "otherExecutor"
}
この場合、"otherExecutor"
は、Spring コンテナー内の Executor
Bean の名前にすることも、Executor
に関連付けられた修飾子の名前にすることもできます(たとえば、<qualifier>
エレメントまたは Spring の @Qualifier
アノテーションで指定)。
7.3.5. @Async
を使用した例外管理
@Async
メソッドに Future
-typed 戻り値がある場合、メソッド実行中にスローされた例外を管理するのは簡単です。この例外は、Future
の結果で get
を呼び出すときにスローされるためです。ただし、void
戻り型では、例外はキャッチされず、送信できません。このような例外を処理する AsyncUncaughtExceptionHandler
を提供できます。次の例は、その方法を示しています。
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// handle exception
}
}
デフォルトでは、例外は単にログに記録されます。AsyncConfigurer
または <task:annotation-driven/>
XML エレメントを使用して、カスタム AsyncUncaughtExceptionHandler
を定義できます。
7.4. task
名前空間
バージョン 3.0 の時点で、Spring には TaskExecutor
および TaskScheduler
インスタンスを構成するための XML 名前空間が含まれています。また、トリガーを使用してスケジュールされるタスクを構成する便利な方法も提供します。
7.4.1. 「スケジューラー」要素
次の要素は、指定されたスレッドプールサイズで ThreadPoolTaskScheduler
インスタンスを作成します。
<task:scheduler id="scheduler" pool-size="10"/>
id
属性に指定された値は、プール内のスレッド名のプレフィックスとして使用されます。scheduler
要素は比較的簡単です。pool-size
属性を指定しない場合、デフォルトのスレッドプールには単一のスレッドしかありません。スケジューラの他の構成オプションはありません。
7.4.2. executor
要素
以下は、ThreadPoolTaskExecutor
インスタンスを作成します。
<task:executor id="executor" pool-size="10"/>
前のセクションで示したスケジューラーと同様に、id
属性に指定された値は、プール内のスレッド名のプレフィックスとして使用されます。プールサイズに関する限り、executor
要素は scheduler
要素よりも多くの構成オプションをサポートしています。ひとつには、ThreadPoolTaskExecutor
のスレッドプール自体がより構成可能です。エグゼキュータのスレッドプールは、単一のサイズではなく、コアと最大サイズに異なる値を設定できます。単一の値を指定すると、executor には固定サイズのスレッドプールがあります(コアサイズと最大サイズは同じです)。ただし、executor
要素の pool-size
属性は、min-max
の形式の範囲も受け入れます。次の例では、5
の最小値と 25
の最大値を設定します。
<task:executor
id="executorWithPoolSizeRange"
pool-size="5-25"
queue-capacity="100"/>
上記の構成では、queue-capacity
値も提供されています。スレッドプールの構成も、エグゼキューターのキュー容量を考慮して考慮する必要があります。プールサイズとキュー容量の関連の詳細については、ThreadPoolExecutor
(標準 Javadoc) のドキュメントを参照してください。主なアイデアは、タスクが送信されると、アクティブなスレッドの数が現在コアサイズより少ない場合、エグゼキューターは最初に空きスレッドを使用しようとすることです。コアサイズに達した場合、その容量にまだ達していない限り、タスクはキューに追加されます。そのときだけ、キューの容量に達した場合、エグゼキューターはコアサイズを超える新しいスレッドを作成します。最大サイズにも達している場合、エグゼキューターはタスクを拒否します。
デフォルトでは、キューは無制限ですが、プールスレッドがすべてビジーである間に十分なタスクがキューに追加されると 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"/>
スケジューラーは外部要素によって参照され、個々のタスクにはトリガーメタデータの構成が含まれます。前の例では、そのメタデータは、各タスクの実行が完了した後に待機するミリ秒数を示す固定遅延を伴う定期的なトリガーを定義します。もう 1 つのオプションは fixed-rate
で、以前の実行にかかる時間に関係なく、メソッドを実行する頻度を示します。さらに、fixed-delay
タスクと fixed-rate
タスクの両方で、メソッドの最初の実行まで待機するミリ秒数を示す「initial-delay」パラメーターを指定できます。より詳細に制御するために、代わりに cron
属性を指定して 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. cron 式
すべての Spring cron 式は、@Scheduled
アノテーション、task:scheduled-tasks
要素、またはその他の場所で使用しているかどうかに関係なく、同じ形式に準拠する必要があります。* * * * * *
などの整形式の cron 式は、スペースで区切られた 6 つの時刻フィールドと日付フィールドで構成され、それぞれに有効な値の範囲があります。
┌───────────── second (0-59) │ ┌───────────── minute (0 - 59) │ │ ┌───────────── hour (0 - 23) │ │ │ ┌───────────── day of the month (1 - 31) │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC) │ │ │ │ │ ┌───────────── day of the week (0 - 7) │ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN) │ │ │ │ │ │ * * * * * *
適用されるいくつかのルールがあります。
フィールドはアスタリスク(
*
)である場合があり、これは常に「最初から最後」を表します。曜日または曜日のフィールドには、アスタリスクの代わりに疑問符(?
)を使用できます。カンマ(
,
)は、リストの項目を区切るために使用されます。ハイフン(
-
)で区切られた 2 つの数値は、数値の範囲を表します。指定された範囲は包括的です。/
を使用して範囲(または*
)を追跡すると、範囲内の数値の値の間隔が指定されます。英語の名前は、曜日および曜日のフィールドにも使用できます。特定の日または月の最初の 3 文字を使用します(大文字と小文字は関係ありません)。
曜日フィールドと曜日フィールドには、異なる意味を持つ
L
文字を含めることができます。日フィールドでは、
L
は月の最後の日を表します。負のオフセット(つまり、L-n
)が後に続く場合、n
の月の最後の日を意味します。曜日フィールドでは、
L
は週の最後の日を表します。接頭辞として数字または 3 文字の名前(dL
またはDDDL
)が付いている場合は、その月の最後の曜日(d
またはDDD
)を意味します。
日フィールドは
nW
にすることができます。これは、月のn
に最も近い曜日を表します。n
が土曜日に当たる場合、これはその前の金曜日になります。n
が日曜日に該当する場合、これは翌月曜日になります。これは、n
が1
であり、土曜日に該当する場合にも発生します(つまり、1W
は月の最初の平日を表します)。日フィールドが
LW
の場合、その月の最後の平日を意味します。曜日フィールドは、
d#n
(またはDDD#n
)にすることができます。これは、月のn
番目の曜日d
(またはDDD
)を表します。
ここではいくつかの例を示します。
cron 式 | 意味 |
---|---|
| 毎日の毎時のトップ |
| 10 秒ごと |
| 毎日の 8, 9 および 10 時 |
| 6:00 AM および 7:00PM を毎日 |
| 8:00, 8:30, 9:00, 9:30, 10:00 および 10:30 毎日 |
| 平日の 9 時から 5 時 |
| 毎年クリスマスの真夜中 |
| 月の最終日深夜 |
| 月の最後から 3 番目の深夜 |
| 月の最終金曜日の深夜 |
| 月の最終木曜日の深夜 |
| 月の最初の平日深夜 |
| 月の最後の平日深夜 |
| 月の第 2 金曜日の深夜 |
| 月の最初の月曜日の深夜 |
7.5.1. マクロ
0 0 * * * *
などの式は、人間が解析するのが難しいため、バグが発生した場合に修正するのが困難です。読みやすさを向上させるために、Spring は、一般的に使用されるシーケンスを表す次のマクロをサポートしています。6 桁の値の代わりにこれらのマクロを使用できます。つまり、@Scheduled(cron = "@hourly")
です。
マクロ | 意味 |
---|---|
| 一年に一度 ( |
| 1 ヶ月に 1 回 ( |
| 1 週間に 1 回 ( |
| 1 日 1 回( |
| 1 時間に 1 回 ( |
7.6. Quartz スケジューラーの使用
Quartz は、Trigger
、Job
、JobDetail
オブジェクトを使用して、あらゆる種類のジョブのスケジューリングを実現します。Quartz の背後にある基本的な概念については、https://www.quartz-scheduler.org/ (英語) を参照してください。便宜上、Spring は、Spring ベースのアプリケーション内での Quartz の使用を簡素化するいくつかのクラスを提供します。
7.6.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.6.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.6.3. トリガーと SchedulerFactoryBean
を使用してジョブを結び付ける
ジョブの詳細とジョブを作成しました。また、特定のオブジェクトでメソッドを呼び出すことができる便利な Bean を確認しました。もちろん、ジョブ自体をスケジュールする必要があります。これは、トリガーと SchedulerFactoryBean
を使用して行われます。Quartz 内ではいくつかのトリガーを使用できます。Spring は、CronTriggerFactoryBean
と SimpleTriggerFactoryBean
の便利なデフォルトを備えた 2 つの Quartz FactoryBean
実装を提供します。
トリガーをスケジュールする必要があります。Spring は、プロパティとして設定されるトリガーを公開する SchedulerFactoryBean
を提供します。SchedulerFactoryBean
は、これらのトリガーを使用して実際のジョブをスケジュールします。
次のリストでは、SimpleTriggerFactoryBean
と CronTriggerFactoryBean
の両方を使用しています。
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<!-- see the example of method invoking job above -->
<property name="jobDetail" ref="jobDetail"/>
<!-- 10 seconds -->
<property name="startDelay" value="10000"/>
<!-- repeat every 50 seconds -->
<property name="repeatInterval" value="50000"/>
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="exampleJob"/>
<!-- run every morning at 6 AM -->
<property name="cronExpression" value="0 0 6 * * ?"/>
</bean>
上記の例では、2 つのトリガーをセットアップします。1 つは 50 秒ごとに実行され、開始遅延は 10 秒で、もう 1 つは毎朝午前 6 時に実行されます。すべてをファイナライズするには、次の例に示すように、SchedulerFactoryBean
をセットアップする必要があります。
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger"/>
<ref bean="simpleTrigger"/>
</list>
</property>
</bean>
SchedulerFactoryBean
には、ジョブの詳細で使用されるカレンダー、Quartz をカスタマイズするためのプロパティなど、その他のプロパティを使用できます。詳細については、SchedulerFactoryBean
(Javadoc) javadoc を参照してください。
8. キャッシュの抽象化
バージョン 3.1 以降、Spring Framework は既存の Spring アプリケーションに透過的にキャッシュを追加するためのサポートを提供します。トランザクションのサポートと同様に、キャッシングの抽象化により、コードへの影響を最小限に抑えながら、さまざまなキャッシングソリューションを一貫して使用できます。
Spring 4.1 以降、キャッシュの抽象化は JSR-107 アノテーションと、より多くのカスタマイズオプションのサポートにより大幅に拡張されました。
8.1. キャッシュの抽象化について
コアでは、キャッシュの抽象化は Java メソッドにキャッシングを適用するため、キャッシュで利用可能な情報に基づいて実行回数を減らします。つまり、ターゲットとなるメソッドが呼び出されるたびに、抽象化により、指定された引数に対してメソッドがすでに呼び出されているかどうかをチェックするキャッシュ動作が適用されます。呼び出された場合、実際のメソッドを呼び出さなくても、キャッシュされた結果が返されます。メソッドが呼び出されていない場合は呼び出され、結果がキャッシュされてユーザーに返されるため、次にメソッドが呼び出されたときに、キャッシュされた結果が返されます。このように、特定のパラメーターセットに対して(CPU または IO に制限された)高負荷なメソッドを 1 回だけ呼び出すことができ、その結果は、メソッドを実際に再度呼び出すことなく再利用できます。キャッシングロジックは、呼び出し側への干渉なしに透過的に適用されます。
このアプローチは、呼び出された回数に関係なく、特定の入力(または引数)に対して同じ出力(結果)を返すことが保証されているメソッドに対してのみ機能します。 |
キャッシュの抽象化は、キャッシュのコンテンツを更新したり、1 つまたはすべてのエントリを削除したりする機能など、他のキャッシュ関連の操作を提供します。これらは、アプリケーションの過程で変化する可能性のあるデータをキャッシュが処理する場合に役立ちます。
Spring Framework の他のサービスと同様に、キャッシングサービスは抽象化(キャッシュ実装ではない)であり、キャッシュデータを保存するために実際のストレージを使用する必要があります。つまり、抽象化によりキャッシングロジックを記述する必要がなくなりますが、実際のデータストアを提供します。この抽象化は、org.springframework.cache.Cache
および org.springframework.cache.CacheManager
インターフェースによって実現されます。
Spring は、その抽象化のいくつかの実装を提供します。JDK java.util.concurrent.ConcurrentMap
ベースのキャッシュ、Ehcache 2.x (英語) 、Gemfire キャッシュ、Caffeine: GitHub (英語) 、JSR-107 準拠のキャッシュ(Ehcache 3.x など)です。他のキャッシュストアとプロバイダーのプラグインの詳細については、異なるバックエンドキャッシュのプラグインを参照してください。
キャッシング抽象化には、マルチスレッドおよびマルチプロセス環境向けの特別な処理はありません。そのような機能はキャッシュ実装によって処理されるためです。 |
マルチプロセス環境(つまり、複数のノードにデプロイされたアプリケーション)がある場合、それに応じてキャッシュプロバイダーを構成する必要があります。ユースケースによっては、複数のノード上の同じデータのコピーで十分な場合があります。ただし、アプリケーションの実行中にデータを変更する場合は、他の伝播メカニズムを有効にする必要があります。
特定のアイテムをキャッシュすることは、プログラムによるキャッシュのやり取りで見つかった一般的な get-if-not-found-then-proced-and-put-putally 最終的にコードブロックに直接相当します。ロックは適用されず、複数のスレッドが同じアイテムを同時にロードしようとする場合があります。同じことが追い出しにも当てはまります。複数のスレッドがデータを同時に更新または削除しようとしている場合、古いデータを使用する可能性があります。特定のキャッシュプロバイダーは、その領域で高度な機能を提供します。詳細については、キャッシュプロバイダーのドキュメントを参照してください。
キャッシュの抽象化を使用するには、次の 2 つの側面に注意する必要があります。
キャッシュ宣言: キャッシュする必要があるメソッドとそのポリシーを特定します。
キャッシュ構成: データが格納され、そこから読み取られるバッキングキャッシュ。
8.2. 宣言的なアノテーションベースのキャッシュ
キャッシュ宣言のために、Spring のキャッシュ抽象化は Java アノテーションのセットを提供します:
@Cacheable
: キャッシュ作成をトリガーします。@CacheEvict
: キャッシュエビクションをトリガーします。@CachePut
: メソッドの実行を妨げることなくキャッシュを更新します。@Caching
: メソッドに適用される複数のキャッシュ操作を再グループ化します。@CacheConfig
: クラスレベルでいくつかの一般的なキャッシュ関連の設定を共有します。
8.2.1. @Cacheable
アノテーション
名前が示すように、@Cacheable
を使用して、キャッシュ可能なメソッド、つまり、結果がキャッシュに保存されるメソッドを区別できます。これにより、後続の呼び出し(同じ引数を使用)で、キャッシュ内の値は返されません。実際にメソッドを呼び出す必要があります。次の例に示すように、最も簡単な形式では、アノテーション宣言にはアノテーション付きメソッドに関連付けられたキャッシュの名前が必要です。
@Cacheable("books")
public Book findBook(ISBN isbn) {...}
上記のスニペットでは、findBook
メソッドは books
という名前のキャッシュに関連付けられています。メソッドが呼び出されるたびに、キャッシュがチェックされ、呼び出しがすでに実行されており、繰り返す必要がないかどうかが確認されます。ほとんどの場合、宣言されるキャッシュは 1 つだけですが、アノテーションでは複数の名前を指定できるため、複数のキャッシュが使用されます。この場合、メソッドを呼び出す前に各キャッシュがチェックされます。少なくとも 1 つのキャッシュがヒットした場合、関連する値が返されます。
キャッシュされたメソッドが実際に呼び出されなかった場合でも、値を含まない他のすべてのキャッシュも更新されます。 |
次の例では、findBook
メソッドで @Cacheable
を使用しています。
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}
デフォルトのキー生成
キャッシュは基本的にキーと値のストアであるため、キャッシュされたメソッドの各呼び出しは、キャッシュアクセスに適したキーに変換する必要があります。キャッシングの抽象化では、次のアルゴリズムに基づいた単純な KeyGenerator
を使用します。
パラメーターが指定されていない場合は、
SimpleKey.EMPTY
を返します。パラメーターが 1 つだけ指定されている場合、そのインスタンスを返します。
複数のパラメーターが指定されている場合、すべてのパラメーターを含む
SimpleKey
を返します。
パラメーターが自然キーを持ち、有効な hashCode()
および equals()
メソッドを実装している限り、このアプローチはほとんどのユースケースでうまく機能します。そうでない場合は、戦略を変更する必要があります。
別のデフォルトキージェネレーターを提供するには、org.springframework.cache.interceptor.KeyGenerator
インターフェースを実装する必要があります。
デフォルトの鍵生成戦略は、Spring 4.0 のリリースで変更されました。Spring の以前のバージョンでは、複数のキーパラメーターに対して、パラメーターの 以前のキー戦略を引き続き使用する場合は、非推奨の |
カスタムキー生成宣言
キャッシングは汎用的であるため、ターゲットメソッドには、キャッシュ構造の上に簡単にマッピングできないさまざまなシグネチャーが含まれている可能性が非常に高くなります。これは、ターゲットメソッドに複数の引数があり、そのうちいくつかのみがキャッシュに適している場合に明らかになります(残りはメソッドロジックによってのみ使用されます)。次の例を考えてみましょう。
@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 以降、キャッシュのアノテーションの
|
同期キャッシング
マルチスレッド環境では、特定の操作が同じ引数に対して(通常は起動時に)同時に呼び出される場合があります。デフォルトでは、キャッシュの抽象化は何もロックせず、同じ値が数回計算され、キャッシュの目的を無効にします。
これらの特定のケースでは、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
に対して評価されます。フレームワークは、組み込みパラメーターに加えて、引数名などの専用のキャッシュ関連メタデータを提供します。次の表は、キーおよび条件付き計算に使用できるように、コンテキストで使用できるようになっているアイテムを示しています。
名前 | ロケーション | 説明 | サンプル |
---|---|---|---|
| ルートオブジェクト | 呼び出されるメソッドの名前 |
|
| ルートオブジェクト | 呼び出されるメソッド |
|
| ルートオブジェクト | 呼び出されるターゲットオブジェクト |
|
| ルートオブジェクト | 呼び出されるターゲットのクラス |
|
| ルートオブジェクト | ターゲットの呼び出しに使用される引数(配列として) |
|
| ルートオブジェクト | 現在のメソッドが実行されるキャッシュのコレクション |
|
引数名 | 評価コンテキスト | メソッド引数の名前。名前が使用できない場合(おそらくデバッグ情報がないため)、引数名は |
|
| 評価コンテキスト | メソッド呼び出しの結果(キャッシュされる値)。 |
|
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
アノテーション
同じタイプの複数のアノテーション(@CacheEvict
や @CachePut
など)を指定する必要がある場合があります。たとえば、条件やキー式がキャッシュごとに異なるためです。@Caching
を使用すると、ネストされた複数の @Cacheable
、@CachePut
、@CacheEvict
アノテーションを同じメソッドで使用できます。次の例では、2 つの @CacheEvict
アノテーションを使用しています。
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
8.2.5. @CacheConfig
アノテーション
これまで、キャッシュ操作には多くのカスタマイズオプションが用意されており、操作ごとにこれらのオプションを設定できることがわかりました。ただし、カスタマイズオプションの一部は、クラスのすべての操作に適用する場合、構成が面倒な場合があります。たとえば、クラスのすべてのキャッシュ操作に使用するキャッシュの名前を指定することは、単一のクラスレベルの定義に置き換えることができます。これが @CacheConfig
の出番です。次の例では、@CacheConfig
を使用してキャッシュの名前を設定します。
@CacheConfig("books") (1)
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}
1 | @CacheConfig を使用してキャッシュの名前を設定します。 |
@CacheConfig
は、キャッシュ名、カスタム KeyGenerator
、カスタム CacheManager
、カスタム CacheResolver
の共有を可能にするクラスレベルのアノテーションです。このアノテーションをクラスに配置しても、キャッシュ操作は有効になりません。
操作レベルのカスタマイズは、常に @CacheConfig
のカスタマイズセットをオーバーライドします。これにより、キャッシュ操作ごとに 3 つのレベルのカスタマイズが可能になります。
グローバルに構成され、
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 を参照してください。 |
XML 属性 | アノテーション属性 | デフォルト | 説明 |
---|---|---|---|
| なし ( |
| 使用するキャッシュマネージャーの名前。デフォルトの |
| なし ( | 構成された | バッキングキャッシュの解決に使用される CacheResolver の Bean 名。この属性は必須ではなく、'cache-manager' 属性の代替としてのみ指定する必要があります。 |
| なし ( |
| 使用するカスタムキージェネレーターの名前。 |
| なし ( |
| 使用するカスタムキャッシュエラーハンドラーの名前。デフォルトでは、キャッシュ関連の操作中にスローされた例外はすべてクライアントでスローされます。 |
|
|
| デフォルトモード( |
|
|
| プロキシモードのみに適用されます。 |
|
| Ordered.LOWEST_PRECEDENCE |
|
<cache:annotation-driven/> は、@Cacheable/@CachePut/@CacheEvict/@Caching が定義されているのと同じアプリケーションコンテキストの Bean でのみ @Cacheable/@CachePut/@CacheEvict/@Caching を検索します。これは、<cache:annotation-driven/> を DispatcherServlet の WebApplicationContext に入れると、サービスではなく、コントローラーでのみ Bean をチェックすることを意味します。詳細について は、MVC セクションを参照してください。 |
Spring は、インターフェースにアノテーションを付けるのではなく、@Cache* アノテーションを使用して、具象クラス(および具象クラスのメソッド)のみにアノテーションを付けることをお勧めします。インターフェース(またはインターフェースメソッド)に @Cache* アノテーションを配置することは確かにできますが、これは、インターフェースベースのプロキシを使用する場合に期待どおりにのみ機能します。Java アノテーションがインターフェースから継承されないという事実は、クラスベースのプロキシ(proxy-target-class="true" )またはウィービングベースのアスペクト(mode="aspectj" )を使用する場合、プロキシ設定およびウィービングインフラストラクチャによってキャッシュ設定が認識されず、オブジェクトがキャッシングプロキシにラップされていません。 |
プロキシモード(デフォルト)では、プロキシを介して受信する外部メソッド呼び出しのみがインターセプトされます。これは、呼び出されたメソッドが @Cacheable でマークされている場合でも、自己呼び出し(実際には、ターゲットオブジェクトの別のメソッドを呼び出すターゲットオブジェクト内のメソッド)が実行時に実際のキャッシュにつながらないことを意味します。この場合、aspectj モードの使用を検討してください。また、期待される動作を提供するためにプロキシを完全に初期化する必要があるため、初期化コード(つまり @PostConstruct )でこの機能に依存しないでください。 |
8.2.7. カスタムアノテーションの使用
キャッシングの抽象化により、独自のアノテーションを使用して、キャッシュの作成またはエビクションをトリガーするメソッドを識別できます。これは、キャッシュアノテーション宣言を複製する必要がないため、テンプレートメカニズムとして非常に便利です。これは、キーまたは条件が指定されている場合、またはコードベースで外部インポート(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 のアノテーションの主な違いを説明しています。
Spring | JSR-107 | リマーク |
---|---|---|
|
| かなり似ています。 |
|
| Spring はメソッド呼び出しの結果でキャッシュを更新しますが、JCache は |
|
| かなり似ています。 |
|
|
|
|
| 同様の方法で、同じ概念を構成できます。 |
JCache の概念は javax.cache.annotation.CacheResolver
です。これは、JCache が単一のキャッシュのみをサポートすることを除いて、Spring の CacheResolver
インターフェースと同一です。デフォルトでは、単純な実装は、アノテーションで宣言された名前に基づいて使用するキャッシュを取得します。アノテーションにキャッシュ名が指定されていない場合、デフォルトが自動的に生成されることに注意してください。詳細については、@CacheResult#cacheName()
の javadoc を参照してください。
CacheResolver
インスタンスは CacheResolverFactory
によって取得されます。次の例に示すように、キャッシュ操作ごとにファクトリをカスタマイズできます。
@CacheResult(cacheNames="books", cacheResolverFactory=MyCacheResolverFactory.class) (1)
public Book findBook(ISBN isbn)
1 | この操作のためにファクトリをカスタマイズします。 |
すべての参照クラスについて、Spring は指定された型の Bean を見つけようとします。複数の一致が存在する場合、新しいインスタンスが作成され、依存性注入などの通常の Bean ライフサイクルコールバックを使用できます。 |
キーは、Spring の KeyGenerator
と同じ目的を果たす javax.cache.annotation.CacheKeyGenerator
によって生成されます。デフォルトでは、少なくとも 1 つのパラメーターに @CacheKey
アノテーションが付けられていない限り、すべてのメソッド引数が考慮されます。これは、Spring のカスタムキー生成宣言に似ています。たとえば、以下は同一の操作です。1 つは Spring の抽象化を使用し、もう 1 つは JCache を使用します。
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@CacheResult(cacheName="books")
public Book findBook(@CacheKey ISBN isbn, boolean checkWarehouse, boolean includeUsed)
CacheResolverFactory
を指定する方法と同様に、操作で CacheKeyResolver
を指定することもできます。
JCache は、アノテーション付きメソッドによってスローされた例外を管理できます。これにより、キャッシュの更新を防ぐことができますが、メソッドを再度呼び出す代わりに、失敗のインジケータとして例外をキャッシュすることもできます。ISBN の構造が無効な場合、InvalidIsbnNotFoundException
がスローされると仮定します。これは永続的な障害です(このようなパラメーターで本を取得することはできません)。次の例では、例外をキャッシュして、同じ無効な ISBN でさらに呼び出した場合、メソッドを再度呼び出すのではなく、キャッシュされた例外を直接スローします。
@CacheResult(cacheName="books", exceptionCacheName="failures"
cachedExceptions = InvalidIsbnNotFoundException.class)
public Book findBook(ISBN isbn)
8.3.2. JSR-107 サポートの有効化
Spring の宣言的アノテーションサポートと一緒に JSR-107 サポートを有効にするために、特別なことを何もする必要はありません。JSR-107 API と spring-context-support
モジュールの両方がクラスパスに存在する場合、@EnableCaching
と cache:annotation-driven
エレメントの両方が自動的に JCache サポートを有効にします。
ユースケースに応じて、選択は基本的にあなた次第です。一部で JSR-107 API を使用し、他で Spring 独自のアノテーションを使用することで、サービスを組み合わせて使用することもできます。ただし、これらのサービスが同じキャッシュに影響を与える場合は、一貫性のある同一のキー生成実装を使用する必要があります。 |
8.4. 宣言的な XML ベースのキャッシュ
アノテーションがオプションではない場合(おそらくソースへのアクセス権がないか、外部コードがないため)、宣言キャッシュに XML を使用できます。キャッシングのメソッドにアノテーションを付ける代わりに、ターゲットメソッドとキャッシングディレクティブを外部で指定できます(宣言的なトランザクション管理アドバイスと同様)。前のセクションの例は、次の例に変換できます。
<!-- the service we want to make cacheable -->
<bean id="bookService" class="x.y.service.DefaultBookService"/>
<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="books">
<cache:cacheable method="findBook" key="#isbn"/>
<cache:cache-evict method="loadBooks" all-entries="true"/>
</cache:caching>
</cache:advice>
<!-- apply the cacheable behavior to all BookService interfaces -->
<aop:config>
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* x.y.BookService.*(..))"/>
</aop:config>
<!-- cache manager definition omitted -->
上記の構成では、bookService
はキャッシュ可能になっています。適用するキャッシングセマンティクスは cache:advice
定義にカプセル化されます。これにより、findBooks
メソッドがデータをキャッシュに入れるために使用され、loadBooks
メソッドがデータを排除するために使用されます。両方の定義は、books
キャッシュに対して機能します。
aop:config
定義は、AspectJ ポイントカット式を使用して、プログラム内の適切なポイントにキャッシュアドバイスを適用します(詳細については、Spring によるアスペクト指向プログラミングを参照してください)。前の例では、BookService
のすべてのメソッドが考慮され、それらにキャッシュアドバイスが適用されます。
宣言型 XML キャッシングは、アノテーションベースのモデルをすべてサポートしているため、2 つの間を移動するのはかなり簡単です。さらに、両方を同じアプリケーション内で使用できます。XML ベースのアプローチは、ターゲットコードに影響を与えません。ただし、本質的に冗長です。キャッシュの対象となるオーバーロードされたメソッドを持つクラスを処理する場合、method
引数は適切な識別子ではないため、適切なメソッドを識別するには余分な労力がかかります。このような場合、AspectJ ポイントカットを使用して、ターゲットメソッドを選択し、適切なキャッシュ機能を適用できます。ただし、XML を使用すると、パッケージ、グループ、またはインターフェース全体のキャッシュを適用し(これも、AspectJ ポイントカットのため)、テンプレートのような定義を作成する方が簡単です(前の例で cache:definitions
を介してターゲットキャッシュを定義した場合と同様)。cache
属性)。
8.5. キャッシュストレージの構成
キャッシュ抽象化は、いくつかのストレージ統合オプションを提供します。使用するには、適切な CacheManager
(Cache
インスタンスを制御および管理し、これらをストレージに取得するために使用できるエンティティ)を宣言する必要があります。
8.5.1. JDK ConcurrentMap
ベースのキャッシュ
JDK ベースの Cache
実装は、org.springframework.cache.concurrent
パッケージにあります。ConcurrentHashMap
をバッキング Cache
ストアとして使用できます。次の例は、2 つのキャッシュを構成する方法を示しています。
<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
</set>
</property>
</bean>
上記のスニペットは、SimpleCacheManager
を使用して、default
および books
という名前の 2 つのネストされた ConcurrentMapCache
インスタンスの CacheManager
を作成します。名前は各キャッシュに直接設定されることに注意してください。
キャッシュはアプリケーションによって作成されるため、そのライフサイクルにバインドされているため、基本的なユースケース、テスト、または単純なアプリケーションに適しています。キャッシュは適切に拡張され、非常に高速ですが、管理、永続化機能、またはエビクション契約は提供されません。
8.5.2. ehcache ベースのキャッシュ
Ehcache 3.x は JSR-107 に完全に準拠しており、専用のサポートは必要ありません。 |
Ehcache 2.x 実装は org.springframework.cache.ehcache
パッケージに含まれています。繰り返しますが、それを使用するには、適切な CacheManager
を宣言する必要があります。次の例は、その方法を示しています。
<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
<!-- EhCache library setup -->
<bean id="ehcache"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache.xml"/>
このセットアップでは、Spring IoC 内の ehcache ライブラリを(ehcache
Bean を介して)ブートストラップし、専用の CacheManager
実装に接続します。Ehcache 固有の設定全体が ehcache.xml
から読み取られることに注意してください。
8.5.3. Caffeine キャッシュ
Caffeine は Guava のキャッシュを Java 8 で書き換えたもので、その実装は org.springframework.cache.caffeine
パッケージにあり、Caffeine のいくつかの機能へのアクセスを提供します。
次の例では、オンデマンドでキャッシュを作成する CacheManager
を構成します。
<bean id="cacheManager"
class="org.springframework.cache.caffeine.CaffeineCacheManager"/>
明示的に使用するキャッシュを提供することもできます。その場合、マネージャーのみが利用可能になります。次の例は、その方法を示しています。
<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
<property name="caches">
<set>
<value>default</value>
<value>books</value>
</set>
</property>
</bean>
Caffeine CacheManager
は、カスタム Caffeine
および CacheLoader
もサポートしています。それらの詳細については、Caffeine のドキュメント: GitHub (英語) を参照してください。
8.5.4. GemFire ベースのキャッシュ
GemFire は、メモリ指向、ディスクバックアップ、弾性スケーラブル、継続的に利用可能、アクティブ(組み込みのパターンベースのサブスクリプション通知を使用)、グローバルに複製されたデータベースであり、完全な機能を備えたエッジキャッシングを提供します。GemFire を CacheManager
(およびそれ以上)として使用する方法の詳細については、Spring Data GemFire リファレンスドキュメント (英語) を参照してください。
8.5.5. JSR-107 キャッシュ
Spring のキャッシュ抽象化では、JSR-107 準拠のキャッシュも使用できます。JCache 実装は org.springframework.cache.jcache
パッケージにあります。
繰り返しますが、これを使用するには、適切な CacheManager
を宣言する必要があります。次の例は、その方法を示しています。
<bean id="cacheManager"
class="org.springframework.cache.jcache.JCacheCacheManager"
p:cache-manager-ref="jCacheManager"/>
<!-- JSR-107 cache manager setup -->
<bean id="jCacheManager" .../>
8.5.6. バッキングストアなしでキャッシュを処理する
場合によっては、環境を切り替えたりテストを行ったりするときに、実際のバッキングキャッシュを構成せずにキャッシュ宣言を行うことがあります。これは無効な構成であるため、キャッシュインフラストラクチャが適切なストアを見つけることができないため、実行時に例外がスローされます。このような状況では、キャッシュ宣言を削除するのは(退屈な作業になる可能性があります)、キャッシュを実行しない単純なダミーキャッシュを接続できます。つまり、キャッシュされたメソッドが毎回呼び出されるようになります。次の例は、その方法を示しています。
<bean id="cacheManager" class="org.springframework.cache.support.CompositeCacheManager">
<property name="cacheManagers">
<list>
<ref bean="jdkCache"/>
<ref bean="gemfireCache"/>
</list>
</property>
<property name="fallbackToNoOpCache" value="true"/>
</bean>
前述のチェーン複数の CacheManager
インスタンスの CompositeCacheManager
は、fallbackToNoOpCache
フラグを介して、構成されたキャッシュマネージャーで処理されないすべての定義に対して何もしないキャッシュを追加します。つまり、jdkCache
または gemfireCache
のいずれにも見つからないすべてのキャッシュ定義(例の前半で構成)は、情報を格納しない no-op キャッシュによって処理され、ターゲットメソッドが毎回呼び出されます。
8.6. 異なるバックエンドキャッシュのプラグイン
明らかに、バッキングストアとして使用できるキャッシング製品がたくさんあります。プラグインするには、CacheManager
および Cache
実装を提供する必要があります。残念ながら、代わりに使用できる標準はありません。実際には、クラスは、ehcache
クラスが行うように、ストレージ API の上にキャッシング抽象化フレームワークをマッピングする単純なアダプター (英語) になる傾向があるため、これは難しいように聞こえるかもしれません。ほとんどの CacheManager
クラスは、org.springframework.cache.support
パッケージのクラスを使用できます(AbstractCacheManager
は、ボイラープレートコードを処理し、実際のマッピングのみを完了させます)。やがて、Spring との統合を提供するライブラリがこの小さな構成ギャップを埋めることができることを願っています。
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>