tomcat、log4j、エラー

tomcatのシャットダウン時に下記のようなエラーログが出る。


log4j:ERROR LogMananger.repositorySelector was null likely due to error in class reloading, using NOPLoggerRepository.


ぐぐってみても解決策は良くわからない。
どうやらtomcatの6以降とlog4jの1.2.15の組み合わせで起きるようだ。


log4jのソースを見るとstaticブロックで初期化しているstatic変数がnullの場合にこのログが出るようだ。
普通に使っている分にはそんなことにはならないはず。
ところがtomcatでは6以降、シャットダウン時にリフレクションを使用してstatic変数を無理やりnullにするという処理が追加されている。
それによってstatic変数がnullになった後でLoggerインスタンスが要求されるとこういう状況になってしまう。
どうやら1.2.16では修正されているようだが、内容を見るとスタックトレースtomcatのシャットダウンメソッドが含まれていたらerrorではなくdebugで出力するという何ともいきあたりばったりな処理。
まあlog4j側ではどうしようもないんだろうねぇ・・・


ではtomcat側はどうなのか?
そもそもなぜシャットダウン中にLoggerのインスタンスが要求されるのかという点だが、大体においてLoggerのインスタンスはstatic変数になっていることが多く、クラスの初期化時に設定されることがほとんどだと思う。
つまりシャットダウン中にクラスの初期化が行われているということだ。
これはリフレクションを使用してFieldの値を設定している都合上仕方のないことらしい。
ロードされただけで初期化されていないクラスはメンバ(FieldやMethod)を参照する時に初期化が行われる。
ロードしたクラスを一つずつ処理
└クラスのFieldに一つずつnullを設定
という処理だとFieldにnullを設定する最中にもクラスの初期化が行われ、いたちごっこになってしまう。
そのためtomcatでは事前にFieldの値を取得することでクラスの初期化が完全に済んでからFieldにnullを設定するようにしている。


ところがここに問題があるようで、、、
Fieldにnullを設定する時にはField#setAccessible(true)をしているのに、Fieldの値を取得する時にはしていない。
つまりクラスの初期化を行うためにFieldの値を取得しているはずなのに、privateなFieldの場合はIllegalAccessExceptionに邪魔されてクラスの初期化ができていないらしい。
この処理はtry、catchブロックの中にあり、すべてのエラーは無視される。
そのままnullの設定処理に進んでしまうもんだから、そこでクラスの初期化が行われ、Loggerのインスタンスが要求され、しかし必要なstatic変数はすでにnullクリアされた後で・・・というのが冒頭のエラーログの原因なのではないかという気がする。


試しにFieldの値を取得する時にsetAccessible(true)を入れてみたら案の定このエラーログは出なくなった。
最新のtomcatのソースを確認してみたがこのあたりのロジックは変わっていなかった。
何かsetAccessible(true)をしない理由でもあるのかなぁ・・・?