Spring で JMS を使ったリモートメソッド呼び出し


Spring の JmsInvoker を使用すると、JMS経由でのRMI処理が簡単に書けます。ここでは、サービスを提供するサーバ上の AccountService をクライアントからJMS経由で呼び出す例を見てみます。

使用ライブラリ

Spring関連

  • spring.jar(2.5.5)
  • spring-test.jar(2.5.5)

ActiveMQ関連

  • activemq-all-5.2.0.jar
  • xbean-spring-3.4.jar

JUnit

サーバが提供するサービス

サービスとして以下のインターフェースを考えます。

package etc9.service;

public interface AccountService {
    public String getAccountName(Long accountId);
}

サービスの実装は以下のような実装としておきます。

package etc9.service;

public class AccountServiceImpl implements AccountService {

    @Override
    public String getAccountName(Long accountId) {
        if (accountId > 10) return "200-" + accountId;
        return "000-" + accountId;
    }
}

サービスを利用するテストケース

上記サービスを利用するテストケースを以下とします。

package etc9.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AccountServiceTest {

    @Autowired
    private AccountService accountServiceClient;
    
    @Test
    public final void testCancelAccount() {
        System.out.println(
                accountServiceClient.getAccountName(new Long(12)));
    }
}

JMSの設定

SpringJUnit4ClassRunner より呼び出す AccountServiceTest-context.xml を用意します。ここでは、サーバとクライアントを同一ホストで動作させるため、設定も1つのファイルでまとめて記述します。AccountServiceTest と同じディレクトリに AccountServiceTest-context.xml を作成します。
まずは、ActiveMQ の設定から見ていきます。http://activemq.apache.org/schema/coreのネームスペースで Spring コンテキストに ActiveMQ の設定を合わせて記述します。

    <!-- ActiveMQ Broker の設定 -->
    <amq:broker id="broker" useJmx="false" persistent="false">
      <amq:transportConnectors>
        <amq:transportConnector uri="tcp://localhost:0" />
      </amq:transportConnectors>
    </amq:broker>
    
    <!--  ActiveMQ Destination  -->
    <amq:queue id="destination" physicalName="jms.service.test" />
    
    <!-- embedded モードでの MessageBroker を使用  -->
    <amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost" />

vm://localhost の設定にて組み込みモードで ActiveMQ を起動するため、別途 ActiveMQ を起動する必要はありません。

サーバ側のサービス設定

JmsInvokerServiceExporter にて公開するサービスを設定し、DefaultMessageListenerContainer のリスナーとして登録しています。

    <!-- コネクションファクトリの定義  -->
    <bean id="jmsServerConnectionFactory" 
          class="org.springframework.jms.connection.SingleConnectionFactory">
      <property name="targetConnectionFactory"><ref local="jmsFactory" /></property>

    </bean>

    <!-- リモートサービスの定義  -->
    <bean id="accountServiceServer"
        class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
      <property name="serviceInterface" value="etc9.service.AccountService"/>
      <property name="service">
        <bean class="etc9.service.AccountServiceImpl"/>
      </property>
    </bean>

    <!-- JMSのリスナのためのコンテナ定義  -->
    <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
      <property name="connectionFactory" ref="jmsServerConnectionFactory"/>
      <property name="destination" ref="destination"/>
      <property name="messageListener" ref="accountServiceServer"/>
    </bean>

クライアント側のサービス設定

JmsInvokerProxyFactoryBean として公開サービスのインターフェースを登録しています。このプロキシを経由してサービスを呼び出すことで、JMS経由でのサービスが利用できます。

    <!-- コネクションファクトリ定義  -->
    <bean id="jmsClientConnectionFactory" 
          class="org.springframework.jms.connection.SingleConnectionFactory">
      <property name="targetConnectionFactory" ref="jmsFactory" />
    </bean>

    <!-- JMS経由のサービスプロキシ定義  -->
    <bean id="accountServiceClient"
        class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
      <property name="serviceInterface" value="etc9.service.AccountService"/>
      <property name="connectionFactory" ref="jmsClientConnectionFactory"/>
      <property name="queue" ref="destination"/>
    </bean>

実行結果

テストケースを実行すると、以下のような出力が得られます。

200-12

設定ファイルの記述が多少面倒ですが、サービスのリモート呼出が簡単に実現できます。なお、サービス呼出時には引数や戻り値はシリアライズされるため、Serializable である必要があります。

AccountServiceTest-context.xml全文

<?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:amq="http://activemq.apache.org/schema/core"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://activemq.apache.org/schema/core 
                           http://activemq.apache.org/schema/core/activemq-core.xsd">

    <amq:broker id="broker" useJmx="false" persistent="false">
      <amq:transportConnectors>
        <amq:transportConnector uri="tcp://localhost:0" />
      </amq:transportConnectors>
    </amq:broker>
    <amq:queue id="destination" physicalName="jms.service.test" />
    <amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost" />


    <bean id="jmsServerConnectionFactory" 
          class="org.springframework.jms.connection.SingleConnectionFactory">
      <property name="targetConnectionFactory"><ref local="jmsFactory" /></property>
    </bean>
    <bean id="accountServiceServer"
        class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
      <property name="serviceInterface" value="etc9.service.AccountService"/>
      <property name="service">
        <bean class="etc9.service.AccountServiceImpl"/>
      </property>
    </bean>
    <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
      <property name="connectionFactory" ref="jmsServerConnectionFactory"/>
      <property name="destination" ref="destination"/>
      <property name="messageListener" ref="accountServiceServer"/>
    </bean>


    <bean id="jmsClientConnectionFactory" 
          class="org.springframework.jms.connection.SingleConnectionFactory">
      <property name="targetConnectionFactory" ref="jmsFactory" />
    </bean>
    <bean id="accountServiceClient"
        class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
      <property name="serviceInterface" value="etc9.service.AccountService"/>
      <property name="connectionFactory" ref="jmsClientConnectionFactory"/>
      <property name="queue" ref="destination"/>
    </bean>

</beans>