はじめに
本稿は、SpringとEJB 3.0を取り上げて比較を行うシリーズの第2回です。第1回では、それぞれのテクノロジが持続性、トランザクション管理、ステートフルネスに対しどのように対処しているかを見てきました。その続きとして今回は、メッセージング、リモート処理、依存性管理、仲介について調査した後、それぞれのテクノロジと、両者の良いところを合わせる統合方針のいくつかについて総合的な分析を行って本シリーズを締めくくりたいと思います。
メッセージング
個々の企業向けアプリケーションは孤立して存在することはあまりなく、組織の内外で稼働中の他のアプリケーションやサービスとのやり取りを行わなければなりません。この問題に対する一般的なアプローチがメッセージング機能であり、SpringとEJB 3.0はどちらもこの機能をサポートしています。メッセージングには、送信と受信という2つの基本的な処理があります。また一般に、メッセージの受信方法には明示的なメッセージ取得と自動的な通知(監視)の2つがあります。
メッセージング - 機能の比較
SpringとEJB 3.0のそれぞれがどのようにJava Message Service(JMS)メッセージの送信機能を実現しているかを探るため、前回も説明した航空券購入の手順を考えてみましょう(図1を参照)。
以下に、SpringによるTsaScreener
オブジェクトの実装を示します。
public class TsaPassengerScreenerClientSpring implements TsaPassengerScreener { private JmsTemplate jmsTemplate; private Queue queue; public void screenPassenger(final Passenger passenger) { jmsTemplate.send(queue, new MessageCreator() { public Message createMessage(Session session) throws JMSException { return session.createObjectMessage(passenger); } }); } ... }
この実装に必要な設定を以下に示します。
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate" /> <bean id="tsaQueue" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate" /> </property> <property name="jndiName"> <value>java:comp/env/queue/tsaQueue</value> </property> </bean> <bean id="tsaQCF" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate" /> </property> <property name="jndiName"> <value>java:comp/env/queue/tsaQCF</value> </property> </bean> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <constructor-arg> <ref bean="tsaQCF" /> </constructor-arg> </bean> <bean id="tsaPassengerScreener" class="org.jug.flight.booking.spring .TsaPassengerScreenerClientSpring"> <property name="jmsTemplate" ref="jmsTemplate" /> <property name="tsaQueue" ref="tsaQCF" /> </bean>
以下のEJB 3.0による実装と比較してみましょう。
@Stateless public class TsaPassengerScreenerClientEJB implements TsaPassengerScreener { @Resource(mappedName = "java:/ConnectionFactory") QueueConnectionFactory factory; @Resource(mappedName = "queue/tsaQueue") Queue queue; public void screenPassenger(Passenger passenger) { try { QueueConnection con = factory.createQueueConnection(); QueueSession session = con.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE); ObjectMessage msg = session.createObjectMessage(passenger); QueueSender sender = session.createSender(queue); sender.send(msg); session.close(); con.close(); } catch (Exception e) { e.printStackTrace(); } } }
Springによる実装では、ヘルパークラスJmsTemplate
を使うことでメッセージ送信のためのJMS APIとの連携が単純化されています。またどちらの場合も、コンテナがスクリーナーオブジェクトに対してJMSリソース(キュー接続ファクトリおよびキュー)を注入しています。注入については、後の依存性管理のセクションで詳しく説明します。この時点では、同等のEJBに比べてSpringの設定が冗長であることに注意しておけばよいでしょう。
乗客の詳細情報を伝えるメッセージが送信された後、TSAではその受信と処理が必要になります。SpringとEJB 3.0はどちらも、メッセージ着信時のオブジェクトの通知に対応できます。こうしたオブジェクトをメッセージリスナと言います。次に示すのは、Springによるメッセージ駆動POJO(MDP)の実装です。
public class TsaPassengerScreenerMDP implements MessageListener { public void onMessage(Message message) { ObjectMessage objectMessage = (ObjectMessage) message; try { Passenger passenger = (Passenger) objectMessage.getObject(); // Process message } catch (JMSException e) { e.printStackTrace(); } } }
以下は、この実装に必要な設定です。
<bean id="tsaListener" class="org.jug.flight.booking.spring.TsaPassengerScreenerMDP" /> <bean id="listenerContainer" class="org.springframework.jms.listener .DefaultMessageListenerContainer"> <property name="concurrentConsumers" value="5" /> <property name="connectionFactory" ref="tsaQCF" /> <property name="destination" ref="tsaQueue" /> <property name="messageListener" ref="tsaListener" /> </bean>
次に示す、EJB 3.0によるメッセージ駆動Bean(MDP)の実装と比較してみましょう。
@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/tsaQueue") }) public class TsaPassengerScreenerMDB implements MessageListener { public void onMessage(Message message) { ObjectMessage objectMessage = (ObjectMessage) message; try { Passenger passenger = (Passenger) objectMessage.getObject(); // Process message } catch (JMSException e) { e.printStackTrace(); } } }
このように、JMSメッセージの送受信はSpringでもEJB 3.0でも可能です。また興味深いことに、SpringのMDPによるメッセージ処理とEJBのMDBによるメッセージ処理の実装は非常によく似ています。さらに、どちらのソリューションでも並列メッセージ処理量の抑制が可能であり、メッセージ処理のためにアプリケーション内に割り当てられるリソースの量を調整できます。これは、EJBの場合はコンテナ固有の機能ですが、Springの場合はメッセージリスナのコンテナの機能です。
企業向けメッセージングソリューションでもう1つ重要なのが、分散トランザクションに参加できる機能です。メッセージ処理には複数のリソースがかかわることも多いため(主にメッセージングおよびデータベースのリソース)、こうしたリソースに対する作業はアトミックに実行する必要があります。SpringとEJB 3.0はどちらも、メッセージングアクティビティによって分散トランザクションへの参加が可能です。ただしEJB 3.0では、メッセージの受信確認とトランザクションの正常な完了とを結び付けるために、コンテナ管理下のトランザクションを使う必要があります。そうしないと、前に処理されたメッセージが再送信される可能性があるのです。Springでは、JTAトランザクションを使用する場合や、メッセージコンテナが「sessionTransacted」として定義されている場合に、同様のことが実現できます。どちらの場合も、メッセージの再送信を許可してこのケースをプログラム的に処理することも可能です。
SpringとEJBのメッセージングサポートにおけるもっと顕著な違いは、メッセージ到着の監視用に設定可能なオブジェクトの種類です。EJBのメッセージリスナはEJBコンポーネントでなければならないのに対し、Springでは受信メッセージに対して任意のPOJOをアクティブ化できます。どちらの場合もメッセージリスナがインターフェイスを実装している必要がありますが、いずれもJMSメッセージリスナのためのjavax.jms.MessageListener
インターフェイスをサポートしています。ただし、EJB仕様バージョン2.1のMDBでは、JCAのCCIコンテナによってMDBをアクティブ化できるjavax.resource.cci.MessageListener
など、その他のメッセージングインターフェイスもサポートしています。
メッセージング - 機能以外の比較
ほとんどのJavaアプリケーションは、基本となるメッセージプロバイダとのやり取りにJMSを使用します。この点はSpringでもEJB 3.0でも同じです。しかしSpringには、アプリケーションサーバから(JNDIを介して)必要なメッセージリソースを取得するのに役立つテンプレートや、メッセージの作成、送信、受信に関するテンプレートが用意されています。一方のEJB 3.0では、その依存性注入の機能を介してメッセージリソースをクラスに注入できますが、簡単にJMS APIが利用できるSpringのテンプレートに相当するものは用意されていません。またSpringは、Java
オブジェクトをメッセージコンテンツに自動的に変換できるメッセージコンバータも備えています。
標準化という点では、どちらのアプローチもJavaのメッセージング標準であるJMSをサポートしています。しかし、実装の点では、MDBがJCP標準に準拠したものであるのに対し、SpringのMDPはそうではありません。ただし、標準ではないもののMDPの移植性は高く、SpringのランタイムはJSEおよびJEEの任意のコンテナを実行できます。
メッセージング - まとめ
表1に、メッセージングのサポートについてのSpringとEJB 3.0の主な違いをまとめておきます。
機能 | Spring | EJB 3.0 |
メッセージ監視 | √ | √ |
リスナーの種類 | javax.jms.MessageListener インターフェイスを実装した任意のPOJO | そのメッセージの種類に合った任意のインターフェイス(通常はjavax.jms.MessageListener )を実装したMDB |
メッセージの抑制 | √ | √(コンテナに依存) |
メッセージングのサポート | √(テンプレート、メッセージコンバータ) | ―(JMS API以上のサポートなし) |
分散トランザクションのサポート | √ | √ |