EJB 3.0(Public Draft)入門記 Simplified API Chapter 6

Chapter 6 です。章立てだけで考えれば「Simplified API」ドキュメントの中間地点にきました。今振り返ると、眠かったときとかの文章は何か変です。それに結構スローペースかも。実は最近Javaな仕事じゃなくなってしまったので、更なるスローペースが懸念されます。でも次回のJ2EE勉強会の前にはSimplified APIは終えるつもりです。発表のネタを考えないといけない、どうしよ。

今日はメッセージ駆動型Beanがお題です。chapter 4 や chapter 5 は例が載っていたのになぜかこの章は例がないんです。さらにたったの2ページ。簡単だから例がいらないとか?メッセージ駆動型Beanをちゃんと使ったことない僕には厳しいかも。

そういえば言うの忘れてましたが、入門記の最初から一応用語の訳はEnterprise JavaBeans 第3版を参考にしてます(そんなに厳密にチェックしてないですが)。ということでここではメッセージドリブンビーンと言わずにメッセージ駆動型Beanと言っています。

では突入〜。

6.1 Requirements for Message-Driven Beans

6.1.1 Business Interfaces

メッセージ駆動型BeanのビジネスインタフェースはMessageListenerインタフェースです。たとえば、JMSの場合javax.jms.MessageListenerインタフェースがそれにあたります、とあります。
メッセージ駆動型BeanといえばJMSというイメージがあったのですが、上述のEnterprise JavaBeans 第3版によれば、EJB 2.1の時点でメッセージ駆動型BeanはJMS以外のメッセージングシステムをサポートできるようなっているんだそうです。ですから例ではJMSが挙げられていますが、JMS以外の場合はそのメッセージングシステムをあらわすMessageListenerインタフェースがビジネスインタフェースとして使われるということだと思われます。

素人考えですが、javax.jms.MessageListenerのonMessageメソッドで受け取るオブジェクトがMessage型に制限されずにPOJOだったらいいのになぁ、とか思います。S2JMSが目指すところってそういうところ?

6.1.2 Bean Class

メッセージ駆動型BeanはMessageDrivenアノテーションでアノテートされるか、デプロイメント記述でメッセージ駆動型Beanとして定義される必要があります。Beanクラスはjavax.ejb.MessageDrivenBeanを実装する必要がありません。

ステートレス/ステートフルセッションBeanと同じような考え方ですね。

6.1.3 Callbacks for Message-Driven Beans

メッセージ駆動型Beanに対しては次のコールバックがサポートされます。

  • PostConstruct
  • PreDestroy

PostConstructはDependecy InjectionとMessageListenerインタフェースメソッドを呼び出す間に実行されます。
PreDestroyはプールから除去されたりインスタンスが破棄されるときに実行されます。
PostConstructもPreDestroyも特定のトランザクションコンテキストやセキュリティコンテキスト内では実行されません。

これはステートレスセッションBeanとほとんど同じですね。

6.1.4 Dependency Injection

Chapter 8 で説明するそうです。ただDependency Injectionはビジネスメソッドやコールバックの実行前に行われるとあります。
この記述、ステートレス/ステートフルセッションBeanのとこでも出てきたなぁ。Chapter 8 じゃなくてもっと前でDependecy Injection説明するばいいのに、とか思いました。

6.1.5 Interceptors for Message-Driven Beans

AroundInvokeメソッド(というかAroundInvokeアノテーション)もメッセージ駆動型Beanに使えるそうです。
この記述もやっぱりステートレス/ステートフルセッションBeanのとこでも出てました。

6.2 Other Requirements

メッセージ駆動型Beanに適用される完全な要件は「EJB Core Contracts and Requirements」に定義されています、とあります。ほんとうはちゃんと「EJB Core Contracts and Requirements」も参照して気になるところをチェックすべきですね。まぁあまり細かく見すぎてもアレなので...、はい、いいわけです。


なんとChapter 6 はたったこれだけでした。


では、実験コーナー。JBoss EJB 3.0にくっついていたサンプルを少しだけ変えて動かして見ます。作るのは4つ

  • JBossにサービスを登録するためのxml。よく知らないのですがQueueを登録するの必要みたいです。
  • メッセージ駆動型Bean。そういえば、ドキュメントに説明がありませんでしたが、Queueなどの情報をActivationConfigPropertyアノテーションを使って記述します(なぜ説明がないのか不思議だ)。PostConstructとPreDestroyのコールバックを使います。あとどのインスタンスに関係するメソッドが呼ばれているのかを見るためにインスタンスの文字列表現も表示してみます。出力を見やすくために変なスペースもいっしょに出力してますがまぁ気にしない。
  • インターセプタクラス。Messageクラスの文字列表現の情報が多いので、引数の値を出力しないシンプルなTraceInterceptorを使ってみます。クラス名の変わりにインスタンスの文字列表現を出力してます。
  • クライアント。JMSを使ってメッセージを送信します。

study-ejb-service.xml:これはejb3モジュールをデプロイするディレクトリと同じところに配置します。

<?xml version="1.0" encoding="UTF-8"?>
<server>
   <mbean code="org.jboss.mq.server.jmx.Queue"
      name="jboss.mq.destination:service=Queue,name=study">
      <attribute name="JNDIName">queue/study/ejb</attribute>
      <depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
   </mbean>
</server>


ExampleMDB:メッセージ駆動型Beanです。

import javax.ejb.ActivationConfigProperty;
import javax.ejb.Interceptor;
import javax.ejb.MessageDriven;
import javax.ejb.PostConstruct;
import javax.ejb.PreDestroy;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(activateConfig = {
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
    @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/study/ejb") })
    @Interceptor(SimpleTraceInterceptor.class)
public class ExampleMDB implements MessageListener {
  public void onMessage(Message msg) {
    TextMessage text = (TextMessage)msg;
    try {
      System.out.println("      " + this + " : " + text.getText());
    } catch(JMSException e) {
      e.printStackTrace();
    }
  }
  
  @PostConstruct
  public void init() {
    System.out.println("      " + this + "#init()");
  }
  
  @PreDestroy
  public void destroy() {
    System.out.println("      " + this + "#destroy()");    
  }
}


SimpleTraceInterceptor

import javax.ejb.AroundInvoke;
import javax.ejb.InvocationContext;

public class SimpleTraceInterceptor {

  @AroundInvoke
  public Object trace(InvocationContext inv) throws Exception {
    StringBuffer buf = new StringBuffer(100);
    buf.append(inv.getBean());
    buf.append("#");
    buf.append(inv.getMethod().getName());
    buf.append("()");
    System.out.println("BEGIN " + buf);
    Object ret = null;
    Exception cause = null;
    try {
      ret = inv.proceed();
      buf.append(" : ");
      buf.append(ret);
    } catch (Exception e) {
      buf.append(" Exception:");
      buf.append(e);
      cause = e;
    }
    System.out.println("END   " + buf);
    if (cause == null) {
      return ret;
    } else {
      throw cause;
    }
  }
}


Client:クライアントです。

import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;

public class Client {
  public static void main(String[] args) throws Exception {
    QueueConnection cnn = null;
    QueueSender sender = null;
    QueueSession session = null;
    
    InitialContext ctx = new InitialContext();
    Queue queue = (Queue) ctx.lookup("queue/study/ejb");
    QueueConnectionFactory factory = (QueueConnectionFactory) ctx
        .lookup("ConnectionFactory");
    cnn = factory.createQueueConnection();
    session = cnn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
    TextMessage msg = session.createTextMessage("Hello World");
    sender = session.createSender(queue);
    
    for (int i = 0; i < 5; i++) {
      sender.send(msg);
    }
  }
}


JBossコンソール出力結果:メッセージ駆動型Beanとインターセプタをejb3モジュールとしてデプロイしてクライアントを実行するとJBossコンソールに出力されます。同じインスタンスが使いまわされてることがわかります。たまたまこの結果だとわからないですけど、複数のメッセージ駆動型Beanが同時に実行されていました。

01:21:55,959 INFO  [STDOUT]       study.ejb.ExampleMDB@1dcb3cd#init()
01:21:55,959 INFO  [STDOUT] BEGIN study.ejb.ExampleMDB@1dcb3cd#onMessage()
01:21:55,959 INFO  [STDOUT]       study.ejb.ExampleMDB@1dcb3cd : Hello World
01:21:55,959 INFO  [STDOUT] END   study.ejb.ExampleMDB@1dcb3cd#onMessage() : null
01:21:55,969 INFO  [STDOUT] BEGIN study.ejb.ExampleMDB@1dcb3cd#onMessage()
01:21:55,969 INFO  [STDOUT]       study.ejb.ExampleMDB@1dcb3cd : Hello World
01:21:55,969 INFO  [STDOUT] END   study.ejb.ExampleMDB@1dcb3cd#onMessage() : null
01:21:55,989 INFO  [STDOUT] BEGIN study.ejb.ExampleMDB@1dcb3cd#onMessage()
01:21:55,989 INFO  [STDOUT]       study.ejb.ExampleMDB@1dcb3cd : Hello World
01:21:55,989 INFO  [STDOUT] END   study.ejb.ExampleMDB@1dcb3cd#onMessage() : null
01:21:55,989 INFO  [STDOUT]       study.ejb.ExampleMDB@1b94ed3#init()
01:21:55,989 INFO  [STDOUT] BEGIN study.ejb.ExampleMDB@1b94ed3#onMessage()
01:21:55,989 INFO  [STDOUT]       study.ejb.ExampleMDB@1b94ed3 : Hello World
01:21:55,989 INFO  [STDOUT] END   study.ejb.ExampleMDB@1b94ed3#onMessage() : null
01:21:55,999 INFO  [STDOUT]       study.ejb.ExampleMDB@1200089#init()
01:21:55,999 INFO  [STDOUT] BEGIN study.ejb.ExampleMDB@1200089#onMessage()
01:21:55,999 INFO  [STDOUT]       study.ejb.ExampleMDB@1200089 : Hello World
01:21:55,999 INFO  [STDOUT] END   study.ejb.ExampleMDB@1200089#onMessage() : null


ところでPreDestroyがちゃんと呼ばれているのか疑問です。この例とは別に100回ほどメッセージ送信して複数インスタンスが作られることを確認したんですが、時間をおいてみてもPreDestroyが呼ばれている様子がないのです。プールする数の設定が十分大きいということかも?
ステートレスセッションBeanで実験したときも実はいつPreDestroyが呼ばれているかわかりませんでした。
まぁあんまし重要じゃないかな。とりあえず放置。