相変わらず放置具合が素敵な感じになっていますが、それでもブログ運営を続けます(≧∇≦)
サンプルが書かれているサイトを参考に製作していたため、思わぬ落とし穴に気付かなかった話でも書いてみようかと思います。
Struts+HibernateのWebアプリケーションを製作していたら、データベースに接続するページがやけに遅い気がしました。そこで、実際にLog4Jを用いて計測してみたところ、データベースの接続(Hibernateのセッション接続)のところで0.5秒前後掛かってました。
これはいくらなんでも掛かりすぎだろ...
データベースのコネクションプールが問題なのかと思い、hibernate.cfg.xmlのhibernate.connection.pool_sizeの値を変更してみましたが、状況としては全く変わらずorz
しかし、調べてみると原因は思わぬところにありました。
例えば、何かの情報について検索するDAOに以下のようなメソッドを作成するとします。
public List searchAll() {
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
// 罠にはまった壁
List list = session.createCriteria(クラス名.class).list();
return list;
}
よくHibernateのサンプルを公開しているHibernate初心者向けサイトに行くと、メソッドごとに「//罠にはまった壁」から上の3行が書かれています。それが問題でした。
上の3行をメソッドごとに記述してしまうと、コネクションプールが設定されていたとしても、それを切断して新たなコネクションプールを作成してしまいます。コネクションプールを用いて接続までの時間を短縮させる設定が全く活かされなかったんですね。
ではどうするかと言いますと、「//罠にはまった壁」の上3行のうち、上位2行をプラグインとしてTomcat起動前に読み込ませる必要があります。
package hibernate;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.config.ModuleConfig;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class SamplePlugIn implements PlugIn {
public void init(ActionServlet servlet, ModuleConfig config)
throws ServletException {
// Webアプリケーションの開始処理
System.out.println("*** SamplePlugIn initメソッド 起動 ***");
// Hibernateのセッションを取得
Configuration hibernateConfig = new Configuration().configure();
SessionFactory sessionFactory = hibernateConfig.buildSessionFactory();
// メッセージをServletContextに保存
ServletContext context = servlet.getServletContext();
context.setAttribute("sessionFactory", sessionFactory);
System.out.println("*** SamplePlugIn initメソッド 終了 ***");
}
public void destroy() {
// Webアプリケーションの終了処理
System.out.println("*** SamplePlugIn destory()メソッド ***");
}
}
次に、DAOやStrutsのActionクラス内でServletContext(Appilcationスコープ)に格納した「sessionFactory」を取得し、セッションを作成します。
ServletContext context = servlet.getServletContext();
Session session = context.getAttribute("sessionFactory").openSession();
その後は、Hibernateによりデータベースの処理を記述するだけです。
これでHibernateに0.05〜0.1秒で接続するようになり、体感的に遅く感じなくなりました。
※上の例ではアプリケーションスコープに格納する方法を採用しましたが、init()メソッドにSessionFactoryの作成までを行うstaticメソッドを作成し、同様にHibernateのセッションを静的メソッドで行う方法もあるようです。
そんなわけで、めでたしめでたしということで。
※StrutsのActionでHiberateのセッションを取得したい方は、StrutsからServletContextクラスを取得するにはも参考にどうぞ。
Apache Tomcat 5.0.28+Strutsを使用しているとちょっと困った症状に出会いました。
StrutsのActionサーブレットでHttpServletRequest#getSession(false)のメソッドを用いても、新しいセッションを取得してしまうようです。ちょっとググってみると、Strutsでではないのですが、同様の問題で悩んでいる人がSeasarのMLでもいる模様です。
例えば、
HttpSession session = reqest.getSession(false);
if(session == null) {
// セッションタイムアウトのときの処理
}
を期待して、コーディングするとセッション切れのときは新しいセッションが作られてしまいセッションタイムアウトのときの処理が実行されません。
これを回避するには、あらかじめsessionに何かのオブジェクトを格納しておく方法をとると良さそうです。
例えば、ログインのActionServletにおいて
HttpSession session = req.getSession(true);
session.setAttribute("hoge","何かの文字列");
としておき、ログイン以外のServletにおいては、
HttpSession session = req.getSession(false);
if( (session == null) || (session.getAttribute("hoge") == null) ) {
// セッションタイムアウトのときの処理
}
とすると、新しいセッションが作成されたときに、HttpSession#getAttribute("hoge")がnullにならないのはログイン用のサーブレットを実行し、セッションタイムアウトしていない場合に限定されるので、うまく動作してくれます。
でも、状況的にApache Tomcat 5.0.28のバグの可能性が高そうだなぁ...
Strutsとセッションの話題をしたので、ついでにもう一つ良さそうな話題を紹介します。
@IT - 第4回 安全なセッション管理を実現するために
ここで公開されている内容は、Strutsのログインシステムを作る上で結構役に立ちそうです。
あるユーザでログイン後、セッションが切れる前にブラウザのログインページに戻って別のユーザでログインすると、前のユーザのログイン情報が残って危険な場合も考えられます。そのような問題も防げそうですね。
向こうの記事で十分説明されているので、敢えてこちらは講評だけにしておきます。
Strutsのタグライブラリを用いたページを作っている最中に起きたトラブルへの備忘録です。
Strutsタグがない全てのページの基となるテンプレートページに、Strutsのタグライブラリを付け足しつつ編集していきました。
一通り完成したので、実際に動作させたところ、ページが表示されません。
「org.apache.struts.taglib.html.BEAN という名前のbeanが見つかりません」とエラーがでたものの、どこが原因になっているか分からない...orz
ググってみたところ、Java Look: formの要素のページが見つかりました。
ただし、なぜかindex.rdfの形で( ̄□ ̄;)!!
※上記のリンクはちゃんと普通のページにリンクしているのでご安心をw
ここを見て激しく高速に解決できました。
本来なら<html:form>タグの中に記述しなくてはいけない、<html:radio>、<html:checkbox>、<html:text>などのタグが<html:form>タグの外側に記述されているか、そのページに<html:form>タグがそもそも記述されていないかのどちらかです。
ちなみに、俺の場合、全てのページの基となるテンプレートをいじりながら製作したことで、<html:form>タグを<form>タグのままにしていたことが原因でしたorz
テンプレートをいじりつつ、本来のHTMLタグをActionフォーム用のStrutsタグに置き換えるときには注意ってことで。
WebサーバにApacheを使用していないこのサーバにとっては関係ない話ではありますが、この対策は必要だなと感じていたので記事として紹介しておきます。
@IT - 第1回 たった2行でできるWebサーバ防御の「心理戦」
ちょっとHTTPやTCP/IPの仕組みに詳しい人ならご存じなのですが、HTTPのレスポンスヘッダという箇所にそのサーバで使用しているWebサーバやそのバージョンが分かるのです。
Netcraftというサイトで検索してみると、あのサーバがあのWebサーバやあのOSを使ってるんだ〜(*//▽//*)ということが簡単に分かります(何)
しかし、Webサーバはときに脆弱性を持っているバージョンがあります。当然、脆弱性があるバージョンを使用しない、使用しているWebサーバのバージョンに脆弱性が発見されたらバージョンアップするという作業は必要です。しかし、このような情報をむやみに公開しないというのも、クラックされにくいWebサーバにするちょっとした作業なのです。
上記のページで公開されている手法は「HTTPリプライヘッダの抑制方法」と「エラーページのフッタの抑制方法」です。
それ以外にもセキュリティ強化のための小さなコツつぉいうのはいっぱいあるのでしょうけど、それに関してはApache関係のブログの偉い人に任せます(ぇ?
そんなわけで、Webサーバのセキュリティには注意しましょ〜( ̄∇ ̄)ノ♪
MVCモデルで知られているJSP&ServletのStrutsフレームワークですが、Contorollerの部分にあたるActionを作成しているときにちと困ったことになりました。
普通のサーブレットの場合、サーブレットの中でServletContextを呼び出すには、
ServletContext sc = getServletContext();
で良いわけですが、Strutsの場合は上記のような構文をexecuteメソッドの中に記述しても動きません。
せっかくStrutsのプラグインを用いてあるクラスをアプリケーションスコープに格納したのに取得できないなんてかなりorzな訳ですが。
ググってみたところ、上記のコードにもうひと味付けることで解決できました。
ServletContext sc = getServlet().getServletContext();
Foo bar = (Foo)sc.getAttribute("bar");
例えば、上記のコードのようにすれば、StrutsのActionサーブレットにおいてもApplicationスコープから値を取得できます。
めでたし。めでたし。