2.4. Bean インテグレーション
概要
Bean インテグレーションは、任意の Java オブジェクトを使用してメッセージを処理するための汎用のメカニズムを提供します。Bean の参照をルートに挿入すると、Java オブジェクトの任意のメソッドを呼び出して、受信エクスチェンジにアクセスしたり変更したりすることができます。エクスチェンジの内容を Bean メソッドのパラメーターと戻り値にマッピングするメカニズムは、パラメーターバインディング と呼ばれます。パラメーターバインディングは、メソッドのパラメーターを初期化するために、以下のアプローチの任意の組み合わせを使用することができます。
- 規約に従ったメソッドシグネチャー - メソッドシグネチャーが特定の規約に準拠している場合、パラメーターバインディングは Java リフレクションを使用して、どのパラメーターを渡すかを決定できます。
- アノテーションと依存性注入 - より柔軟なバインディングメカニズムが必要な場合は、Java アノテーションを使用してメソッドの引数に何を注入するかを指定します。この依存性注入メカニズムは、Spring 2.5 のコンポーネントスキャンに基づきます。通常、Apache Camel アプリケーションを Spring コンテナーにデプロイする場合、依存性注入メカニズムは自動的に機能します。
- 明示的に指定したパラメーター - Bean が呼び出される段階で、パラメーターを明示的に (定数として、または Simple 言語を使用して) 指定できます。
Bean レジストリー
Bean は Bean レジストリー を介してアクセスできます。Bean レジストリーは、クラス名または Bean ID のいずれかをキーとして Bean を検索できるサービスです。Bean レジストリーにエントリーを作成する方法は、基盤となるフレームワーク (たとえばプレーンな Java、Spring、Guice、または Blueprint など) によって異なります。レジストリーのエントリーは通常暗黙的に作成されます (例: Spring XML ファイルで Spring Bean をインスタンス化するときなど)。
レジストリープラグインストラテジー
Apache Camel は Bean レジストリーのプラグインストラテジーを実装しており、基盤となるレジストリー実装から透過的に Bean にアクセスするためのインテグレーション層を定義しています。そのため、表2.2「レジストリープラグイン」 に示されるように、Apache Camel アプリケーションをさまざまな Bean レジストリーと統合させることが可能です。
レジストリー実装 | レジストリープラグインのある Camel コンポーネント |
---|---|
Spring Bean レジストリー |
|
Guice Bean レジストリー |
|
Blueprint Bean レジストリー |
|
OSGi サービスレジストリー | OSGi コンテナーにデプロイされている |
JNDI レジストリー |
通常、関連する Bean レジストリーが自動的にインストールされるため、Bean レジストリーの設定を自ら行なう必要はありません。たとえば、Spring フレームワークを使用してルートを定義する場合、Spring ApplicationContextRegistry
プラグインは現在の CamelContext
インスタンスに自動的にインストールされます。
OSGi コンテナーへのデプロイは特別なケースになります。Apache Camel ルートが OSGi コンテナーにデプロイされると、CamelContext
が Bean インスタンスの解決のためにレジストリーチェーンを自動的に設定します。レジストリーチェーンは OSGi レジストリーと、それに続く Blueprint (または Spring) レジストリーで設定されます。
Java で作成された Bean へのアクセス
Java Bean (Plain Old Java Object または POJO) を使用してエクスチェンジオブジェクトを処理するには、インバウンドエクスチェンジを Java オブジェクトのメソッドにバインドする bean()
プロセッサーを使用します。たとえば、MyBeanProcessor
クラスを使用してインバウンドエクスチェンジを処理するには、以下のようにルートを定義します。
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody") .to("file:data/outbound");
bean()
プロセッサーは MyBeanProcessor
型のインスタンスを作成し、processBody()
メソッドを呼び出してインバウンドエクスチェンジを処理します。単一のルートからのみ MyBeanProcessor
インスタンスにアクセスする場合には、この方法が適切です。しかし、複数のルートから同じ MyBeanProcessor
インスタンスにアクセスする場合は、Object
型を最初の引数として取る bean()
のバリアントを使用します。以下に例を示します。
MyBeanProcessor myBean = new MyBeanProcessor(); from("file:data/inbound") .bean(myBean, "processBody") .to("file:data/outbound"); from("activemq:inboundData") .bean(myBean, "processBody") .to("activemq:outboundData");
オーバーロードされた Bean メソッドへのアクセス
Bean がオーバーロードされた複数のメソッドを定義する場合、メソッド名とそのパラメーター型を指定して、どのオーバーロードされたメソッドを呼び出すかを選択できます。たとえば、MyBeanBrocessor
クラスに 2 つのオーバーロードされたメソッド processBody(String)
および processBody(String,String)
がある場合、後者のオーバーロードされたメソッドを以下のように呼び出すことができます。
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(String,String)") .to("file:data/outbound");
または、各パラメーターの型を明示的に指定するのではなく、受け取るパラメーターの数でメソッドを特定する場合は、ワイルドカード文字 *
を使用できます。たとえば、パラメーターの正確な型に関係なく、2 つのパラメーターを取る名前が processBody
のメソッドを呼び出すには、以下のように bean()
プロセッサーを呼び出します。
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(*,*)") .to("file:data/outbound");
メソッドを指定する場合、単純な修飾なしの型名 (例: processBody(Exchange)
) または完全修飾型名 (例: processBody(org.apache.camel.Exchange)
) のいずれかを使用できます。
現在の実装では、指定された型名はパラメーター型に完全に一致する必要があります。型の継承は考慮されません。
パラメーターの明示的な指定
Bean メソッドを呼び出す際に、パラメーター値を明示的に指定できます。以下の単純な型の値を渡すことができます。
-
ブール値:
true
またはfalse
-
数値:
123
、7
など -
文字列:
'In single quotes'
または"In double quotes"
-
Null オブジェクト:
null
以下の例は、同じメソッド呼び出しの中で明示的なパラメーター値と型指定子を混在させる方法を示しています。
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(String, 'Sample string value', true, 7)") .to("file:data/outbound");
上記の例では、最初のパラメーターの値はパラメーターバインディングアノテーションによって決定されます (「基本アノテーション」 を参照)。
単純な型の値の他に、Simple 言語 (30章Simple 言語) を使用してパラメーター値を指定することもできます。これは、パラメーター値を指定する際に Simple 言語の完全な機能が利用可能である ことを意味します。たとえば、メッセージボディーと title
ヘッダーの値を Bean メソッドに渡すには、以下のようにします。
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBodyAndHeader(${body},${header.title})") .to("file:data/outbound");
ヘッダーのハッシュマップ全体をパラメーターとして渡すこともできます。たとえば、以下の例では、2 つ目のメソッドパラメーターは java.util.Map
型として宣言する必要があります。
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBodyAndAllHeaders(${body},${header})") .to("file:data/outbound");
Apache Camel 2.19 のリリースから、Bean メソッド呼び出しから null を返すことで、常にメッセージボディーが null 値として設定されるようになりました。
基本的なメソッドシグネチャー
エクスチェンジを Bean メソッドにバインドするには、特定の規約に準拠するメソッドシグネチャーを定義します。特に、メソッドシグネチャーには 2 つの基本的な規約があります。
メッセージボディーを処理するメソッドシグネチャー
受信メッセージボディーにアクセスしたり、これを変更したりする Bean メソッドを実装する場合は、単一の String
引数を取り、String
値を返すメソッドシグネチャーを定義する必要があります。以下に例を示します。
// Java package com.acme; public class MyBeanProcessor { public String processBody(String body) { // Do whatever you like to 'body'... return newBody; } }
エクスチェンジを処理するメソッドシグネチャー
より柔軟性を高めるために、受信エクスチェンジにアクセスする Bean メソッドを実装できます。これにより、すべてのヘッダー、ボディー、エクスチェンジプロパティーにアクセスしたり、変更したりすることができます。エクスチェンジの処理には、メソッドシグネチャーは単一の org.apache.camel.Exchange
パラメーターを取り、void
を返します。以下に例を示します。
// Java package com.acme; public class MyBeanProcessor { public void processExchange(Exchange exchange) { // Do whatever you like to 'exchange'... exchange.getIn().setBody("Here is a new message body!"); } }
Spring XML から Spring Bean へのアクセス
Java で Bean インスタンスを作成する代わりに、Spring XML を使用してインスタンスを作成できます。実際、ルートを XML で定義している場合には、これが唯一の実行可能な方法です。XML で Bean を定義するには、標準の Spring bean
要素を使用します。以下の例は、MyBeanProcessor
のインスタンスを作成する方法を示しています。
<beans ...> ... <bean id="myBeanId" class="com.acme.MyBeanProcessor"/> </beans>
Spring 構文を使用して、データを Bean のコンストラクター引数に渡すこともできます。Spring bean
要素の使用方法に関する詳細は、Spring リファレンスガイドの The IoC Container を参照してください。
bean
要素を使用してオブジェクトインスタンスを作成する場合、Bean の ID (bean
要素の id
属性の値) を使用すると後でオブジェクトインスタンスを参照できます。たとえば、ID が myBeanId
と同じ bean
要素がある場合は、以下のように beanRef()
プロセッサーを使用して Java DSL ルート内で Bean を参照できます。
from("file:data/inbound").beanRef("myBeanId", "processBody").to("file:data/outbound");
beanRef()
プロセッサーは、指定された Bean インスタンスで MyBeanProcessor.processBody()
メソッドを呼び出します。
Spring XML ルート内から、Camel スキーマの bean
要素を使用して Bean を呼び出すこともできます。以下に例を示します。
<camelContext id="CamelContextID" xmlns="http://camel.apache.org/schema/spring"> <route> <from uri="file:data/inbound"/> <bean ref="myBeanId" method="processBody"/> <to uri="file:data/outbound"/> </route> </camelContext>
効率を若干向上させるために、cache
オプションを true
に設定して、Bean が使用されるたびにレジストリーを検索しないようにすることもできます。たとえば、キャッシュを有効にするには、以下のように bean
要素の cache
属性を設定します。
<bean ref="myBeanId" method="processBody" cache="true"/>
Java からの Spring Bean へのアクセス
Spring bean
要素を使用してオブジェクトインスタンスを作成する場合、Bean の ID (bean
要素の id
属性の値) を使用して Java からオブジェクトインスタンスを参照できます。たとえば、ID が myBeanId
と同じ bean
要素がある場合は、以下のように beanRef()
プロセッサーを使用して Java DSL ルート内で Bean を参照できます。
from("file:data/inbound").beanRef("myBeanId", "processBody").to("file:data/outbound");
または、以下のように @BeanInject
アノテーションを使用して、依存性注入によって Spring Bean を参照することもできます。
// Java import org.apache.camel.@BeanInject; ... public class MyRouteBuilder extends RouteBuilder { @BeanInject("myBeanId") com.acme.MyBeanProcessor bean; public void configure() throws Exception { .. } }
@BeanInject
アノテーションから Bean ID を省略した場合、Camel は型別にレジストリーを検索しますが、これは指定された型の Bean が 1 つだけの場合にのみ機能します。たとえば、com.acme.MyBeanProcessor
型の Bean を検索して依存性注入するには、以下を実行します。
@BeanInject com.acme.MyBeanProcessor bean;
Spring XML における Bean のシャットダウン順序
Camel コンテキストで使用される Bean の場合、通常、正しいシャットダウンの順序は次のようになります。
-
camelContext
インスタンスをシャットダウンします。 - 使用された Bean をシャットダウンします。
このシャットダウン順序が逆の場合、Camel コンテキストがすでに破棄された Bean にアクセスしようとすることがあります (直接エラーになるか、または Camel コンテキストが破棄されている間に見つからなかった Bean を作成しようして、結局エラーになるかのどちらかです)。Spring XML のデフォルトのシャットダウン順序は、Bean と camelContext
が Spring XML ファイルの中で出現する順序によって異なります。誤ったシャットダウン順序によるランダムなエラーを回避するため、camelContext
は Spring XML ファイルの他の Bean よりも 前に シャットダウンするように設定されています。これは Apache Camel 2.13.0 以降のデフォルトの動作です。
この動作を変更 (Camel コンテキストが他の Bean の前に強制的にシャットダウン されない ように) する必要がある場合は、camelContext
要素の shutdownEager
属性を false
に設定します。この場合、Spring の depends-on
属性を使用して、シャットダウンの順序をより詳細に制御することもできます。
パラメーターバインディングアノテーション
「基本的なメソッドシグネチャー」 で説明されている基本的なパラメーターバインディングは、必ずしも便利に使えるとは限りません。たとえば、何らかのデータ操作を行うレガシーな Java クラスがある場合、インバウンドエクスチェンジからデータを抽出し、既存のメソッドシグネチャーの引数にマップする必要があるかもしれません。このようなパラメーターバインディングには、Apache Camel は以下のような Java アノテーションを提供します。
基本アノテーション
表2.3「基本の Bean アノテーション」 は、Bean メソッドの引数にメッセージデータを依存性注入するために使用できる org.apache.camel
Java パッケージのアノテーションを示しています。
アノテーション | 意味 | パラメーター |
---|---|---|
| アタッチメントのリストにバインドします。 | |
| インバウンドメッセージのボディーにバインドします。 | |
| インバウンドメッセージのヘッダーにバインドします。 | ヘッダーの文字列名。 |
|
インバウンドメッセージヘッダーの | |
|
アウトバウンドメッセージヘッダーの | |
| 名前のあるエクスチェンジプロパティーにバインドします。 | プロパティーの文字列名。 |
|
エクスチェンジプロパティーの |
たとえば、以下のクラスは基本アノテーションを使用してメッセージデータを processExchange()
メソッド引数に依存性注入する方法を示しています。
// Java import org.apache.camel.*; public class MyBeanProcessor { public void processExchange( @Header(name="user") String user, @Body String body, Exchange exchange ) { // Do whatever you like to 'exchange'... exchange.getIn().setBody(body + "UserName = " + user); } }
アノテーションがどのようにデフォルトの規約と混在できるかに注目してください。パラメーターバインディングは、アノテーションが付けられた引数を依存性注入するだけでなく、エクスチェンジオブジェクトも org.apache.camel.Exchange
引数に自動的に依存性注入します。
式言語アノテーション
式言語アノテーションは、メッセージデータを Bean メソッドの引数に依存性注入する強力なメカニズムを提供します。これらのアノテーションを使用すると、任意のスクリプト言語で書かれた任意のスクリプトを呼び出して、インバウンドエクスチェンジからデータを抽出し、メソッド引数に注入することができます。表2.4「式言語アノテーション」 は、Bean メソッドの引数にメッセージデータを依存性注入するために使用できる org.apache.camel.language
パッケージ (およびコア以外のアノテーションのサブパッケージ) のアノテーションを示しています。
アノテーション | 説明 |
---|---|
| Bean 式を注入します。 |
| Constant 式を注入します。 |
| EL 式を注入します。 |
| Groovy 式を注入します。 |
| ヘッダー式を注入します。 |
| JavaScript 式を注入します。 |
| OGNL 式を注入します。 |
| PHP 式を注入します。 |
| Python 式を注入します。 |
| Ruby 式を注入します。 |
| Simple 式を注入します。 |
| XPath 式を注入します。 |
| XQuery 式を注入します。 |
たとえば、以下のクラスは、XML 形式の受信メッセージのボディーからユーザー名とパスワードを抽出するために @XPath
アノテーションを使用する方法を示しています。
// Java import org.apache.camel.language.*; public class MyBeanProcessor { public void checkCredentials( @XPath("/credentials/username/text()") String user, @XPath("/credentials/password/text()") String pass ) { // Check the user/pass credentials... ... } }
@Bean
アノテーションは、特殊なケースになります。登録された Bean の呼び出し結果を依存性注入できるためです。たとえば、相関 ID をメソッド引数に依存性注入するには、以下のように @Bean
アノテーションを使用して ID 生成クラスを呼び出します。
// Java import org.apache.camel.language.*; public class MyBeanProcessor { public void processCorrelatedMsg( @Bean("myCorrIdGenerator") String corrId, @Body String body ) { // Check the user/pass credentials... ... } }
文字列 myCorrIdGenerator
は ID 生成インスタンスの Bean ID です。ID 生成クラスは、以下のように Spring の bean
要素を使用してインスタンス化できます。
<beans ...> ... <bean id="myCorrIdGenerator" class="com.acme.MyIdGenerator"/> </beans>
MyIdGenerator
クラスは以下のように定義することができます。
// Java package com.acme; public class MyIdGenerator { private UserManager userManager; public String generate( @Header(name = "user") String user, @Body String payload ) throws Exception { User user = userManager.lookupUser(user); String userId = user.getPrimaryId(); String id = userId + generateHashCodeForPayload(payload); return id; } }
参照された Bean クラス MyIdGenerator
でアノテーションを使用することもできます。generate()
メソッドシグネチャーに対する唯一の制限は、@Bean
アノテーションが付けられた引数に依存性注入するために正しい型を返す必要があることです。@Bean
アノテーションではメソッド名を指定できないため、依存性注入メカニズムは単純に参照された Bean の戻り値型が一致する最初のメソッドを呼び出します。
言語アノテーションのいくつかはコアコンポーネントで利用できます (@Bean
、@Constant
、@Simple
、および @XPath
)。しかし、コア以外のコンポーネントの場合、該当するコンポーネントをロードしておく必要があります。たとえば、OGNL スクリプトを使用するには、camel-ognl
コンポーネントをロードする必要があります。
継承されたアノテーション
パラメーターバインディングアノテーションは、インターフェイスまたはスーパークラスから継承できます。たとえば、以下のように Header
アノテーションと Body
アノテーションの付いた Java インターフェイスを定義したとします。
// Java import org.apache.camel.*; public interface MyBeanProcessorIntf { void processExchange( @Header(name="user") String user, @Body String body, Exchange exchange ); }
実装クラス MyBeanProcessor
で定義されたオーバーロードされたメソッドは、以下のように基本インターフェイスに定義されたアノテーションを継承します。
// Java import org.apache.camel.*; public class MyBeanProcessor implements MyBeanProcessorIntf { public void processExchange( String user, // Inherits Header annotation String body, // Inherits Body annotation Exchange exchange ) { ... } }
インターフェイスの実装
Java インターフェイスを実装するクラスは、多くの場合、protected
、private
、または package-only
の範囲となります。このように制限された実装クラスのメソッドを呼び出す場合、Bean バインディングはフォールバックして、公開アクセス可能な対応するインターフェイスメソッドを呼び出します。
たとえば、以下のパブリック BeanIntf
インターフェイスについて考えてみましょう。
// Java public interface BeanIntf { void processBodyAndHeader(String body, String title); }
BeanIntf
インターフェイスは、以下の protected な BeanIntfImpl
クラスによって実装されます。
// Java protected class BeanIntfImpl implements BeanIntf { void processBodyAndHeader(String body, String title) { ... } }
以下の Bean 呼び出しは、フォールバックして public な BeanIntf.processBodyAndHeader
メソッドを呼び出します。
from("file:data/inbound") .bean(BeanIntfImpl.class, "processBodyAndHeader(${body}, ${header.title})") .to("file:data/outbound");
static メソッドの呼び出し
Bean インテグレーションには、関連付けられたクラスのインスタンスを作成 せずに static メソッドを呼び出す機能があります。たとえば、static メソッド changeSomething()
を定義した以下の Java クラスについて考えてみましょう。
// Java ... public final class MyStaticClass { private MyStaticClass() { } public static String changeSomething(String s) { if ("Hello World".equals(s)) { return "Bye World"; } return null; } public void doSomething() { // noop } }
以下のように、Bean インテグレーションを使用して static changeSomething
メソッドを呼び出すことができます。
from("direct:a") *.bean(MyStaticClass.class, "changeSomething")* .to("mock:a");
この構文は、通常の関数の呼び出しと同じように見えますが、Bean インテグレーションは Java のリフレクションを利用してこのメソッドを static と識別し、MyStaticClass
をインスタンス化 せずに メソッドの呼び出しに進むことに留意してください。
OSGi サービスの呼び出し
ルートが Red Hat Fuse コンテナーにデプロイされた特別なケースでは、Bean インテグレーションを使用して OSGi サービスを直接呼び出すことができます。たとえば、OSGi コンテナーのバンドルのいずれかがサービス org.fusesource.example.HelloWorldOsgiService
をエクスポートしているとすると、以下のような Bean インテグレーションのコードを使用して sayHello
メソッドを呼び出すことができます。
from("file:data/inbound") .bean(org.fusesource.example.HelloWorldOsgiService.class, "sayHello") .to("file:data/outbound");
以下のように Bean コンポーネントを使用して、Spring または Blueprint XML ファイル内から OSGi サービスを呼び出すこともできます。
<to uri="bean:org.fusesource.example.HelloWorldOsgiService?method=sayHello"/>
これが動作する仕組みは、Apache Camel が OSGi コンテナーにデプロイされる際にレジストリーのチェーンを設定することによります。まず、OSGi サービスレジストリーで指定のクラス名を検索します。検索に失敗した場合、ローカルの Spring DM または Blueprint レジストリーにフォールバックします。