LoginSignup
0
0

More than 5 years have passed since last update.

Spring Integration 勉強の備忘録 ~Spring Integration Sampleの理解 3. Enricher~

Posted at

目的

Spring Integrationを学ぶために、Spring Projectが提供しているサンプルを理解して、実装し直す + アレンジしてみた。本記事は、その備忘録として。なので色々間違いがあるとは思いますが、その点はご指摘いただければ幸いです。
サンプルは以下のGitに登録してあるもの。
spring-integration-sample

今回はbasicのEnricherについて。
JMS Gatewayの続きで、JMSのaggregatorでもやろうかなと思いましたが、JMSとかのAdapter系をやる前に基本的なコンポーネントをやっておこうと思ったところ、このサンプルプログラムは単にenricherだけでなく、普通のgatewayタグあったりと基本そうだったのでこちらを先にやることに。
ちなみにSpring Integrationのバージョンは4.3.10.RELEASEである。

概要

enricherの概念は簡単で、途中でメッセージヘッダやペイロードに対して何か情報を付け足すというもの。
サンプルアプリはペイロードに対する情報を付加しているのみであるため、せっかくなので私はヘッダに情報を付与するものも実装してみた。

アプリの流れは以下の通り。

AP起動 -> コンソールで1~3のいずれかを入力してEnterを押下(qの場合はAP停止) -> 1~4によって、それぞれ異なる情報を付与した結果をコンソールに出力する。1 ~ 4はそれぞれ以下のようになっている。

1.PayloadであるUserオブジェクトに値を入れて表示する。
2.1のPayloadがUser.usernameになったもの
3.PayloadのMapに項目を追加して表示する。
4.Haederに属性を追加して表示する。

実装

全体像

全体のフローは以下のような感じ。

  • 1(2,3もServiceActivator用に定義してあるChannel名が異なるだけであとは同じ)

RequestChannel(gateway) -> findUserEnricherChannel -> ServiceActivator -> findUserServiceChannel -> ReplyChannel(gateway) -> 標準出力(Mainクラスで値を出力)

まずMainクラスにてgatewayタグで定義してあるservice interfaceをgetBeanしておく。そして、コンソールからの入力値に応じて呼び出すメソッドを変更する。呼び出されたメソッドに対する紐づけはgatewayタグにて行っており、<int:method>タグの値で指定したchannel名に応じて<int:enricher>タグ<int:service-activator>へと処理を委譲していく流れである。

  • 4(こちらはgatewayを使わない。というかgatewayはdefaultだとpayloadをreplyしてしまうので上手く実装できず。。。)
  • MessageChannel -> HeaderEnricher -> PollableChannel -> 標準出力(Mainクラスで値を出力)

HeaderEnricherの方は、gatewayタグで実装する方法がわからなかったため、Mainクラスで送信用のChannelと受信用のChannelをそれぞれgetBeanして、sendとreceiveして値を取り出す一番基本的な実装としている。

bean定義ファイル

サンプルプログラムの構成は3部構成(gateway, enricher, service-activator)になっているが、 bean定義が長いとQiita上、辛いので今回はservice-activatorはアノテーションで実装した。
1はそのservice-activator用のコンポーネントスキャン。2はgatewayで処理をルーティングする際に各enricherに送信するためのチャネルのbean定義である。3はgatewayの設定で、service-interfaceinterfaceを定義し、子要素にてメソッド毎に送るチャネルを変えていることを表す定義をしている。

  • spring-integration-context.xml
    <!-- 1 -->
    <context:component-scan base-package="org.ek.sample"/>

    <!-- 2 -->
    <int:channel id="findUserEnricherChannel"/>
    <int:channel id="findUserByUsernameEnricherChannel"/>
    <int:channel id="findUserWithMapEnricherChannel"/>
    <int:channel id="tryHeaderEnricherChannel"/>
    <int:channel id="requestChannel"/>
    <int:channel id="replyChannel"/>

    <!-- 3 -->
    <int:gateway id="gateway"
        default-request-timeout="5000"
        default-reply-timeout="5000"
        default-request-channel="requestChannel"
        default-reply-channel="replyChannel"
        service-interface="org.ek.sample.service.UserService">
        <int:method name="findUser"                  request-channel="findUserEnricherChannel"/>
        <int:method name="findUserByUsername"        request-channel="findUserByUsernameEnricherChannel"/>
        <int:method name="findUserWithUsernameInMap" request-channel="findUserWithMapEnricherChannel"/>
    </int:gateway>

4では、request-channel属性のチャネルを入力とするメソッド(service-activator)に処理を委ねるという意味である。また、gatewayタグのservice-interface属性で指定したメソッドの中で、ここから委譲するメソッドは引数がorg.ek.sample.domain.Userというbeanファイルであり、特に他の属性の設定がないためserviceActivatorはそれに合わせてメソッドを定義する必要がある。次に、子要素の<int:property>で表しているのはreturnするtypeがorg.ek.sample.domain.Userであるが、そのうちemailとpassword属性だけenricherの内容を採用するということを表している。つまり、この設定において、enricherで他の項目をどれだけ操作してもそれはreplyされる値に反映されない。

  • spring-integration-context.xml -続き
    <int:channel id="findUserServiceChannel"/>
    <int:channel id="findUserByUsernameServiceChannel"/>

    <!-- 4 -->
    <int:enricher id="findUserEnricher"
                  input-channel="findUserEnricherChannel"
                  request-channel="findUserServiceChannel">
        <int:property name="email" expression="payload.email"/>
        <int:property name="password" expression="payload.password"/>
    </int:enricher>

    <!-- 5 -->
    <int:enricher id="findUserByUsernameEnricher"
                  input-channel="findUserByUsernameEnricherChannel"
                  request-channel="findUserByUsernameServiceChannel"
                  request-payload-expression="payload.username">
        <int:property name="email" expression="payload.email"/>
        <int:property name="password" expression="payload.password"/>
    </int:enricher>

    <!-- 6 -->
    <int:enricher id="findUserWithMapEnricher"
                  input-channel="findUserWithMapEnricherChannel"
                  request-channel="findUserByUsernameServiceChannel"
                  request-payload-expression="payload.username">
        <int:property name="user" expression="payload"/>
    </int:enricher>

    <int:channel id="tryHeaderEnricherPollarChannel" >
        <int:queue capacity="10"/>
    </int:channel>

    <!-- 7 -->
    <int:header-enricher id="tryHeaderEnricher" 
                        input-channel="tryHeaderEnricherChannel" 
                        output-channel="tryHeaderEnricherPollarChannel">                
                        <int:header name="headerTest" value="test" />       
                        <int:header name="addedHeader" ref="stringConv" method="upperCase" />
    </int:header-enricher>
    <bean id="stringConv" class="org.ek.sample.domain.StringConverter"/>

5は4とほぼ同じだが、1つだけ異なるのは<request-payload-expression>を設定しているということ。このenricherが指定するserviceActivatorのメソッドは引数がStringであるため、ここではpayloadのusernameという項目をserviceActivatorに引数として連携するという意味である。

また、6は5と一見一緒に思えるが、6ではservice-interfaceにて、引数がMapのメソッドを呼び出したときのルートであり、ここでは5と異なり、Mapkeyでusernameという項目がある場合、それをserviceActivatorのメソッド(5と同じ)引数に連携するという意味である。

7はheaderEnricherであり、単純なinput_channelから渡ってきたMessageHeadersに2つの属性を付与するというものであり、2個目はbeanファイルの参照(ref)を使って表現した。参照しているクラスはただただ引数を大文字にして返却するという簡単なクラスを作成した。

service-interface

これはgatewayのタグで定義したインターフェースである。

public interface UserService {

    User findUser(User user);

    User findUserByUsername(User user);

    Map<String, Object> findUserWithUsernameInMap(Map<String, Object> userdata);

}

serviceActivator

実際にEnricherの役割のクラスはserviceActivatorとして定義する。xmlのところで述べた通り、1つ目のメソッドは引数の型がorg.ek.sample.domain.Userであり、2つ目のメソッドは引数の型がStringとなっている。

@MessageEndpoint
@EnableIntegration
public class UserServiceEnricher {

    private static final Logger LOGGER = Logger.getLogger(UserServiceEnricher.class);

    @ServiceActivator(inputChannel="findUserServiceChannel")
    public User findUser(User user) {

        LOGGER.info(String.format("Calling method 'findUser' with parameter %s", user));
        return new User(user.getUsername() + "-test",//usernameで付け足している文字列は反映されない
                   "secret",
                   user.getUsername() + "@springintegration.org");
    }



    @ServiceActivator(inputChannel="findUserByUsernameServiceChannel")
    public User findUserByUsername(String username) {

        LOGGER.info(String.format("Calling method 'findUserByUsername' with parameter: %s", username));

        return new User(username, "secret", username + "@springintegration.org");

    }

Mainクラス

大事なところ以外は省略するが、以下の通り。

  • Main
    User user = new User();

            if ("1".equals(input)) {

                final User fullUser = service.findUser(user);
                printUserInformation(fullUser);

            } else if ("2".equals(input)) {

                final User fullUser = service.findUserByUsername(user);
                printUserInformation(fullUser);

            } else if ("3".equals(input)) {

                // 3
                final Map<String, Object> userData = new HashMap<String, Object>();
                userData.put("username", "foo_map");
                userData.put("username2", "bar_map");
                final Map<String, Object> enrichedUserData = service.findUserWithUsernameInMap(userData);
                printUserFullInformation(enrichedUserData);

            } else if("4".equals(input)){
              // 4
                MessageChannel mc = context.getBean("tryHeaderEnricherChannel", MessageChannel.class);
                PollableChannel pc = context.getBean("tryHeaderEnricherPollarChannel", PollableChannel.class);
                mc.send(new GenericMessage<String>("foo.bar"));
                printHeaderInfo(pc.receive().getHeaders());             

            } else{         
                LOGGER.info("\n\n    Please enter '1', '2', or '3' <enter>:\n\n");
            }   



            // omitted


                // 5
                private static void printUserFullInformation(Map<String, Object> userdataMap) {
        for(Map.Entry<String, Object> entry : userdataMap.entrySet()) {
            Object val = entry.getValue();
            if(val instanceof User) {
                User user = (User) entry.getValue();
                LOGGER.info(String.format("\n\n    User found - Key of Map: '%s',  Username: '%s',  Email: '%s', Password: '%s'.\n\n",
                        entry.getKey(), user.getUsername(), user.getEmail(), user.getPassword()));
            }else {
                LOGGER.info(String.format("\n\n    User found - Key of Map: '%s',  Username: '%s',  Email: '%s', Password: '%s'.\n\n",
                        entry.getKey(), val, null, null));
            }
        }
    }

    // 6
    private static void printHeaderInfo(MessageHeaders mh) {
        LOGGER.info("\n\n    " + "headerTest :" + mh.get("headerTest") + ", addedHeader :" + mh.get("addedHeader"));
    }

多少変更しているのが3の部分で、引数のMapにusernameとusername2というkey-valueをputして引数とした。また、戻り値も全てmap.entrySet()で全て表示するようにした(5)が、以下の結果が示す通り引数に指定したmapの要素 + enricherで作成した1要素となるようである。なお、key名はxmlの<int:property>で定義しているuserであり、xmlではvalue属性にpayloadを指定していたため、UserオブジェクトがそのままMapのvalueとなる。

0:10:42.631 INFO  [main][org.ek.sample.Main] 

    User found - Key of Map: 'username2',  Username: 'bar_map',  Email: 'null', Password: 'null'.


00:10:42.632 INFO  [main][org.ek.sample.Main] 

    User found - Key of Map: 'user',  Username: 'foo_map',  Email: 'secret', Password: 'foo_map@springintegration.org'.


00:10:42.633 INFO  [main][org.ek.sample.Main] 

    User found - Key of Map: 'username',  Username: 'foo_map',  Email: 'null', Password: 'null'.

4はheaderEnricherであり、お作法通りにMessageHeaderを取り出すと、xmlで指定した通りに、headerに属性が追加される。なんでheaderだけこんな実装にしているかというと、前述の通り、gatewayのデフォルトはpayloadを返すようになっているため、payload用のenricher同様にheaderの値を取得することができなかった。ただ、それだけだと使い勝手悪そうだし、gatewayタグでなんとかheaderもenricherできるように設定できるんだろうなと思ったものの、時間のあるときに調べることにする。
以下は6の表示結果である。

00:16:42.883 INFO  [main][org.ek.sample.Main] 

    headerTest :test, addedHeader :FOO.BAR

今回はadapterじゃないgatewayのタグをはじめて使ったので、なんとなくintegrationの基礎がわかってきた気がした。ところで効果的にspring integrationを学ぶためにはどういう順序でサンプルを実装すればいんだろうか...

今回実装したコードは以下。
サンプルをいじったもの

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0