Seasar 2.4リリース! 今更でも恥ずかしくない、始めてみようDIプログラミング HOT deploy 編
昨日は SMART deploy 編と言いつつ,実際に試せるのは WARM/COOL だけで,HOT deploy は追加のコードが必要とだけ書いて説明を端折ってしまいました.心より恥じる.
そんなわけで (どんなわけで?),今日は昨日のサンプルを HOT deploy 対応にしてみます.
まずは実行用の Test
クラスを修正します.
Test.java
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(); } } }
main()
メソッドでコンテナを初期化している最初の 2 行は昨日と同じです.
その後の Class#forName()
は後述.
for
文は HOT deploy の効果を確認するために追加しています.
そして for
ループの中に加えられた HotdeployUtil#start()
と stop()
が HOT deploy のためのおまじないです.
HOT deploy は,この start()
から stop()
までが一つのコンテキストとなります.
このコンテキスト内では HotdeployClassLoader
がスレッドのコンテキストクラスローダーに設定され,新たなクラスはこのクラスローダーにロードされます.
このクラスローダーは HotdeployUtil#stop()
で破棄されます.
次の繰り返しになると,また新たにクラスローダーが作成され,クラスも改めてロードされます.
簡単に言うと,これが HOT deploy の全てです.
Web アプリの場合は HotdeployServletFilter
に HotdeployUtil#start()
と stop()
をお任せできます.
つまり,一つのリクエストが一つのコンテキストになります.
修正するファイルがもうひとつ.
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 path="hotdeploy.dicon"/> </components>
warmdeploy.dicon
に代えて,hotdeploy.dicon
をインクルードしています.
それでは実行してみます.
その前に,Test#main()
のループの中にブレークポイントを設定してください.
そしてデバッグ実行します.
ループに入ったところで止まるので,まずはゴー!! (Eclipse だと F8).
次のように出力されます.
DEBUG 2006-11-19 01:06:55,750 [main] BEGIN org.example.service.impl.PrintServiceImpl#print() DEBUG 2006-11-19 01:06:55,750 [main] BEGIN org.example.service.impl.MessageServiceImpl#getMessage() DEBUG 2006-11-19 01:06:55,765 [main] END org.example.service.impl.MessageServiceImpl#getMessage() : 新大陸へようこそ 新大陸へようこそ DEBUG 2006-11-19 01:06:55,765 [main] END org.example.service.impl.PrintServiceImpl#print() : null
2 回目のループで止まっているはずなので,ここで MessageServiceImpl
を修正します.
もちろん実行したまま.
MessageServiceImpl.java
package org.example.service.impl; import org.example.service.MessageService; public class MessageServiceImpl implements MessageService { public String getMessage() { return ("エビちゃん最高!!"); } }
そしてゴー (F8)!!
DEBUG 2006-11-19 01:08:49,421 [main] BEGIN org.example.service.impl.PrintServiceImpl#print() DEBUG 2006-11-19 01:08:49,421 [main] BEGIN org.example.service.impl.MessageServiceImpl#getMessage() DEBUG 2006-11-19 01:08:49,421 [main] END org.example.service.impl.MessageServiceImpl#getMessage() : エビちゃん最高!! エビちゃん最高!! DEBUG 2006-11-19 01:08:49,421 [main] END org.example.service.impl.PrintServiceImpl#print() : null
ちゃんとメッセージが変わりました.
まだ繰り返しの中で止まっているはずなので,今度は新たなクラスを追加して MessageService
のインタフェースごと変えてみましょう.
次のパッケージを作成します.
org.example.dto
そして新たなクラス,MessageDto
を作成します.
package org.example.dto; public class MessageDto { private String text; public MessageDto(String text) { this.text = text; } public String getText() { return text; } public void setText(String text) { this.text = text; } }
MessageService
を DTO を返すように修正します.
MessageService.java
package org.example.service; import org.example.dto.MessageDto; public interface MessageService { public MessageDto getMessage(); }
実装クラスも修正します.
MessageServiceImpl.java
package org.example.service.impl; import org.example.dto.MessageDto; import org.example.service.MessageService; public class MessageServiceImpl implements MessageService { public MessageDto getMessage() { return new MessageDto("大好き! ぷよぷよ!!"); } }
最後に MessageService
を利用している PringServiceImpl
を修正します.
PrintServiceImpl.java
package org.example.service.impl; import org.example.service.MessageService; import org.example.service.PrintService; public class PrintServiceImpl implements PrintService { private MessageService messageService; public void setMessageService(MessageService messageService) { this.messageService = messageService; } public void print() { System.out.println(messageService.getMessage().getText()); } }
そしてゴー (F8)!!
DEBUG 2006-11-19 01:14:09,015 [main] BEGIN org.example.service.impl.PrintServiceImpl#print() DEBUG 2006-11-19 01:14:09,015 [main] BEGIN org.example.service.impl.MessageServiceImpl#getMessage() DEBUG 2006-11-19 01:14:09,015 [main] END org.example.service.impl.MessageServiceImpl#getMessage() : org.example.dto.MessageDto@c8570c 大好き! ぷよぷよ!! DEBUG 2006-11-19 01:14:09,015 [main] END org.example.service.impl.PrintServiceImpl#print() : null
そんなわけで (どんなわけで?),実行途中でも HOT deploy できていることが確認できました♪
さて.
このサンプルでは,4 つのクラス (インタフェース 含む) が登場していました.
MessageService
MessageServiceImpl
PrintService
PrintServiceImpl
途中で追加したクラスもあります.
MessageDto
これらの中で,HOT deploy できないものが一つだけあります.
それは
PrintService
です.
なぜなら,このインタフェースは HOT deploy なコンテキストの外側にいる,Test
クラスから参照されるからです.
もし HotdeployClassLoader
が PrintService
をロードしてしまうと,Test#main()
の
PrintService printer = (PrintService) container .getComponent("printService");
という部分で ClassCastException
が発生します.
Test
からは HotdeployClassLoader
は見えないため,本来のクラスローダー (このサンプルではシステムクラスローダー) から PrintService
をロードします.
しかし,S2 コンテナから返されるのは HotdeployClassLoader
からロードされた PrintService
(を実装したクラス) のインスタンスです.
Java では異なるクラスローダーからロードされたクラスは別のクラスとして扱われ,代入やキャストができません.
そのため ClassCastException
が発生します.
このような事態を極力避けるため,HotdeployClassLoader
はオリジナルのクラスローダー (このサンプルではシステムクラスローダー) にロード済みのクラスはロードしないようになっています.
ところが,Test#main()
を実行する場合に PrintService
がシステムクラスローダーにロードされるタイミングは,予想外に遅いです.
もし Test#main()
の
Class.forName(PrintService.class.getName());
という行がないと,コンテナから返された PrintService
(を実装したクラス) のインスタンスが返された後,それをフィールドに代入する直前までロードされません.
そのため,上記の Class#forName()
を削除すると,コンテナから PrintService
を取得する時点ではシステムクラスローダーに PrintService
がロードされていないため,HotdeployClassLoader
にロードされてしまいます.
その後システムクラスローダーが PrintService
をロードして,めでたく ClassCastException
になるという次第.
この挙動は結構悩ましいです〜.
ともあれ (JW),通常の Web アプリなどでは,HOT deploy のコンテキストの外側にいるのはフレームワーク (Teeda とか S2Struts) で,それらが HOT deploy 対象となるアプリケーションのクラスをロード/リンクすることはまずないと思います.
convention.dicon
に記述するルートパッケージ配下のクラスを dicon に明示的に定義したり,ルートパッケージ配下にないクラスから直接参照したりといったことを避ければ ClassCastException
に悩まされることはそれほどないと思います.
現在開発中の案件ではこれに悩まされているのは内緒だ.
そんなわけで (どんなわけで?),HOT deploy 編をお送りしました.
Seasar2.4 をダウンロードして,是非ともお試しください♪