木曜日 8 31, 2006
木曜日 8 31, 2006
JBoss Seamはバージョン1.0.1からGlassFishをサポートするようになりました。SJS AppServer 9 (glassfish)でSeamを動作させる方法の例は、Brian Leonard's Blogの以下のエントリを参照して頂くのがよいと思います。
上記の手順を参照すると、JBoss用に作られたSeamアプリケーションをGlassFish用に変更するポイントは以下の2点であることが分かります。
前者のライブラリの差分を埋める作業はたいした問題ではありませんが、後者のJNDI問題はSeam on GlassFishにとってちょっと厄介な問題です。もし、Seamで使用するセッション・ビーンが数十個あったら、GlassFishの場合はそれと同じ数だけ<ejb-local-ref>をweb.xmlに定義しなければならなくなってしまいます。
web.xmlにおける<ejb-local-ref>定義の煩わしさは、J2EE 1.4の時代から問題になっていましたが、Java EE 5からは@EJBアノーテーションによるEJB参照のインジェクションによって解決したことになっていました。JSF+EJB3の場合はバッキング・ビーンにEJB参照をインジェクションすることが期待されていたわけですが、Seamではそういうわけにはいきません。Seam+JSF+EJB3のアプリケーションではバッキング・ビーンを端折って、JSFページから直接セッション・ビーンのプロパティやビジネス・メソッドにバインディングできるようになっています([1])。この場合、EJBをアロケートする作業はWeb層のSeamランタイムが内部でJNDIルックアップを行っているわけですが、Seamランタイムは以下のようなデフォルトルールでejb-nameからのJNDI物理名を連想するようになっています。
<context-param>
<param-name>org.jboss.seam.core.init.jndiPattern</param-name>
<param-value>application-name/#{ejbName}/local</param-value>
</context-param>
JBossはベンダ独自仕様でEJBローカルに対してもJNDI物理名が付与されるようになっているため上記のcontext-paramの宣言さえ不要なのですが、GlassFishではEJBローカル参照にJNDI物理名は割り当てられないことになっています。GlassFishでは、EJBローカル参照の取得には必ずjava:comp/envで始まる環境コンテキスト名(論理名)を使用しなければならないため、web.xmlにおけるSeamのJNDI連想ルールを以下のように明示し、
<context-param>
<param-name>org.jboss.seam.core.init.jndiPattern</param-name>
<param-value>java:comp/env/ejb/#{ejbName}</param-value>
</context-param>
さらに、使用する全てのセッション・ビーンについて、以下のような<ejb-local-ref>宣言が必要になってしまいます。
<ejb-local-ref>
<ejb-ref-name>ejb/BookingListAction</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.BookingList</local>
<ejb-link>BookingListAction</ejb-link>
</ejb-local-ref>
<ejb-local-ref>
<ejb-ref-name>ejb/RegisterAction</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>org.jboss.seam.example.booking.Register</local>
<ejb-link>RegisterAction</ejb-link>
</ejb-local-ref>
:
Java EEの仕様上、EJBローカル参照はEARアプリケーションを超えて参照することはできないため、グローバルな物理名を与える必要はなかったかもしれませんが、Java EE 5のEoDの観点からすれば、上記のような<ejb-local-ref>宣言列挙の状態は望むところではなかったでしょう。実際、GlassFishでもベンダ独自仕様としてEJBローカル参照にもデフォルトのJNDI物理名が与えられるようにしてほしいという要望は上がっているようです。
確かに、@EJBによるインジェクションができるオブジェクトは限られていますし、ステートフルなセッション・ビーンの場合はJNDIルックアップは避けられないわけですから、是非とも<ejb-local-ref>なしでEJBローカル参照が取得できるようになるように改善してほしいものです。
現状のSeam on GlassFish環境で、アプリケーション開発者ができるワークアラウンドとしては、web.xmlに<ejb-local-ref>を列挙する代わりに、@EJBが機能するWeb層のなんらかのオブジェクト(以前のエントリ「Java EE 5:Dependency Injection (2)」のインジェクション可能なクラスの一覧を参照)に、@EJBアノーテーションを列挙することが考えられます。これは、Web層における環境コンテキスト名のスコープは同一WAR内であり、WAR内のどこかで宣言した@EJB宣言の情報は同一WAR内の任意のクラスで参照可能であるというJava EE 5仕様を利用しています。例えば、ServletContextListenerを使用した場合の例として、以下のようなクラスを作成し、WARファイルに含めれば<ejb-local-ref>の宣言は不要になります。
@EJBs({
@EJB(name="ejb/BookingListAction", beanInterface=BookingList.class),
@EJB(name="ejb/RegisterAction", beanInterface=Register.class),
:
})
public class JndiResourceRefs implements ServletContextListener {
public void contextDestroyed(ServletContextEvent sce) {}
public void contextInitialized(ServletContextEvent sce) {}
}
<ejb-local-ref>の宣言と@EJBの宣言は同じ意味のメタ情報ですが、Javaのソースで宣言した方が記述量が少なく、NetBeansなどのJDK 1.5対応のIDEを使用すればbeanInterface要素のインタフェース・クラスでコンプリーションが効くため、web.xmlをメンテナンスするよりも開発効率はいいと思います。
念のため、GrassFish v1 b48(Milestone 7)で動作確認もしてみましたが、web.xmlで<listener>宣言をしなくても、(リスナとしては機能しませんが)@EJBアノーテーションの効果はあるようです。
<!-- リスナ登録はなくても、@EJB宣言の効果はあった。
<listener>
<listener-class>com.example.JndiResourceRefs</listener-class>
</listener>
-->
幸いなことに、環境エントリ名を使用して修正したSeamアプリケーションは、JBoss上でもGrassFish上でも同じように動作することが期待されます([2])。JNDI物理名の代わりに環境コンテキスト名を使用することで、手間と引き換えにベンダ・コンパチビリティを得ることができる一つの例ですね。
[1] JavaOne SF 2006のSeamのセッションで、Gavin Kingが「バッキング・ビーンはノイズだ」と言っていたことが印象的でしたが、せっかくPOJOになったセッション・ビーンのビジネス・メソッドにJSFアクション・メソッドの縛り(String <actionName>()の形式であること)を持ち込むのは全ての場合において適切ではないと思います。例えば、入出力のあるWebサービスとステートレス・セッション・ビーンのハイブリッド形式は、Seamとの整合性がよくありません。将来的には、JAX-WSの@WebParamで実現されているパラメータ・インジェクション/バイジェクションのような仕組みを用いて、普通のビジネス・メソッドでもJSFからメソッド・バインディングできるようになることが期待されます。
[2] ただし、WebコンテナがServlet 2.4ベースのJBoss 4では環境エントリ名を使用した方式が利用できません。Servlet 2.5を含むJava EE 5フルスペックのJBoss 5になれば、@EJB方式/ejb-local-ref方式ともに利用可能になるでしょう。
ところが今、リモート参照ではまっています。リモート参照のプログラムを書くのが始めてのためSeamの設定の問題なのか、GFの固有の問題なのか、そもそもリモート参照のやり方が悪いのかわかりません。
@Stateless @Name("action") public class ActionBean implements ActionRemote { public ActionBean() {} public void sayHello() { System.out.println("Hello, seam."); } }というオブジェクトをSeamのWeb層からリモート参照を行いたいのですが、web.xmlに<context-param> <param-name>org.jboss.seam.core.init.jndiPattern</param-name> <param-value>java:comp/env/ejb/#{ejbName}</param-value> </context-param> <ejb-ref> <ejb-ref-name>ejb/ActionBean</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <remote>org.example.ActionRemote</remote> <ejb-link>org.example.ActionBean</ejb-link> </ejb-ref>と定義をして、<h:commandButton action="#{action.sayHello}"/>で参照するとjavax.el.PropertyNotFoundException: Target Unreachable, identifier 'action' resolved to nullとなりオブジェクトがルックアップされていないようなのです。リモートの場合、どのように参照すればよいのでしょうか?Posted by かず on 1月月 25日, 2007年 at 01:19 午前 JST #
コメントありがとうございます。
スローされている例外を見るとELの評価で失敗しているようですので、Seam側の設定の問題のような気がします。すなわち、JSFページの#{action.sayHello}の評価に失敗して、JNDIのルックアップまで行けてないようにみえます。
また、上記例外を解決した後になるとおもいますが、修正すべき点としてweb.xml内の<ejb-link>の値があります。<ejb-link>は、ejb-jar.xml内の<ejb-name>(すなわち、@EJB.name)に一致していなければなりません。GlassFishの場合、@EJB.nameを明示しなかった場合、そのデフォルト値は、Session Beanクラス(Seamアクションクラス)名からパッケージを除いたものになりますので、かずさんの場合、
ではなく、
になると思われます。
Posted by nishigaya on 1月月 25日, 2007年 at 03:41 午後 JST #
Posted by かず on 4月月 16日, 2007年 at 01:18 午後 JST #
Posted by かず on 4月月 16日, 2007年 at 01:49 午後 JST #
Posted by かず on 4月月 16日, 2007年 at 09:28 午後 JST #
かずさん、こんばんは。
なかなか興味深い課題を提供していただきまして、ありがとうございます。
私の方でも調査してみましたが、Seam on Glassfishにおいてセッションビーンにセッションビーンをインジェクションしたい場合、かずさんの仰るように、@EJBアノーテーションをつけるしか方法はなさそうです。これは、GlassFishにおけるEJBローカルインタフェースはjava:comp/envで始まるコンテキスト名でしかアロケートすることができないという仕様があり、コンテキスト名でのlookup()を成功させるためには、<ejb-local-ref>か@EJBのどちらかの定義が必要であるためです。
JBossでは、EJBローカルインタフェースに対しても末尾が"/local"のJNDI物理名がデフォルトで与えられるため、JBossではこの問題が起きないのでしょう。
Posted by nishigaya on 4月月 17日, 2007年 at 12:20 午前 JST #
インジェクト対象のコンポーネントに@Name("foo") @Startup @Scope(ScopeType.SESSION)を付与して、セッション開始時にコンテキスト変数にオブジェクトをセットしてしまいます。@Startupはセッション開始時にコンポーネントを実体化してコンテキストに登録してくれるようです。
これで他のコンポーネント中で@In(create=true) Foo foo;でインジェクトできるようになります(create=trueはなくてもいいですが)。
issue 577, 900の実装も始まったようですし、そのうちにこういったTipsが必要なくなるといいんですが…その場合は@Startup @Scope(ScopeType.SESSION)を削除すればよいでしょう。
Posted by かず on 4月月 17日, 2007年 at 10:46 午前 JST #