HOT deployでのClassCastException対策
Seasar2.4.xのHOT deployでは、filterやutil、entityといったのようなSMART deploy対象外のクラスから、actionやservice、dao、dtoといったSMART deploy対象のクラスを参照すると、ClassCastExceptionが発生する。この対策を考えてみた。
ちなみに、COOL deployでは発生しない。
2009/4/14 追記
utilやentityがSMART deploy対象外クラスと書いたが、S2Containerにコンポーネントの自動登録がされない(おそらくS2Containerがインスタンスを管理する必要がないから)だけで、コードの変更はHotに反映されるし、クラスローダーもちゃんとHotdeployClassLoaderになる。なので、SMART deploy対象外というのは誤りと思いますので、訂正しました。
原因
なぜ、HOT deployでは、SMART deploy対象外のクラスからSMART deploy対象のクラスを参照すると、ClassCastExceptionが発生するのか?例えば、SMART deploy対象外のクラスで次のような記述があると、ClassCastExceptionが発生する。
XxxDto xxxDto = (XxxDto) container.getComponent("xxxDto");
上記の場合、S2Containerから取得されるXxxDtoは、dtoがSMART deploy対象クラスであり、HOT deploy対象クラスとなるため、org.seasar.framework.container.hotdeploy.HotdeployClassLoaderでロードされたXxxDtoとなる。一方、キャスト先のXxxDtoは、この記述を実装しているクラスがSMART deploy対象外であるため、オリジナルのクラスローダー(Tomcatならorg.apache.catalina.loader.WebappClassLoader)にロードされたXxxDtoとなる。Javaでは、同一クラスであってもロードしたクラスローダーが異なれば、別クラスとして認識されるため、ClassCastExceptionが発生してしまう。
ちなみに、これがWebアプリであれば、一度ClassCastExceptionが発生すると、次回のリクエスト以降は発生しなくなる。これは、XxxDtoがClassCastException発生直前にオリジナルクラスローダーにロード済みのため、次回以降のリクエスト時には、例えSMART deploy対象クラスであってもHotDeployClassLoaderがXxxDtoをロードしないようになっており、S2Containerから取得されるXxxDtoもオリジナルクラスローダーにロードされたXxxDtoとなるため。
対策
原因を見れば分かる通り、同一のクラスローダーにロードされるようにすればClassCastExceptionは防げる。
その方法をいくつか考えてみる。
案1.SMART deploy対象外クラスを対象にする。
全部HotDeployClassLoaderにロードさせるようにできればClassCastExceptionは起きないという発想だけど、Creator、Customizerを作成して、S2Containerにコンポーネント登録させるところはできたとしても、HOT deployに対応させるのは、おそらく相当敷居が高いと思う。
案2.SMART deploy対象クラスとSMART deploy対象外クラスを明確に分離して、対象外クラスから対象クラスを参照しないようにする。
対象クラスと対象外クラスでパッケージを分けてしまう。
http://d.hatena.ne.jp/higayasuo/20090317/1237268663
や
http://ml.seasar.org/archives/seasar-user/2009-February/016916.html
で、示されている方法。これが公式な対策ということになるのかな?
ただ、個人的にはどうもこの方法はしっくりこない。
例えば、Filterでアクセスロギングを行っていて、ログインユーザー情報も併せてロギングするために、FilterでHttpSession上のログインユーザーDtoを取得しているようなアプリの場合、パッケージ分割の考え方にならうと、ログインユーザーDtoはSMART deploy対象外パッケージに置くことになる。開発体制として、Filter等の共通処理部分は共通基盤チームみたいなのが開発して、個々の業務画面等を個々の開発者が開発するような体制(SIerではよくある体制だと思う)を想定した場合、ログインユーザーDtoなんかは、色んな画面で参照するものなので、個々の開発者が皆、パッケージの違いを意識しないといけなくなってしまう。個々の開発者はSMART deploy対象クラスからログインユーザーDtoを参照するので元々ClassCastExceptionが発生しないにも関わらず、だ。個々の開発者も皆、HOT deployのクラスローダー云々とかClassCastException云々を知るべきでその上でパッケージ分割理由も皆で共有する、というような方針であれば、これでいいと思うけど、個々の開発者にそこまで意識させない方針であれば、パッケージ分割がどうしても非直観的になってしまわないだろうか?という気がする。
2009/3/19 追記
コメントで指摘いただいた。パッケージの違いはIDEが吸収してくれる(自力でパッケージを書くわけではない)から個々の開発者が意識する必要はないと思う。
案3.diconに書く。
SMART deploy対象外クラスから参照されるSMART deploy対象クラスをdiconに書くことで、SMART deploy対象外にする方法。Seasar2.3のコンポーネント自動登録を使うのも同様。
デメリット?は、SMART deploy対象外になるので、customizer.diconで定義したInterceptorとかが適用されなくなること。まあ、S2Containerの管理対象からはずれるわけではないので、Seasar2.3の記述方法で記述すれば、AOPも仕掛けられるけど。
案4.HotDeployClassLoaderより先にオリジナルクラスローダーにロードさせる。
上述の通り、HotDeployClassLoaderはオリジナルクラスローダーにロード済みのクラスはロードしないので、SMART deploy対象外クラスから参照されるSMART deploy対象クラスを、HotDeployClassLoaderより先にオリジナルクラスローダーにロードさせることで、オリジナルクラスローダーのみを使用することとなり、ClassCastExceptionを防ぐ。
HOT deploy対象外だけど、SMART deploy対象ということになる。なので、案3.のようにcustomizer.diconの定義内容が適用されないなんてこともない。
やり方は、Webアプリであれば、例えば次のようなFilterを作成し、web.xmlで、HotdeployFilterよりも前に適用されるように定義すればよい。HOT deployのためだけの処理が運用時に入っているのが気に入らなければ、COOL deploy時は、web.xmlでコメントアウトすることで処理をはずすこともできる(案3.でもdiconから記述を削除すれば同じことになりそうだけど、それによってcustomizer.diconの適用がされることになったり等、影響が出るのでこちらの方が安心な気がする)
XxxDtoがSMART deploy対象外クラスから参照される場合の例
public class ExcludeHotdeployFilter implements Filter { private FilterConfig filterConfig; public void init(FilterConfig filterConfig) { this.filterConfig = filterConfig; } public void destroy() { this.filterConfig = null; } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { Class clazz = XxxDto.class; chain.doFilter(req, res); } }
例では、XxxDtoを直書きしているけど、Filterなのでパラメータを使って、Class.forName()でやれば、web.xmlにクラス名を記述することも可能。
とりあえず、Webアプリであれば、案4.が簡単でデメリットも特になくてよいと思う。
ただし、あまりないとは思うけど、SMART deploy対象外クラスから参照するSMART deploy対象クラスが大量にある場合は、案3.とか案4.もしんどそうなので、案2ということになるかなあ。