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 レジストリーと統合させることが可能です。

表2.2 レジストリープラグイン

レジストリー実装レジストリープラグインのある Camel コンポーネント

Spring Bean レジストリー

camel-spring

Guice Bean レジストリー

camel-guice

Blueprint Bean レジストリー

camel-blueprint

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
  • 数値: 1237 など
  • 文字列: '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 の場合、通常、正しいシャットダウンの順序は次のようになります。

  1. camelContext インスタンスをシャットダウンします。
  2. 使用された 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 パッケージのアノテーションを示しています。

表2.3 基本の Bean アノテーション

アノテーション意味パラメーター

@Attachments

アタッチメントのリストにバインドします。

 

@Body

インバウンドメッセージのボディーにバインドします。

 

@Header

インバウンドメッセージのヘッダーにバインドします。

ヘッダーの文字列名。

@Headers

インバウンドメッセージヘッダーの java.util.Map にバインドします。

 

@OutHeaders

アウトバウンドメッセージヘッダーの java.util.Map にバインドします。

 

@Property

名前のあるエクスチェンジプロパティーにバインドします。

プロパティーの文字列名。

@Properties

エクスチェンジプロパティーの java.util.Map にバインドします。

 

たとえば、以下のクラスは基本アノテーションを使用してメッセージデータを 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 パッケージ (およびコア以外のアノテーションのサブパッケージ) のアノテーションを示しています。

表2.4 式言語アノテーション

アノテーション説明

@Bean

Bean 式を注入します。

@Constant

Constant 式を注入します。

@EL

EL 式を注入します。

@Groovy

Groovy 式を注入します。

@Header

ヘッダー式を注入します。

@JavaScript

JavaScript 式を注入します。

@OGNL

OGNL 式を注入します。

@PHP

PHP 式を注入します。

@Python

Python 式を注入します。

@Ruby

Ruby 式を注入します。

@Simple

Simple 式を注入します。

@XPath

XPath 式を注入します。

@XQuery

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 インターフェイスを実装するクラスは、多くの場合、protectedprivate、または 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 レジストリーにフォールバックします。