リファレンスドキュメントのこのパートでは、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 MVC REST コントローラー) で使用されます。
メインメディア(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 メッセージサービス) を参照してください。
次のインターフェースは、サーバー側とクライアント側の両方で使用されます。
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 メッセージサービス)
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 実装を指定できます。例については、ExponentialBackOff (Javadoc) を参照してください。
その兄弟(SimpleMessageListenerContainer)と同様に、DefaultMessageListenerContainer はネイティブ JMS トランザクションをサポートし、確認応答モードをカスタマイズできます。シナリオで実行可能な場合、これは外部で管理されるトランザクションよりも強くお勧めします。つまり、JVM が停止した場合にメッセージが重複することがある場合です。ビジネスロジックのカスタム重複メッセージ検出手順は、このような状況をカバーできます。たとえば、ビジネスエンティティの存在チェックやプロトコルテーブルチェックの形式です。このような配置は、他の方法よりもはるかに効率的です。処理全体を XA トランザクションでラップして(DefaultMessageListenerContainer を JtaTransactionManager で構成することにより)、JMS メッセージの受信とメッセージリスナーでのビジネスロジックの実行をカバーします(データベース操作などを含む)。 |
デフォルトの 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[]、BytesMessage の間、および 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 を参照してください。
| 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 名が自動的に生成されます。 |
|
The destination name for this listener, resolved through the |
|
The bean name of the handler object. |
| 呼び出すハンドラーメソッドの名前。 |
| レスポンスメッセージを送信するデフォルトのレスポンス宛先の名前。これは、 |
| 永続サブスクリプションの名前(ある場合)。 |
| このリスナーのオプションのメッセージセレクター。 |
| このリスナーに対して開始する同時セッションまたはコンシューマーの数。この値は、最大数を示す単純な数値(たとえば、 |
<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 を参照してください。
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 秒(5000 ミリ秒)ごとに呼び出されます。つまり、期間は、先行する各呼び出しの完了時間から測定されます。
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// something that should run periodically
} デフォルトでは、ミリ秒は、固定遅延、固定レート、初期遅延値の時間単位として使用されます。秒や分などの別の時間単位を使用する場合は、 例: 前の例は次のように書くこともできます。 |
固定レートの実行が必要な場合は、アノテーション内で fixedRate 属性を使用できます。次のメソッドは、5 秒ごとに呼び出されます(各呼び出しの連続する開始時間の間で測定されます)。
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
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 を使用している場合は “annotation-driven” 要素、または存在する場合は 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. 'scheduler' エレメント
次の要素は、指定されたスレッドプールサイズで 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. 'scheduled-tasks' エレメント
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 式 | 意味 |
|---|---|
| 毎日 0 時 0 分 |
| 10 秒ごと |
| 毎日 8, 9, 10 時 |
| 毎日 6:00 AM、7:00 PM |
| 毎日 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") です。
| マクロ | 意味 |
|---|---|
|
once a year ( |
| 1 ヶ月に 1 回 ( |
| 1 週間に 1 回 ( |
|
once a day ( |
| 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> ジョブの詳細で使用されるカレンダー、Quartz をカスタマイズするためのプロパティ、Spring が提供する JDBC DataSource など、SchedulerFactoryBean で使用できるプロパティは他にもあります。詳細については、SchedulerFactoryBean javadoc を参照してください。
SchedulerFactoryBean は、通常の Quartz 構成と同様に、Quartz プロパティキーに基づいて、クラスパス内の quartz.properties ファイルも認識します。多くの SchedulerFactoryBean 設定は、プロパティファイルの一般的な Quartz 設定と相互作用することに注意してください。両方のレベルで値を指定することはお勧めしません。例: Spring が提供する DataSource に依存する場合は、"org.quartz.jobStore.class" プロパティを設定しないでください。または、標準の org.quartz.impl.jdbcjobstore.JobStoreTX の本格的な代替である org.springframework.scheduling.quartz.LocalDataSourceJobStore バリアントを指定してください。 |
8. キャッシュの抽象化
バージョン 3.1 以降、Spring Framework は既存の Spring アプリケーションに透過的にキャッシュを追加するためのサポートを提供します。トランザクションのサポートと同様に、キャッシングの抽象化により、コードへの影響を最小限に抑えながら、さまざまなキャッシングソリューションを一貫して使用できます。
Spring Framework 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-proceed-and-put-evently コードブロックと直接同等です。ロックは適用されず、複数のスレッドが同じアイテムを同時にロードしようとする場合があります。同じことが小作農追い出しにも当てはまります。複数のスレッドが同時にデータを更新または削除しようとしている場合は、古いデータを使用する可能性があります。特定のキャッシュプロバイダーは、その領域で高度な機能を提供します。詳細については、キャッシュプロバイダーのドキュメントを参照してください。
キャッシュの抽象化を使用するには、次の 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 の戻り型をサポートします。Optional 値が存在する場合、関連するキャッシュに保存されます。Optional 値が存在しない場合、null は関連するキャッシュに保存されます。#result は常にビジネスエンティティを参照し、サポートされているラッパーを参照しないため、前の例は次のように書き直すことができます。
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)#result は、Optional<Book> ではなく Book を参照していることに注意してください。null である可能性があるため、SpEL のセーフナビゲーション演算子を使用します。
利用可能なキャッシング 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* アノテーションをインターフェース(またはインターフェースメソッド)に配置できますが、これはプロキシモード(mode="proxy")を使用する場合にのみ機能します。ウィービングベースのアスペクト(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 標準(JSR-107)アノテーション(@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 XML 要素の両方が自動的に 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. 異なるバックエンドキャッシュのプラグイン
明らかに、バッキングストアとして使用できるキャッシュ製品はたくさんあります。JSR-107 をサポートしていない場合は、CacheManager と Cache の実装を提供する必要があります。実際には、クラスは ehcache クラスと同様に、ストレージ API の上にキャッシング抽象化フレームワークをマップする単純なアダプター [Wikipedia] である傾向があるため、これは実際よりも難しいように聞こえるかもしれません。ほとんどの CacheManager クラスは、org.springframework.cache.support パッケージのクラスを使用できます(ボイラープレートコードを処理し、実際のマッピングのみを完了する AbstractCacheManager など)。
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>