SAStruts の LinkageError で足踏み

SAStruts でテストケースを用意してユニットテストを頑張っていたら、なぜか触っていないはずのアプリケーションにて、java.lang.LinkageError が発生して動かなくなってしまった。初めて見た、このエラー。

java.lang.LinkageError: 
loader constraint violation: 
loader (instance of org/apache/catalina/loader/WebappClassLoader) 
previously initiated loading for a different type with name 
"com/deftrash/app/dto/SampleDto"

どうやら例によって HotdeployClassLoader と Tomcat のクラスローダのせめぎあいの問題のようだ。ただ、今回は1回目だけ〜ということもなく、ずーっと起こる。いまいち仕組みが理解できてないっす…。

HotdeployClassLoader より先に Tomcat の WebappClassLoader が対象クラスをロードしちゃっているので、他のHOTなクラスと型が合わなくてエラーになっているってことか。でも、今まで動いていたということは、HotdeployClassLoader より先に WebappClassLoader がロードするような箇所がどっかに生み出されたというわけで…。

で、冒頭のテストケース。

SampleDto を参照するクラス com.deftrash.app.util.SampleUtil があって、これのテストケースを、com.deftrash.app.util.SampleUtilTest として用意。当然テストケースでも SampleDto を参照する。ところが、SampleUtilTest は名前的に SMART Deploy 対象ではないから、HotdeployClassLoader ではなくて WebappClassLoader が最初にロードすることになって…ということかな?

実際、SampleUtilTest の名前を TestSampleUtil に変更したら、エラーが出なくなった(と思う)。要は、テストケースクラスも Hotdeploy の対象にしてしまったんだけど、なんか違う気がする。

SAStruts では、どういうパッケージ構成というかファイル構成でテストケースを用意してやっていくのがセオリーなんだろう。同じパッケージにテスト対象とテストケースが入るような構成じゃダメなんか…。それとも、何か根本的に間違ってるのかな??

COOL deploy 爆速伝説

ということで、COOL deploy で動かせるようになって、ビックリ驚いた!すんごい速いッス!

HOT で製造しているときに、「この性能ではちょっとマズイかもなあ…」と思って、やたらDBや計算ロジックでのチューニングを考えていたのだけど、何のことはない、HOT だからだった。ほっとひと安心。メチャクチャ速いです!

HOT では確認できなかった非同期リクエストの処理も、COOL でバッチリ確認できた。爆速のために非同期状態が一瞬すぎて、最初うまくいっていないと思ってしまったのは、ここだけの秘密。

COOL deploy にしたら動かなくなった…

ローカルで HOT でサクサク開発してきて、いざ COOL にしてサーバにのっけたら、一部で動かないところがあった。ActionFormWrapper が reset メソッドを呼ぼうとして、 NullPointerException になっているっぽい。うーむ。

起動時のログを見ていくと、ActionForm 用に作成した Dtoコンポーネント登録されていない様子。うにゃあ、なんでだー?

調べてみると、ML にドンピシャのがあった。

CoolDeploy 時に登録されないコンポーネント

FooDto extends HogeDto となっているとき、FooDto はコンポーネント登録されるけど、HogeDto はコンポーネント登録されない。HogeDto の型で、名前順に走査していったとき、先に FooDto が来るから、そのあとの HogeDto はコンポーネント登録をスキップする、と。まさに同じ動き。

ML でのアドバイスどおり FooDto と HogeDto の共通部分を abstract な BaseDto として切り出して、

FooDto extends BaseDto
HogeDto extends BaseDto

という形にしたら、無事コンポーネント登録されて動くようになりました。

いやー、見事にハマりました。HOT と COOL の動きを詳細に把握して製造していれば何てことないのだろうけど、初心者にはまだまだ厳しい。クラス設計から変えないといけないとなると、たまに COOL で動かしておかないと怖いなあ。

[java][seasar] JdbcManager に Interceptor を織り込む

SAStruts で JdbcManager に Interceptor を織り込もうと画策。

まず、app.dicon にて

<component name="jdbcManager" class="org.seasar.extension.jdbc.JdbcManager">
    <aspect pointcut="toString">
        <component class="org.seasar.framework.aop.interceptors.TraceInterceptor" />
    </aspect>
</component>

そして、試しにこんなコードを。

public JdbcManager jdbcManager;

@Execute
public String update() {
    jdbcManager.toString();

    return "update.jsp";
}

しかし、これだと TraceInterceptor がちゃんと動いてくれなかった。

しばし試行錯誤で動かしてみるも、どうも Interceptor が織り込めない。暗黙的なコンポーネントには織り込めないのかと思ったけど、いや、そうじゃないでしょうと思って、気付いた。

s2jdbc.dicon に設定してやらないとダメでした。

<component name="jdbcManager" class="org.seasar.extension.jdbc.manager.JdbcManagerImpl">
    <property name="maxRows">0</property>
    <property name="fetchSize">0</property>
    <property name="queryTimeout">0</property>
    <property name="dialect">postgreDialect</property>
    <aspect pointcut="toString">
        <component class="org.seasar.framework.aop.interceptors.TraceInterceptor" />
    </aspect>		
</component>

したらば、ちゃんとログが出ましたよ!

DEBUG 2008-03-10 23:39:34,250 [http-8080-1] BEGIN org.seasar.extension.jdbc.manager.JdbcManagerImpl#toString()
DEBUG 2008-03-10 23:39:34,250 [http-8080-1] END org.seasar.extension.jdbc.manager.JdbcManagerImpl#toString() : org.seasar.extension.jdbc.manager.JdbcManagerImpl$$EnhancedByS2AOP$$ca2369@1f4bf33

s2jdbc.dicon の存在をすっかり忘れてました。一度設定したら、あとは見なくなっちゃうからなあ。いかん、いかん。

[java][seasar] BeanWrapper の bean が欲しい

SAStruts の良いところのひとつは、アクセッサメソッドを用意していない Bean であっても、Map 実装の BeanWrapper にくるんで、そうとは意識せずに値のやりとりができるところだと思う。

が、独自の Validation 処理で、ちょいと込み入った要件があって、Bean を直接操作したくなって困った。BeanWrapper には、ラップしたオブジェクトを取得するための方法が用意されていなかった!うーん。

仕方ないので、

Object object = PropertyUtils.getProperty(bean, property);
if (object instanceof BeanWrapper) {
    Field field = BeanWrapper.class.getDeclaredField("bean");
    field.setAccessible(true);
    object = field.get(object);
}

なんてことをしてムリヤリ取得してみたんだけど、やっぱりフィールド名をハードコーディングするのは気持ち悪いなあ。ActionFormWrapper など他のラッパーでも同じで、ラップ対象のオブジェクトが遠い。まあ、ぼやいていないで、今後のサポートに期待して、とりあえず歩を進めなくては。

[java][seasar] Hot Deploy は便利なんです

メンバーに「Hot Deploy は便利だよねー」と言ったら、「今までと変わらなくないですかぁ?」と返されて焦る。Hot Deploy のおかげで、かなり開発効率が上がっていると思っていただけに、ショック。

しかし、よくよく話を聞いて開発環境を触ってみると、ソース修正後に tomcat が再ロードされている。これ、Hot Deploy できてないから!

tomcat の context 設定で、

reloadable="true"

になってた。うわー、もったいない!(過ぎ去った時間が)

reloadable="false" に書き換えてあげて、使ってもらったら、メンバーの目から鱗が落ちていた。「Hot Deploy は便利なんですね!」という言葉に続いて、今日はいつもよりソースのロールアウト量が増えている気がする。もう少しすれば、気のせいじゃないくらいの効率アップを、肌で感じられそうな気がする。

さーて、メンバーみんなのために、縁の下から持ち上げましょ。次は validator だな。

[java][seasar] BeanWrapper

例によって SAStruts の話題ですけれども。

pageContext.findAttribute() したら、BeanWrapper が返ってきた。こ、これは!

ActionWrapper さんが、POJO Action のプロパティを色んなラッパーで包んでから HttpServletRequest に詰めてあげているのか。WrapperUtil#convert() が肝。ふむふむ。BeanWrapper は Map 実装だから、EL式でサラサラッとアクセスできる、ということかな?

ということは、前述の

${sessionScope.formDto.first}

を可能にするには、セッションに格納する際に、dto を BeanWrapper で包んであげれば良いってことか。あー、でも Action で使うときが、めんどくさい?