DIの実用例を考えていたのですが,よいものが思いつかないので少し違う視点で書きなぐりますw
ダイちゃんの以下のエントリにコンポーネントの自動登録がでてきましたね.ComponentAutoRegisterは実はS2.3で登場した機能です.
私の方では,Seasar2.4からの新機能であるSMART deployについて書いてみます.
これは「jp.xet.tutorial.s2.converterパッケージにある、.*という名前(正規表現だから、全ての名前)のクラスは、勝手にコンテナに登録しとけやゴルァ」という指示。MySQLConverterとPostgreSQLConverterは、そういえばこのパッケージに置いてある。ということで、勝手に登録されたんですね。
では、そのコンポーネントの名前は? ここも空気を読むSeasar2、のターン。「クラス名の最初を小文字にした名前で、登録しとくお」。
ということで、mySQLConverterとpostgreSQLConverterというコンポーネントが、自動登録されました。
SMART deployへいざなう
さらに,S2.4以降からSMART deployといういかした機能が利用できます.詳しくはこのあたり参照ください.ここ.SMART deployには,HOT deploy, WARM deploy, COOL deployがあります.
まず,大きなメリットとしては,SMART deployではdiconファイル自体の管理から解放されます.diconファイル自体にAutoRegisterを書く必要すらなくなります.それを実現する大まかな規約としては,決められたパッケージにコンポーネントを配置する,決めれたクラスサフィックス名を取るぐらいです.diconファイルで記述するような細かい設定は,基本的にはアノテーションで記述することになります.
それ以外に大きなメリットは,やはりHOT deployでしょうか.私自身,HOT deployについてはTeedaで大変お世話になりました.通常Javaのクラスローダは一度クラスがロードされると解放されませんが,HOT deployを利用するとリクエスト毎にクラスがロードされるため,スクリプトのようにアプリケーションが動作中にコードを修正しても即座に反映されるようになります.Javaでのウェブ開発が"かったるい"一つの原因がコード修正後のアプリケーションサーバの再起動ですが,これがHOT deployを使うと不要になります.地味な改善とも思われがちですが,実はJavaのプログラミングスタイルを大きく変えている技術です.本番運用時では,WARM deployかCOOL deployを選びます.
ルートパッケージとサブパッケージの登場
SMART deployでは,まずもって,ルートパッケージという概念が登場します.
これまでに数々登場してきたdaoやlogicなどのクラスを,ルートパッケージ名+daoや,ルートパッケージ名+logicという統一化されたパッケージで管理しようというものです.
jp.xet.tutorial.s2を,ルートパッケージにする場合は,以下のようにconvention.diconを用意してデフォルトパッケージに配置してください.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR2.4//DTD S2Container//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <component class="org.seasar.framework.convention.impl.NamingConventionImpl"> <initMethod name="addRootPackageName"> <arg>"jp.xet.tutorial.s2"</arg> </initMethod> </component> </components>
登場人物は,.*Converter, .*Logic, .*Table, .*Column ですので,ルートパッケージ名+サブパッケージ名で,
jp.xet.tutorial.s2.converter
jp.xet.tutorial.s2.logic
jp.xet.tutorial.s2.table
jp.xet.tutorial.s2.column
のようになります.こちらにでそれぞれ対応するクラスを配置することになります.
上記2つはすでにS2がでデフォルトで認識するサブパッケージ名になりますので,table, columnにコンテナに認識されるように設定を施す必要があります.
Seasar2.4標準のCreatorを定義する
まず,以下のようにcreator.diconを用意します.ここではLogic,Converterは,LogicCreatorはlogicサブパッケージにあるLogicで終わるクラスを,ConverterCreatorはconverterサブパッケージにあるConverterで終わるクラスを自動的にコンテナに登録します.
<?xml version="1.0" encoding="Shift_JIS"?> <!DOCTYPE components PUBLIC "-//SEASAR2.4//DTD S2Container//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <include path="convention.dicon"/> <include path="customizer.dicon"/> <component class="org.seasar.framework.container.creator.LogicCreator"/> <component class="org.seasar.framework.container.creator.ConverterCreator"/> </components>
自前のCreatorを定義する
Table用のCreatorは以下のようになります.ColumnCreatorについてもs/Table/Column/gとしていただければよいです.
package jp.xet.tutorial.s2.creator; import org.seasar.framework.container.ComponentCustomizer; import org.seasar.framework.container.creator.ComponentCreatorImpl; import org.seasar.framework.container.deployer.InstanceDefFactory; import org.seasar.framework.convention.NamingConvention; public class TableCreator extends ComponentCreatorImpl { private static final String NAME_SUFFIX_TABLE = "Table"; public TableCreator(NamingConvention namingConvention) { super(namingConvention); this.setNameSuffix(NAME_SUFFIX_TABLE); this.setInstanceDef(InstanceDefFactory.SINGLETON); this.setExternalBinding(false); this.setEnableAbstract(false); this.setEnableInterface(false); } public ComponentCustomizer getTableCustomizer() { return super.getCustomizer(); } public void setTableCustomizer(ComponentCustomizer customizer) { super.setCustomizer(customizer); } }
setInstanceDefでシングルトンを指定しているので,.*TableクラスはS2にはコンテナに自動登録されます.
で,creator.diconの最後にTableCreatorとColumnCreatorを定義します.
<?xml version="1.0" encoding="Shift_JIS"?> <!DOCTYPE components PUBLIC "-//SEASAR2.4//DTD S2Container//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <include path="convention.dicon"/> <include path="customizer.dicon"/> <component class="org.seasar.framework.container.creator.LogicCreator"/> <component class="org.seasar.framework.container.creator.ConverterCreator"/> <component class="jp.xet.tutorial.s2.creator.TableCreator"/> <component class="jp.xet.tutorial.s2.creator.ColumnCreator"/> </components>
さらにCustomizerを用意する
Customizerは,SMART deployの管理下のコンポーネントに対するAOPの設定を記述することになります.
LogicやConverterに対して標準のCustomizerでよければdefault-customizer.diconをインクルードするだけでよいです.
Tableや,Columnの場合は,以下のようにCustomizerChainを使って定義してください.ここでは標準で用意されているトレース用のCustomizerであるtraceCustomizerを定義しています.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR2.4//DTD S2Container//EN" "http://www.seasar.org/dtd/components24.dtd"> <components> <include path="default-customizer.dicon"/> <component name="taskCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain"> <initMethod name="addCustomizer"> <arg>traceCustomizer</arg> </initMethod> </component> <component name="columnCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain"> <initMethod name="addCustomizer"> <arg>traceCustomizer</arg> </initMethod> </component> </components>
SMART deployの切り替え
env.txtに記述された文字列によってHOT, WARM, COOLのモードを切り替えます.そのためのs2container.diconを同様に用意します.
<?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 condition="#ENV == 'ut'" path="warmdeploy.dicon"/> <include condition="#ENV == 'ct'" path="hotdeploy.dicon"/> <include condition="#ENV != 'ut' and #ENV != 'ct'" path="cooldeploy.dicon"/> </components>
で,めでたくSMART deployの管理下に置かれるので,diconの定義が一切不要になります.
HotDeployUtilを使う
が,しかしHOT deployが適用されていません.
S2のネ申である小林さんのブログのこのあたりに書かれています.とても参考になります.
Seasar 2.4リリース! 今更でも恥ずかしくない、始めてみようDIプログラミング SMART deploy 編
Seasar 2.4リリース! 今更でも恥ずかしくない、始めてみようDIプログラミング HOT deploy 編
HOT deploy時に処理単位でクラスをロードさせるには,コンテナからgetComponentする前後にHotDeployUtilのstartとstopを明示的に呼び出す必要があります.
TeedaのPageクラスでは,Teeda ExtensionがこのAPIを呼び出してくれますが,コンソールアプリではHotDeployUtilが必要になります.(TeedaではPageクラスに依存しているすべてのコンポーネントがリクエスト単位でロードされるはずです)
package test; import org.example.service.PrintService; import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.SingletonS2ContainerFactory; import org.seasar.framework.container.hotdeploy.HotdeployUtil; public class Test { public static void main(String[] args) throws Exception { SingletonS2ContainerFactory.init(); S2Container container = SingletonS2ContainerFactory.getContainer(); Class.forName(PrintService.class.getName()); for (int i = 0; i < 3; ++i) { HotdeployUtil.start(); PrintService printer = (PrintService) container.getComponent("printService"); printer.print(); HotdeployUtil.stop(); } } }
さらに重要なのが以下の下り.PrintService.classは,明示的に先にクラスをロードしておかないとClassCastExceptionになります.
このあたりは悩ましいですね.
なぜなら,このインタフェースは HOT deploy なコンテキストの外側にいる,Test クラスから参照されるからです.
もし HotdeployClassLoader が PrintService をロードしてしまうと,Test#main() の
PrintService printer = (PrintService) container
.getComponent("printService");という部分で ClassCastException が発生します.
Test からは HotdeployClassLoader は見えないため,本来のクラスローダー (このサンプルではシステムクラスローダー) から PrintService をロードします.
しかし,S2 コンテナから返されるのは HotdeployClassLoader からロードされた PrintService (を実装したクラス) のインスタンスです.
Java では異なるクラスローダーからロードされたクラスは別のクラスとして扱われ,代入やキャストができません.
そのため ClassCastException が発生します.
まとめ
最後のHotDeployUtilは使いどころを考える必要があると思いますが,SMART deployでコンポーネントがdiconファイルから分離できるメリットは大きいと思うんですよね.diconファイルを書きたくないいう方はCreatorだけでも定義してあげるとものすごく楽になるはずです.以上,ご参考までに.