ついにS2JSFに

ついにS2JSFに踏み込むときがやってきた。

素のJSFを捨てたのはなぜか?(おおげさ
今のところ、あまり強い思いはないんだけど、とりあえずは以下のような理由。

  • コンポーネントをFacesContextとS2Containerで2重管理するのはいや。どっちに登録されたっけ?とか面倒。バグの元。
  • S2JSF: HTMLでプレビュー可能なのが魅力。
    • JSF: panelGroupであまり考えずに機械的にレイアウトできるのも魅力だけど、HTMLの簡単さには勝てず。
  • managed beanやlogicに自動DIが可能。
    • managed beanの管理をS2ContainerがするのでDI,aspectがそのままかかる。
    • JSFにmanaged bean管理をさせると、途中で明示的にS2Containerからのコンポーネント取得をはさまないと、それ以降のLogicなどにDIができない。
  • 単に使ってみたかった。


とりあえず、作ってたサンプルをS2JSF化するのに成功。

メモ。

  • inputタグでtype属性がないと、managed beanに値がわたらない。orz
  • 実行系のmanaged bean(くーす/goyaではActionと呼ぶ)は、インタフェース化することは必須ではない。実装上の規約が明確になるというだけ。
  • managed bean → Logicへは引数でDtoを渡したほうが良い。直接LogicでDIしてもらうのは、可能だけれど…。
  • JSFの仕組みとして、greacemonkeyなどでクライアント側で勝手にフォームを追加された場合に、予期しないmanaged beanのプロパティに値を設定されたりアクションを実行されたりすることを防止しているか?自力でやる必要があるのか?

10/4追記:
メモの最後について。
サーバ側で構築したUIコンポーネントに対して値の設定やアクション実行がなされるわけだから、UIコンポーネントに対応させていない裏のmanaged beanのプロパティやメソッドは実行できないようになっているようです。

instance="session"でエラー発生

instance="session" or "request"にすると、以下のエラーが発生する。

[ERROR] 2005-10-02 19:23:03 StandardContext#loadOnStartup() サーブレット /hoge がload()例外を投げました
org.seasar.framework.exception.EmptyRuntimeException: [ESSR0007]sessionはnullあるいは空であってはいけません
	at org.seasar.framework.container.deployer.SessionComponentDeployer.deploy(SessionComponentDeployer.java:28)
	at org.seasar.framework.container.impl.ComponentDefImpl.getComponent(ComponentDefImpl.java:75)
	at org.seasar.framework.container.impl.S2ContainerImpl.getComponent(S2ContainerImpl.java:89)
	at org.seasar.framework.container.assembler.AutoPropertyAssembler.assemble(AutoPropertyAssembler.java:44)
	at org.seasar.framework.container.deployer.SingletonComponentDeployer.assemble(SingletonComponentDeployer.java:47)
	at org.seasar.framework.container.deployer.SingletonComponentDeployer.deploy(SingletonComponentDeployer.java:27)
	at org.seasar.framework.container.deployer.SingletonComponentDeployer.init(SingletonComponentDeployer.java:55)
	at org.seasar.framework.container.impl.ComponentDefImpl.init(ComponentDefImpl.java:249)
	at org.seasar.framework.container.impl.S2ContainerImpl.init(S2ContainerImpl.java:344)
	at org.seasar.framework.container.factory.SingletonS2ContainerFactory.init(SingletonS2ContainerFactory.java:36)
	at org.seasar.framework.container.servlet.S2ContainerServlet.init(S2ContainerServlet.java:47)
	at javax.servlet.GenericServlet.init(GenericServlet.java:211)
〜(省略)〜

当然、web.xmlで

    <filter>
        <filter-name>s2filter</filter-name>
        <filter-class>org.seasar.framework.container.filter.S2ContainerFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>s2filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>s2servlet</servlet-name>
        <servlet-class>org.seasar.framework.container.servlet.S2ContainerServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

という設定はしてますよ、はい。その辺りは抜かりなく。

実際、別のフィルタでS2Containerのrequest,response,sessionとういコンポーネント名でgetしてみると、ちゃんとオブジェクトは入ってるんです。
つーか、Tomcat起動時に上記のエラーが発生するんですよ。これが。


で、色々試した結果、大体見えてきた。

HogeLogicImplクラスの中で、UserImpl(インタフェースUser)をDIしたい場合に、

<components>
	<component name="user" class="hoge.entity.impl.UserImpl" instance="session" />
	<component class="hoge.logic.impl.HogeLogicImpl" instance="request" ></component>
</components>

という設定をすると、起動時にエラーは発生せず、実行も可能。

<components>
	<component name="user" class="hoge.entity.impl.UserImpl" instance="session" />
	<component class="hoge.logic.impl.HogeLogicImpl" instance="singleton" ></component>
</components>

のようにDI先コンポーネントがsingletonだと、起動時にエラー発生!


エラーメッセージから解析するのはとっても難しいけど、原因がわかった今となっては、そりゃそうだ、という感じかも。

つまり、

  • ライフサイクルがsingletonなコンポーネントHogeLogicImplは、起動時にインスタンス化されてコンテナに設定される。
  • その時点で、HogeLogicImplが必要なコンポーネントがDIされる。
  • その時点では、sessionなどないのでUserImplは取得できない。
  • エラー!!

というわけだ。

ま、そりゃそうだ。


でも、エラーメッセージがわかりづらいっす。検索してもぜんぜん情報ヒットしないし。
実際、マニュアルにも明示的には書いてないですよね。ふつーに気付けよッてことかしら?これにハマったのって僕だけ?


こういう設定上の制約は、機械的にチェックできるんで、kijimunaで警告してくれるようになると大変助かります。
…と、世界の隅っこでぼそっとつぶやいておこう。

ユーザ情報とかの保持方法

Webアプリだとログインした後、そのユーザ情報をセッションとかに保持しておきますよね。
で、JSFを使ってちょっとサンプルをつくってるときにそれをどうやって実現しようかなー考えてたわけです。

  • A) 直接HttpSessionに登録して、自力でハンドリング。
  • B) faces-config.xmlでsessionスコープのmanaged beanとして登録して、JSFまかせ。
  • C) diconファイルでsessionスコープのコンポーネントとして登録して、S2まかせ。

のどれか?

一瞬、特定のフレームワークに依存するってのもどうよ、とか思ったりしたけど、(A)だと後でそのオブジェクトにアクセスするために、自力でThreadLocal系の仕組みを実装したりしなきゃいけなさそうで、ちょっと面倒。というかJSFとかS2でカバーしているのにわざわざ自分で実装っていまどきどうよ?と。

全体をシンプルに保つためだったら別にある時点で特定のフレームワークに依存してしまってもいいじゃないの?と開き直ってみたり。いや、やっぱりと迷ってみたり。

そんなとき、例のsession-nullエラーの解析してるときに見つけたMLの記事で、

S2.1 以降では,例えばログイン情報をセッションで保持したい場合にはアプリケーションで HttpSession#setAttribute() するのではなく,かわりにログイン情報のクラスを instance="session" で diocn ファイルに記述します.
それだけで,コンテナがログイン情報をセッションに設定してくれます.
エラー - Seasar - SourceForge.JP

というのがあった。
あぁ、もうやっぱそういう方法でふつーにやるんですよね?そうですよね?


ってことで、その方向で。


目下の残る悩みは↓。

  • この場合のユーザ情報って、エンティティ?DTO
  • 業務ロジックでこのユーザ情報をDIしてもろうのはアリ?DAOではアリ?


追記:
素のJSFなら(B)、S2JSFなら(C)かと思ったけど、前者でちょっと問題が。
試してみるとservlet-filter中ではFacesContext.getCurrentContext()がnullになるようだ。
このため、ユーザ情報をJSFに管理してもらっていると、filterでの認証状態チェックができなくなってしまう。
素のJSFの場合でもfilterでチェックしたいなら、(A)(C)のどちらかでやらないといけないみたい。
なんか方法を見落としているだけかも。