概要
S2JMS-Containerは,POJO (Plain Old Java Object) がJMSのAPIを意識することなく, 受信したJMSメッセージを処理できるようにするためのコンポーネント群です.
S2JMS-Containerを利用することで,JMSのAPIに依存しない受信コンポーネント (Message-Driven POJO) を作成することができ,特に既存の資産をJMS受信アプリケーションに流用することが容易となります. このメッセージ受信コンポーネントを,「リスナ・コンポーネント」と呼びます.
リスナ・コンポーネントはEJBのMessage-Driven Beanと似ていますが,
javax.jms.MessageLister
を実装したり,
受信したjavax.jms.Message
を直接扱う必要がありません.
S2JMS-Containerは,インバウンド通信によりJMSメッセージを受信すると,
JMSメッセージのヘッダやプロパティ,ボディをリスナ・コンポーネントにバインドしてメッセージを処理するメソッドを呼び出します
(このメソッドを,「リスナ・メソッド」と呼びます).
このため,リスナ・コンポーネントはjavax.jms.MessageListener
インターフェースを実装したり,
JMSメッセージを直接扱ったりすることなく,メッセージを処理することができます.
メッセージのバインド
S2JMS-Containerを使用すると,受信したJMSメッセージのヘッダ,プロパティ,およびボディを, リスナ・コンポーネントにバインドすることができます.
メッセージヘッダのバインド
リスナ・コンポーネントに対して,受信したメッセージのヘッダをバインドします.
バインドしたいフィールドまたはsetterメソッドに @JMSHeader
アノテーションを指定することで,
該当するメッセージヘッダがバインドされます.
以下のように,@JMSHeader
アノテーションのみを指定した場合,
フィールド名に一致するメッセージヘッダがバインドされます.
この場合,フィールド名は"correlationID
" ("JMS
"というプレフィックスをつけない)
または"JMSCorrelationID
" ("JMS
"というプレフィックスをつける) のどちらでも有効です.
以下の例では,correlationID
フィールドに,
JMSCorrelationID
ヘッダの値がバインドされます.
@JMSHeader private String correlationID;
以下のように,name
メンバを利用してヘッダ名称を指定することもできます.
この場合,フィールド名には関係なくname
メンバで指定されたヘッダ名が使用されます.
この場合もヘッダ名称には「JMS」プレフィックスをつけてもつけなくても構いません.
@JMSHeader(name = "deliveryMode") private int mode;
@JMSHeader
アノテーションには,
S2ContainerのbindingType
メンバも指定することができます.
@JMSHeader(bindingType = BindingType.MUST) private String JMSMessageID;
bindingType
については,以下の表を参照してください.
bindingType | 説明 |
---|---|
MUST |
バインドに失敗した場合、例外が発生します. |
SHOULD (デフォルト) |
バインドに失敗した場合、警告を通知します. |
MAY |
バインドに失敗した場合,何もおきません. |
NONE |
バインドを行いません. |
setterメソッドに対しても@JMSHeader
アノテーションを指定することができます.
name
メンバを省略した場合,
メソッド名から先頭の"set
"を取り除いた名前がヘッダ名となります.
この場合もヘッダ名称には「JMS」プレフィックスをつけてもつけなくても構いません.
private int priority; @JMSHeader(name = "JMSPriority", bindingType = BindingType.MUST) public void setPriority(int priority) { this.priority = priority; }
メッセージプロパティのバインド
ヘッダと同様にメッセージのプロパティもバインドすることができます.
以下のように,フィールドやsetterメソッドに@JMSProperty
アノテーションのみを指定した場合,
フィールド名に一致するメッセージプロパティがバインドされます.
この例の場合,「foo」という名前のプロパティがバインドされます.
@JMSProperty private int foo;
@JMSProperty public void setFoo(int foo){ this.foo = foo; }
また,以下のようにname
メンバを利用してプロパティ名を明示することもできます.
@JMSProperty(name = "baz") public void setBar(String bar) { this.bar = bar; }
@JMSProperty
アノテーションの付けられたフィールドまたはプロパティの型が
java.util.Map
の場合は,
全てのメッセージプロパティの名前とその値のマッピングを持つMap<String, Object>
が
バインドされます.
@JMSProperty private Map<String, Object> properties;
@JMSProperty
アノテーションにも,@JMSHeader
アノテーションと同様に,
bindingType
メンバによるバインディングの制御を指定することができます.
setterメソッドに対しても@JMSProperty
アノテーションを指定することができます.
name
メンバを省略した場合,
メソッド名から先頭の"set
"を取り除いて先頭を小文字にした名前がプロパティ名となります
("set
"を取り除いた名前の先頭2文字がどちらも大文字の場合は先頭を小文字にしません).
メッセージボディのバインド
ヘッダと同様にメッセージのボディもバインドすることができます.
MapMessage
を除いて,
以下のようにフィールドやsetterメソッドに@JMSBody
アノテーションのみを指定した場合,
メッセージのボディがバインドされます.
この例の場合,TextMessage
のボディである文字列がバインドされます.
@JMSBody private String text;
@JMSBody public void setText(String text){ this.text = text; }
MapMessage
の場合,
以下のようにフィールドやsetterメソッドに@JMSBody
アノテーションのみを指定した場合,
メッセージのボディからフィールド名に一致するマッピングの値がバインドされます.
この例の場合,MapMessage
のボディから,
foo
というキーにマッピングされている値がバインドされます.
@JMSBody private int foo;
@JMSBody public void setFoo(int foo){ this.foo = foo; }
また,以下のようにname
メンバを利用してキーを明示することもできます.
@JMSBody(name = "baz") public void setBar(String bar) { this.bar = bar; }
ただし,フィールドまたはプロパティの型がMap
の場合は,
MapMessage
のボディが持つ全てのマッピングを含んだ
Map<String, Object>
がバインドされます.
@JMSBody public void setBaz(Map baz) { this.baz = baz; }
@JMSBody
アノテーションにも,@JMSHeader
アノテーションと同様に,
bindingType
メンバによるバインディングの制御を指定することができます.
リスナ・メソッド
受信したメッセージがバインドされた後に呼び出されるのがリスナ・メソッドです.
リスナ・メソッドは,デフォルトではメソッド名がonMessage
のメソッドです.
public void onMessage() { ... }
引数を一つ持つこともできます.
引数の型はメッセージのボディ型か,javax.jms.Message
です.
public void onMessage(String text) { ... }
public void onMessage(Message message) { ... }
また,以下のように@OnMessage
アノテーションで任意の名前のメソッドをリスナ・メソッドにすることができます.
@OnMessage public void foo() { ... }
@OnMessage public void bar(Map body) { ... }
いずれも,戻り値の型は任意です. S2JMS-Containerが戻り値を利用することはありません.
SMART deploy
リスナ・コンポーネントはSMART deployで自動登録することができます. HOT deployモードでは,S2JMS-Containerを利用したアプリケーションを再起動することなく, リスナ・コンポーネントを修正して確認することができます.
リスナ・コンポーネントの規約
リスナ・コンポーネントのFQNは次のようになります.
<root>.listener.XxxListener
インタフェースと実装を分離する場合は次のようになります.
<root>.listener.XxxListener
(インタフェース)<root>.listener.impl.XxxListenerImpl
()
これらのコンポーネント名は,クラスの単純名 (FQNからパッケージを除いた名前) の先頭を小文字にしたものになります. ただし,クラス名の先頭2文字が大文字の場合はコンポーネント名の先頭2文字も大文字となります.
root.listener.XxxListener
→xxxListener
root.listener.XXXListener
→XXXListener
listener
パッケージ以下にサブパッケージを作成することもできます.
<root>.listener.aaa.XxxListener
<root>.listener.bbb.YyyListenerImpl
サブパッケージを使った場合,コンポーネント名はサブパッケージ名がプレフィックスとして先頭に付加されます.
root.listener.aaa.XxxListener
→aaa_xxxListener
root.listener.aaa.XXXListener
→aaa_XXXListener
コンポーネント名は,インバウンド通信の設定を行うdiconファイルで使用します.
以下のように,JMSContainerImpl
の定義の中で
リスナ・コンポーネントのコンポーネント名を指定します.
<!-- S2JMS-Container の設定 --> <component class="org.seasar.jms.container.impl.JMSContainerImpl"> <!-- アプリケーションのリスナ・コンポーネントの名前を指定します (複数指定可) --> <initMethod name="addMessageListener"> <arg>"aaa_XxxListener"</arg> </initMethod> </component>
JMSContainerImpl
の設定については,
「JMSコンテナの設定
」を参照してください.
diconファイルの設定
creator.dicon
creator.dicon
ファイルにListenerCreator
の定義を追加します.
<component class="org.seasar.jms.container.creator.ListenerCreator"/>
デフォルトでは,リスナ・コンポーネントはリクエストスコープになります.
customizer.dicon
customizer.dicon
ファイルにlistenerCustomizer
の定義を追加します.
<component name="listenerCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain"> <initMethod name="addCustomizer"> <arg>traceCustomizer</arg> </initMethod> </component>
JMSメッセージをトランザクショナルに受信するように設定されている場合,
リスナ・コンポーネントはトランザクション境界内で呼び出されるため,
listenerCustomizer
にトランザクション制御のカスタマイザを設定する必要はありません.
JMSメッセージをトランザクショナルに受信する設定については,
「MessageEndpointFactory
」を参照してください.
フィルタ
フィルタを作成することにより,S2JMS-Containerが受信したJMSメッセージをリスナ・コンポーネントにバインドするまでの間に, 様々な処理を行うことができます.
フィルタの作成
フィルタは,次のインタフェースを実装したクラスです.
org.seasar.jms.container.filter.Filter
Filter
インタフェースは次のメソッドを定義しています.
void doFilter(Message message, FilterChain chain) throws Exception
第2引数で渡されるFilterChain
は次のインタフェースです.
org.seasar.jms.container.filter.FilterChain
FilterChain
は次のメソッドを定義しています.
void doFilter(Message message) throws Exception
フィルタのdoFilter(Message, FilterChain)
メソッドでは,
メッセージを処理してFilterChain#doFilter(Message)
を呼び出すことで
後続のフィルタに制御を移すことができます.
標準のフィルタ
S2JMS-Containerでは,標準で次のフィルタを提供しています.
org.seasar.jms.container.filter.impl.TraceFilter
- リスナコンポーネントの呼び出し前後にトレースをログ出力するコンポーネントです.
org.seasar.jms.container.filter.impl.DumpMessageFilter
- JMSメッセージの内容をログにダンプ出力するフィルタです.
org.seasar.jms.container.filter.impl.RollBackFilter
- リスナコンポーネントまたはフィルタで例外が発生した場合にトランザクションをロールバックするフィルタです.
org.seasar.jms.container.filter.impl.ExternalContextFilter
- JMSメッセージを外部コンテキストに設定するフィルタです. このフィルタにより,受信したJMSメッセージがリスナ・コンポーネントにバインドできるようになります.
org.seasar.jms.container.filter.impl.HotdeployFilter
- HOT deployを有効にするためのフィルタです.
これらのフィルタは,上記の記述順でjms-default-filter.dicon
に定義されています.
jms-default-filter.dicon
はS2JMS-ContainerのJarファイルの中に含まれています.
フィルタの設定
フィルタを使用するには,インバウンド通信の設定を行っている
(JMSContainerImpl
を定義している)
diconファイルから,フィルタを定義しているdiconファイルをインクルードします.
S2JMS-Containerの標準フィルタを使用する場合は次のようになります.
<include path="jms-default-filter.dicon"/> ・・・ <component class="org.seasar.jms.container.impl.JMSContainerImpl"> ・・・ </component>
JMSContainerImpl
の設定については,
「JMSコンテナの設定
」を参照してください.
Tips
リクエスト~リプライ
要求メッセージを受信した後に応答メッセージを送信する,リクエスト~リプライ型のアプリケーションは次のように実現することができます.
public class RequestReplyListener { @Binding(bindingType = BindingType.MUST) private MessageSender sender; @JMSHeader private String messageID; @JMSBody private String requestMessage; public void onMessage() { String replyMessage = ...; sender.setCorrelationID(messageID); sender.send(replyMessage); } }
受信した要求メッセージのJMSMessageID
ヘッダの値を,
応答メッセージのJMSCorrelationID
ヘッダ設定します.