- PR -

jspでstaticイニシャライザが2回実行される

1
投稿者投稿内容
ほまらら
ベテラン
会議室デビュー日: 2005/10/19
投稿数: 54
投稿日時: 2005-12-01 15:09
こんにちは。
現在JSPで開発を行っています。

全ユーザ通してTomcat起動後に1回だけ実行する作業を、簡略化して記述しようと考え、
以下のように<%! %>の中でstaticイニシャライザを使用するJSPを試しに書いてみました。

<%@ page language="java" contentType="text/html; charaset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Trasitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charaset=UTF-8">
<title>Test</title>
</head>
<body>
<%!
static long _time = -1;
static
{
_time = System.currentTimeMillis();
}
%>
<%= _time %>
</body>
</html>

で、ほぼ目論見どおりには動いたのですが、どういうわけか『1回』ではなく『2回』だけ実行されるようなのです。
つまり、最初に表示した時の_timeと、そこからリロードもしくは別のコンピュータから見に来た時の_timeとで、値が異なっているのです。
2回目以降は一切_timeは変化しないので、『最初に表示したとき』と『2回目に表示したとき』にstaticイニシャライザが動いているようなのです。
これは私の知るstaticイニシャライザの原則『クラスロード時に1度だけ実行される。』と相反する挙動です。
これは一体どういうわけなのでしょうか?それとも、私が何か勘違いしているのでしょうか?

実行環境はCentOS(RedHat系Linux)の4.2と、Tomcat5、J2SE1.4.2_08です。
ほまらら
ベテラン
会議室デビュー日: 2005/10/19
投稿数: 54
投稿日時: 2005-12-01 16:02
追記します

staticイニシャライザの代わりに、インスタンス変数とインスタンスイニシャライザを使った場合も同じ現象が起きました。
また、<%! %>タグ内でinit()メソッドをオーバーライドして、その中で実行させた場合も同じ現象が起きました。

また、それらの際、this.hashCode()の値も変化したので、クラスロードも2回、インスタンスも2回作られたということのように思えます。
一回だけ実行させるにはどうしたら良いのでしょうか?
さぷり
会議室デビュー日: 2005/11/18
投稿数: 18
投稿日時: 2005-12-01 16:15
こんにちわ。

staticイニシャライザに対する回答ではなくてすみません。
代替案としてなのですが、一回だけ実行させたいのならjavax.servlet.ServletContextListener
を利用するのはどうですか?ダメですかね??
ほまらら
ベテラン
会議室デビュー日: 2005/10/19
投稿数: 54
投稿日時: 2005-12-01 16:43
さぷりさんご返答ありがとうございます。
『ServletContextListener』は初めて使ってみたのですが、さっそく試してみました。
方法としては、『HttpJspBase』クラス(←『HttpServlet』クラスを拡張した、JSPがデフォでextendsするクラス)を更にextendsしたクラスを作り、そのクラスで『ServletContextListener』をimplementsして『contextDestroyed』メソッドと『contextInitialized』メソッドを実装。その両メソッドの内、『contextInitialized』の中でlongのインスタンス変数にSystem.currentTimeMillis()を代入する処理を記述。ついでに、インスタンス変数を取得するためのgetLong()メソッドを追加。
そのクラスを<%@page %>タグの中でextendsして<%= super.getLong() %>する、という手順で試してみました。
で結果なんですが、longのインスタンス変数が初期値から動きません・・・。つまり、『contextInitialized』メソッドが呼び出されないみたいです。
JSPがサーブレットと異なるところなのでしょうか・・・。

それと、更に実験した結果、次のような事がわかりました。
2回実行される現象は、『classファイルが作成される時に限られる』ようです。
つまり、単純にTomcatを再起動した場合、問題なく1回だけ実行されます。
しかし、JSPソースを書き換えたり、生成されたclassファイルを削除するなりして、Tomcatがコンパイルして新たにclassファイルを作り直す必要が出てきた場合のみ、『2回』の現象が起こるようです。



[ メッセージ編集済み 編集者: ほまらら 編集日時 2005-12-01 16:44 ]
さぷり
会議室デビュー日: 2005/11/18
投稿数: 18
投稿日時: 2005-12-01 16:51
引用:

ほまららさんの書き込み (2005-12-01 16:43) より:
で結果なんですが、longのインスタンス変数が初期値から動きません・・・。つまり、『contextInitialized』メソッドが呼び出されないみたいです。
JSPがサーブレットと異なるところなのでしょうか・・・。



web.xmlに
<listener>
<listener-class>クラス名</listener-class>
</listener>
を追記されましたか?

引用:

それと、更に実験した結果、次のような事がわかりました。
2回実行される現象は、『classファイルが作成される時に限られる』ようです。
つまり、単純にTomcatを再起動した場合、問題なく1回だけ実行されます。
しかし、JSPソースを書き換えたり、生成されたclassファイルを削除するなりして、Tomcatがコンパイルして新たにclassファイルを作り直す必要が出てきた場合のみ、『2回』の現象が起こるようです。



そうなんですか!あまりJSP内で static initializer を使ったことがないので知りませんでした(^^;
ほまらら
ベテラン
会議室デビュー日: 2005/10/19
投稿数: 54
投稿日時: 2005-12-01 17:05
あいや、web.xmlに記述してませんでした。
なので加えてみましたが、結果は変わらず(とほほ)。
すでに『本当にcontextInitializedが呼び出されない』のか『web.xmlでの指定の方法が間違ってるのか』の判別がつかない状況・・・。

あと、多分staticイニシャライザは無罪のようです。インスタンスイニシャライザ及びinit()でやっても同じでしたので・・・。
こりゃもう、TomcatのJSPコンテナの仕様かバグの領域っぽいですね。
あしゅ
ぬし
会議室デビュー日: 2005/08/05
投稿数: 613
投稿日時: 2005-12-01 18:15
引用:
これは私の知るstaticイニシャライザの原則『クラスロード時に1度だけ実行される。』と相反する挙動です。



引用:
しかし、JSPソースを書き換えたり、生成されたclassファイルを削除するなりして、Tomcatがコンパイルして新たにclassファイルを作り直す必要が出てきた場合のみ、『2回』の現象が起こるようです。



新たにclassファイルを作り直したのならばクラスを再度ロードする必要があります。
クラスロードが複数回行われるのならばstatic initializerも複数回実行されますよ。
これはJSPがServletを生成して動的にロードする仕様なので避けようのないものです。

この時にJVMからは前にロードしたクラスと全く別個に扱われます。
JSPからgetClass()を出力してみるとわかりやすいかもしれません。
ココの話題に出ていたドッペルゲンガーに近い現象でしょう。

解決策は他の方が示されているように、ServletContextListenerがベストかと。
さぷり
会議室デビュー日: 2005/11/18
投稿数: 18
投稿日時: 2005-12-01 18:59
引用:

ほまららさんの書き込み (2005-12-01 17:05) より:

すでに『本当にcontextInitializedが呼び出されない』のか『web.xmlでの指定の方法が間違ってるのか』の判別がつかない状況・・・。



ServletContextListener.contextInitialized(...)が呼ばれていないとは考えにくいので、
web.xmlでの指定の方法がまずいのでしょう。
ただ、ServletContextListenerを実装したclassのインスタンスとそのclassを継承したJSPのインスタンスは
当然別物なので、インスタンス変数での受け渡しだったら意図通りに取得できないでしょう。

ServletContextListenerを実装したclassで取得した値をJSPで使用する方法として、ServletContextを利用する手もありますね。
1

スキルアップ/キャリアアップ(JOB@IT)