[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の動作は、
そして、teeda-html-example-hotdeploy.diconから参照されているteeda-html-example-customizer.diconも重要です。ここには、
ここまでの内容から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が関係しそうな機がするのですが、果たしてどうしたものか。長くなってしまったので、今日のところはここまで。