かとじゅんの技術日誌

技術の話をするところ

SMART deployに対応させてみる

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だけでも定義してあげるとものすごく楽になるはずです.以上,ご参考までに.