[Sesar2] Hot Deployを試す

※注意:この文書はJUNDUがソースを超ザックリと読んだだけの内容に基づいています。つまり、間違っている可能性がすごく高いので鵜呑みにしないでください。

 時代の流れ(?)に乗って、Hot Deployを試してみました。1から全部作るのが面倒だったので、teeda-html-exampleにs2jsf-exampleに実装されているEmployee Managementを移植するという試みです。結論から言うと、あっさりと1度目の挑戦はClassCastExceptionにより失敗に終わりました。ともあれ、どこまで出来たかを書いてみます。

 まず、Hot Deployについておさらい。Hot Deployは、サーバの再起動なしにクラスファイルの変更を動作中のアプリケーションに反映させるSeasar2の機能です。この機能は、テストをスムーズに行うことを目的としていて、本番では使わない機能のようです。あまり、この辺りは語られていない気がします。本番では、Hot Deployとdiconファイルを共有できるCool Deployを使うことが想定されているようです。(間違っていたら、ツッコミお願いします)

 さて、このHot Deployを使う上で重要なのがs2container.diconから参照されるdiconファイルで、通常「ほにゃらら-hotdeploy.dicon」というファイル名にするようです。このほにゃらら-hotdeploy.diconファイルは標準で提供されるものをリネームして使うイメージのように感じますが、今のところ詳細不明ですね。teeda-html-example-hotdeploy.diconの場合は、各種コンポーネント用のCreatorが登録されていて、一番下のOndemandBehaviorに基準となるパッケージを定義してあります。

 Hot Deployでは、Seasar2側で想定している命名規則に従ったファイルを各Creatorが想定するコンポーネントとして、S2コンテナにデプロイします。例えば、xxxDaoというクラス名であれば、Daoクラスとして登録するといった感じです。つまりCreatorの動作は、要素を書くことに対応していると思われます。ちなみに、命名規則はNamingConventionの実装クラスを用意することで、独自に設定することも出来そうですが、teedaなどの他のプロダクトとの連携が取れるのか心配ですね。

 そして、teeda-html-example-hotdeploy.diconから参照されているteeda-html-example-customizer.diconも重要です。ここには、要素に相当する定義が書かれています。もっとも、Customizerという名前から、それ以外のことも出来そうですが。このファイルは、Cool Deployでも共通で使うようですね。

 ここまでの内容からEmployee Managementを作るための情報がそろいました。そこで、まずviewフォルダ配下にemployeeSeasch.htmlというファイルを作成します。まだ、日付型へのコンバータやバリデータの使い方がよく分かっていないので、かなり適当な内容になってます(^^;

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Employee Management</title>
</head>
<body>
<form id="employeeSearchForm">
<table>
<tr>
    <td class="label">EmployeeNo</td>
    <td>
        <input type="text" id="empno" class="right"/>
        <span id="empnoMessage"/>
    </td>
</tr>
<tr>
    <td class="label">EmployeeName</td>
    <td>
        <input type="text" id="ename"/>
        <span id="enameMessage"/>
    </td>
</tr>
<tr>
    <td class="label">Job</td>
    <td>
        <input type="text" id="job"/>
        <span id="jobMessage"/>
    </td>
</tr>
<tr>
    <td class="label">Manager</td>
    <td>
        <input type="text" id="mgr" class="right"/>
    </td>
</tr>
<tr>
    <td class="label">HireDate</td>
    <td><input id="fromHiredate" type="text"/> 〜 <input id="toHiredate" type="text"/></td>
</tr>
<tr>
    <td class="label">Salary</td>
    <td><input id="fromSal" type="text" class="right"/> 〜 <input id="toSal" type="text" class="right"/></td>
</tr>
<tr>
    <td class="label">Department</td>
    <td>
        <select id="deptnoItems">
            <option value="">Please select</option>
            <option value="10">ACCOUNTING</option>
            <option value="20">RESEARCH</option>
            <option value="30">SALES</option>
            <option value="40">OPERATIONS</option>
        </select>
    </td>
</tr>
</table>
<input type="button" value="create" id="doCreate"/>
<input type="button" value="search" id="doSearch"/>
</form>
</body>
</html>

 そして、このHTMLに対応するPageクラスをexamples.teeda.web.exployeeに作成します。今回は1回目の実験なのでActionやSearviceは作っていません(^^;; 

package examples.teeda.web.employee;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import examples.teeda.entity.Department;
import examples.teeda.logic.DepartmentLogic;

public class EmployeeSearchPage {

    private List deptnoItems;
    
    private String empno;
    private String ename;
    private String job;
    private String mgr;
    private String fromHiredate;
    private String toHiredate;
    private String fromSal;
    private String toSal;
    
    private int deptno;
    
    private DepartmentLogic deptLogic;
    
    public String initialize() {
        System.out.println("initialize()");
        deptnoItems = new ArrayList();
        List deptList = deptLogic.getAllDepartments();
        Iterator itr = deptList.iterator();
        while(itr.hasNext()) {
            DeptnoDto dto = new DeptnoDto();
            Department dept = (Department)itr.next();
            dto.setValue(dept.getDeptno());
            dto.setLabel(dept.getDname());
            deptnoItems.add(dto);
        }
        return null;
    }
    
    public String doCreate() {
        System.out.println("doCreate()");
        return null;
    }
    
    public String doSearch() {
        System.out.println("doSearch()");
        return null;
    }

    // ... getter/setterは省略
}

 <select>の内容を動的に作成するためにEntityがそのまま使いたかったのですが、ラベルや値をマッピングする方法が分からなかったので、とりあえずは詰め替えしてます。DeptnoDtoは以下のような感じです。

package examples.teeda.web.employee;

public class deptnodto {

    private string label;

    private int value;

    public string getlabel() {
        return label;
    }

    public int getvalue() {
        return value;
    }

    public void setlabel(string label) {
        this.label = label;
    }

    public void setvalue(int value) {
        this.value = value;
    }
}

 あとは、s2jsf-exampleのLogic、Dao、Entityを参考に、それぞれのファイルを作成します。ポイントは、それぞれexamples.teeda.logic、examples.teeda.dao、examples.teeda.entityに作成することです。Hot Deployでは命名規則重要なので。

 それから、S2Daoを使うためにdao.diconとs2-dao-1.0.35.jarを入れて、hsqldbの設定とかします。この辺も、S2Daoを使うとき、サンプルを動かすときに共通なので省略します。

 最後に、app.diconとteeda-html-example-customizer.diconを書き換えます。まず、app.diconはj2ee.diconとdao.diconを使える用にincludeしています。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
    "http://www.seasar.org/dtd/components24.dtd">
<components>
    <include path="convention.dicon"/>
    <include path="aop.dicon"/>
    <include path="app_aop.dicon"/>
    <include path="teedaExtension.dicon"/>
    <include path="j2ee.dicon"/>
    <include path="dao.dicon"/>
    <include condition="#ENV != 'ut'" path="teeda-html-example-cooldeploy.dicon"/>
</components>

 そして、teeda-html-example.customizer.diconには、dao.interceptorとj2ee.requiredTxを使うようにしています。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components21.dtd">
<components>
    <component name="defaultCustomizer" class="org.seasar.framework.container.autoregister.AspectCustomizer">
        <property name="interceptorName">"aop.classLoaderAwareTraceInterceptor"</property>
    </component>
    <component name="commandAspectCustomizer" class="org.seasar.framework.container.autoregister.AspectCustomizer">
        <property name="interceptorName">"aop.classLoaderAwareTraceInterceptor"</property>
        <property name="pointcut">"do.*, initialize"</property>
    </component>
    <component name="actionSupportAspectCustomizer" class="org.seasar.framework.container.autoregister.AspectCustomizer">
        <property name="interceptorName">"app_aop.actionSupportInterceptor"</property>
        <property name="pointcut">"do.*, initialize"</property>
    </component>
    <component name="daoSupportAspectCustomizer" class="org.seasar.framework.container.autoregister.AspectCustomizer">
        <property name="interceptorName">"dao.interceptor"</property>
    </component>
    <component name="transactionSupportAspectCustomizer" class="org.seasar.framework.container.autoregister.AspectCustomizer">
        <property name="interceptorName">"j2ee.requiredTx"</property>
    </component>

    <component name="actionCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
        <initMethod name="addCustomizer">
            <arg>commandAspectCustomizer</arg>
        </initMethod>
        <initMethod name="addCustomizer">
            <arg>actionSupportAspectCustomizer</arg>
        </initMethod>
    </component>
    <component name="daoCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
        <initMethod name="addCustomizer">
            <arg>defaultCustomizer</arg>
        </initMethod>
        <initMethod name="addCustomizer">
            <arg>daoSupportAspectCustomizer</arg>
        </initMethod>
    </component>
    <component name="dtoCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
    </component>
    <component name="dxoCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
        <initMethod name="addCustomizer">
            <arg>defaultCustomizer</arg>
        </initMethod>
    </component>
    <component name="helperCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
        <initMethod name="addCustomizer">
            <arg>defaultCustomizer</arg>
        </initMethod>
    </component>
    <component name="logicCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
        <initMethod name="addCustomizer">
            <arg>defaultCustomizer</arg>
        </initMethod>
        <initMethod name="addCustomizer">
            <arg>transactionSupportAspectCustomizer</arg>
        </initMethod>
    </component>
    <component name="pageCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
        <initMethod name="addCustomizer">
            <arg>defaultCustomizer</arg>
        </initMethod>
    </component>
    <component name="serviceCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
        <initMethod name="addCustomizer">
            <arg>defaultCustomizer</arg>
        </initMethod>
    </component>
    <component name="interceptorCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
    </component>
    <component name="validatorCustomizer" class="org.seasar.framework.container.autoregister.CustomizerChain">
    </component>
</components>

 ここでのポイントは、daoSupportAspectCustomizerとtransactionSupportAspectCustomizerの2つです。これがInterceptorの設定をしてくれるようです。

 というわけで、私がやったのは、ここまでです。これで画面の初期表示の時だけは正常に動きます。ところが2度目以降の画面表示(今は手抜きなのでボタンを押すと自画面に遷移する)をするとClassCastExceptionが発生します。

 原因は、Daoが返す検索結果のEntityクラスとPage側が持っている(?)Entityクラスが別々のクラスローダにロードされる殻のようです。試しにClassLoaderのtoStringを表示してみました。まず1回目のとき。

Page: org.seasar.framework.container.hotdeploy.HotdeployClassLoader@5fbbf3
Logic: org.seasar.framework.container.hotdeploy.HotdeployClassLoader@5fbbf3
Department: org.seasar.framework.container.hotdeploy.HotdeployClassLoader@5fbbf3
Result: org.seasar.framework.container.hotdeploy.HotdeployClassLoader@5fbbf3

 そして、エラーの出る2回目以降。

Page: org.seasar.framework.container.hotdeploy.HotdeployClassLoader@1f71773
Logic: org.seasar.framework.container.hotdeploy.HotdeployClassLoader@1f71773
Department: org.seasar.framework.container.hotdeploy.HotdeployClassLoader@1f71773
Result: org.seasar.framework.container.hotdeploy.HotdeployClassLoader@5fbbf3

 というわけで、検索結果は1回目と同じクラスローダが使われていますが、Pageなどは別のクラスローダに変わってしまうと。なんとなく、コンポーネントのScopeが関係しそうな機がするのですが、果たしてどうしたものか。長くなってしまったので、今日のところはここまで。