概要
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→xxxListenerroot.listener.XXXListener→XXXListener
listenerパッケージ以下にサブパッケージを作成することもできます.
<root>.listener.aaa.XxxListener<root>.listener.bbb.YyyListenerImpl
サブパッケージを使った場合,コンポーネント名はサブパッケージ名がプレフィックスとして先頭に付加されます.
root.listener.aaa.XxxListener→aaa_xxxListenerroot.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ヘッダ設定します.
