広告 |
---|
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 2010年12月04日 Java総合講座 - 初心者から達人へのパスポート vol.210 セルゲイ・ランダウ バックナンバー: http://www.flsi.co.jp/Java_text/ ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ ======================================================== ◆ 00.お知らせ(バックナンバーの閲覧に関して) ======================================================== ------------------------------------------------------- ・現在、このメールマガジンは以下の2部構成になっています。 [1] 当初からのコース:vol.xxx(xxxは番号)が振られています。 これは現在、中級レベルになっています。 [2] 2009年11月開講コース:xxx号(xxxは番号)が振られています。 これは現在、初心者向けのレベルになっています。 ・このメールマガジンは、画面を最大化して見てください。 小さな画面で見ていると、不適切な位置で行が切れてしまう など、問題を起すことがあります。 ・このメールマガジンに掲載されているソース・コード及び 文章は特に断らない限り、すべて筆者が著作権を所有してい ます。また、これらのソース・コードは学習用のためだけに 提供しているものです。 ------------------------------------------------------- ======================================================== ◆ 01.Strutsのアプリケーション開発(プロジェクト:StrutsShop) ======================================================== 再度DbManagerのソース・ファイルに戻りましょう。 その中のconnect()メソッドの部分 -------------------------------------------------------- public void connect() throws StruShopDbException { logger.info("Start ..............."); try{ if (conn == null || conn.isClosed()) { if ("junit".equals(System.getProperty("ut"))) { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/STRUSHOP", "root", "rootpass"); conn.setAutoCommit(false); return; } Context initCtx = new InitialContext(); if(initCtx == null) throw new StruShopDbException("Error: InitialContext could not be generated!"); DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/STRUSHOP"); if (ds != null) { conn = ds.getConnection(); if(conn == null) throw new StruShopDbException("Error: Connection does not exist!"); else logger.info("Connection has been gotten."); } else { throw new StruShopDbException("Error: DataSource does not exist!"); } } } catch (NamingException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (SQLException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (StruShopDbException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (Throwable e) { logger.error(e, e); } finally { logger.info("End ..............."); } } -------------------------------------------------------- を見ると、この中の catch (Throwable e) { logger.error(e, e); } の部分ではログ出力以外は何もしていません。つまり、 throw new StruShopDbException("Error: Connect() failed!", e); のような処理は行っていません。 これでは、何らかの不慮の例外が投げられてこのcatchブロックが処理をしても、 その例外は呼び出し元には伝わりません。 呼び出し元であるItemListActionクラスのexecute()メソッドに戻っても、 execute()メソッドの中では正常な処理が続行されることになります。そして、 forwardの値が"success"のまま return mapping.findForward(forward); の行までたどり着いてしまうことになります。 試しにこのconnect()メソッドの部分を -------------------------------------------------------- public void connect() throws StruShopDbException { logger.info("Start ..............."); try{ int zero = 0; int zeroDivided = 100 / zero; if (conn == null || conn.isClosed()) { if ("junit".equals(System.getProperty("ut"))) { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/STRUSHOP", "root", "rootpass"); conn.setAutoCommit(false); return; } Context initCtx = new InitialContext(); if(initCtx == null) throw new StruShopDbException("Error: InitialContext could not be generated!"); DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/STRUSHOP"); if (ds != null) { conn = ds.getConnection(); if(conn == null) throw new StruShopDbException("Error: Connection does not exist!"); else logger.info("Connection has been gotten."); } else { throw new StruShopDbException("Error: DataSource does not exist!"); } } } catch (NamingException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (SQLException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (StruShopDbException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (Throwable e) { logger.error(e, e); } finally { logger.info("End ..............."); } } -------------------------------------------------------- のように書き換えてみて下さい。つまり、わざと0で割り算するという誤りを おかすことによって、ArithmeticExceptionを発生させます。 (わざとこんな誤りを組み込むことは馬鹿げていますが、何らかの不慮のエラー の代わりにこの例外を発生させることによって実験を行うのです。) この状態で再度ItemListActionTestのJUnitテストを実行してみると、 やはりforwardの値は"success"になってしまい、テスト結果には、やはり junit.framework.AssertionFailedError: was expectiong '/systemerror.jsp' but received '/itemList.jsp' というエラー・メッセージが表示されてしまいますね。 そこで、このconnect()メソッドの部分を、例えば -------------------------------------------------------- public void connect() throws StruShopDbException { logger.info("Start ..............."); try{ if (conn == null || conn.isClosed()) { if ("junit".equals(System.getProperty("ut"))) { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/STRUSHOP", "root", "rootpass"); conn.setAutoCommit(false); return; } Context initCtx = new InitialContext(); if(initCtx == null) throw new StruShopDbException("Error: InitialContext could not be generated!"); DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/STRUSHOP"); if (ds != null) { conn = ds.getConnection(); if(conn == null) throw new StruShopDbException("Error: Connection does not exist!"); else logger.info("Connection has been gotten."); } else { throw new StruShopDbException("Error: DataSource does not exist!"); } } } catch (NamingException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (SQLException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (StruShopDbException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (Throwable e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } finally { logger.info("End ..............."); } } -------------------------------------------------------- のように書き換えたくなりますが、不慮のエラーに対してStruShopDbExceptionを 投げるのはちょっと変です。 StruShopDbExceptionはデータベースがらみの例外につけて用意したものであり (そのためにDbという名前を含めている)、また、データベースがらみのエラー はほとんどの場合、原因が容易につきとめられるので特別扱いできます。 (ほとんどの場合は、RDBMSがダウンしていた等であり、予め対処手順をマニュアル化 可能で、オペレーターが手順通りに操作すれば済む場合が多い。) その点、その他の不慮のエラーに対してStruShopDbExceptionを用いるのは不適切だし、 混乱の元になります。 (対処法が不確定であり、万全の体制をとって原因を究明しなければわからない 場合が多いので、それを識別するためにStruShopDbExceptionとは別の名前の例外 クラスにしたほうがよい。) ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ そこで、別の例外クラスを用意することにします。ここでは StruShopOtherException という名前で例外クラスを作成しましょう。 (1) プロジェクト・エクスプローラー内のStrutsShopの配下の「Javaリソース: src」 の配下のjp.co.flsi.lecture.strutsを右クリックし、「新規」→「クラス」を 選択します。 (2) 「名前」欄に StruShopOtherException と入力し、「スーパークラス」欄にはjava.lang.Exceptionを指定して、 「完了」ボタンをクリックします。 (3) StruShopOtherException.javaのエディターが開いたら、下記のように 編集しましょう。 -------------------------------------------------------- package jp.co.flsi.lecture.struts; public class StruShopOtherException extends Exception { public StruShopOtherException() { } public StruShopOtherException(String message) { super(message); } public StruShopOtherException(Throwable cause) { super(cause); } public StruShopOtherException(String message, Throwable cause) { super(message, cause); } } -------------------------------------------------------- このファイルを保管(Ctrl+S)し、閉じましたら、DbManager.javaファイル に戻りましょう。 ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ そして先ほどの catch (Throwable e) { logger.error(e, e); } の部分を catch (Throwable e) { logger.error(e, e); throw new StruShopOtherException("Error: Connect() failed!", e); } に書き換えましょう。(コンテンツ・アシストの機能を利用してimport文も入れて おきます。)そして保管(Ctrl+S)しましょう。 すると、 throw new StruShopOtherException("Error: Connect() failed!", e); の部分がコンパイル・エラーになりますね。(そこに赤い下線が引かれ、その行の 左側に赤い×マークが付きます。) これは、このStruShopOtherExceptionが処理されていない(catchもthrowsもされて いない)からです。 この赤い下線が引かれたStruShopOtherExceptionの中にカーソルを入れ、Ctrl+1キー (Ctrlキーを押しながら数字の1のキーを押す)を押しましょう。 そして、ポップアップ・メニューの中から「スロー宣言の追加」を選択(クリックして Enterキーを押す)します。 すると、connect()のthrowsにStruShopOtherExceptionが追加されますね。 念のため、その結果できたDbManager.javaのソース・コード全体を下に提示して おきます。 -------------------------------------------------------- package jp.co.flsi.lecture.struts.db; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Connection; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import jp.co.flsi.lecture.struts.StruShopOtherException; import org.apache.log4j.Logger; public class DbManager { protected Connection conn = null; private static Logger logger = Logger.getLogger(DbManager.class); public void connect() throws StruShopDbException, StruShopOtherException { logger.info("Start ..............."); try{ if (conn == null || conn.isClosed()) { if ("junit".equals(System.getProperty("ut"))) { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/STRUSHOP", "root", "rootpass"); conn.setAutoCommit(false); return; } Context initCtx = new InitialContext(); if(initCtx == null) throw new StruShopDbException("Error: InitialContext could not be generated!"); DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/STRUSHOP"); if (ds != null) { conn = ds.getConnection(); if(conn == null) throw new StruShopDbException("Error: Connection does not exist!"); else logger.info("Connection has been gotten."); } else { throw new StruShopDbException("Error: DataSource does not exist!"); } } } catch (NamingException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (SQLException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (StruShopDbException e) { logger.error(e, e); throw new StruShopDbException("Error: Connect() failed!", e); } catch (Throwable e) { logger.error(e, e); throw new StruShopOtherException("Error: Connect() failed!", e); } finally { logger.info("End ..............."); } } public void disconnect() { logger.info("Start ..............."); try { if (conn != null && !conn.isClosed()) conn.close(); } catch(SQLException e) { logger.error(e, e); } catch (Throwable e) { logger.error(e, e); } finally { logger.info("End ..............."); } } } -------------------------------------------------------- なお、disconnect()メソッドのほうのcatchブロックでもStruShopDbExceptionや StruShopOtherExceptionをthrowする必要があるのではないか、と気にする人も いるかも知れませんが、disconnect()メソッドが呼び出される時点では、アプリ ケーションの本質的な作業(データベースへの書き込みや検索)は終わっている ので、気にする必要はないのです。したがって、ここで例外をthrowする必要は ありません。(このままほうっておくと、次回の接続のときconnect()メソッド でエラーが出るかも知れませんが、そのときには例外がthrowされます。) では、このファイルを保管(Ctrl+S)しておきましょう。 ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ すると、この変更に影響を受けるクラスが出てきますね。プロジェクト・エクスプローラー を見て下さい。 StrutsShop配下の「Javaリソース: src」の配下のjp.co.flsi.lecture.struts配下 のItemSelectForm.javaの左側にコンパイル・エラーのマーク(赤い×マーク)が 付いていますね。 このItemSelectForm.javaファイルを開いてみて下さい。 このソース・コードの中のreset()メソッドの中の -------------------------------------------------------- try { request.setCharacterEncoding("UTF-8"); ServletContext serv = getServlet().getServletContext(); Vector<Category> categoryList = (Vector<Category>) serv.getAttribute("categoryList"); if (categoryList == null) { dbManager.connect(); categoryList = dbManager.getDataByName(""); serv.setAttribute("categoryList", categoryList); } } catch (UnsupportedEncodingException e) { logger.error(e, e); } catch (StruShopDbException e) { logger.error(e, e); } -------------------------------------------------------- の中の dbManager.connect(); の部分に赤い下線が引かれていますから、そこにマウス・ポインターを持っていくと StruShopOtherExceptionが処理されていないためのエラーであることがわかりますね。 したがって、StruShopOtherExceptionに対するcatchブロックを追加すればエラーは 消えますので、下記のように編集しましょう。 -------------------------------------------------------- try { request.setCharacterEncoding("UTF-8"); ServletContext serv = getServlet().getServletContext(); Vector<Category> categoryList = (Vector<Category>) serv.getAttribute("categoryList"); if (categoryList == null) { dbManager.connect(); categoryList = dbManager.getDataByName(""); serv.setAttribute("categoryList", categoryList); } } catch (UnsupportedEncodingException e) { logger.error(e, e); } catch (StruShopDbException e) { logger.error(e, e); } catch (StruShopOtherException e) { logger.error(e, e); } -------------------------------------------------------- なお、DbManagerのときと同様に、ここでもcatchブロックに catch (StruShopOtherException e) { logger.error(e, e); throw new StruShopOtherException("・・・"); } のようにthrow文を入れることによって、reset()メソッドの外に例外を伝えたい と思うかもしれませんが、それはできません。 なぜかというと、このreset()メソッドはActionFormのreset()メソッドを オーバーライド(override = 上書き)したものであり、メソッドに throwsを勝手に付け足して public void reset(ActionMapping mapping, HttpServletRequest request) throws Exception のような定義形式にすることはできないからです。 ┌補足─────────────────────────┐ ここでいう定義形式とは、メソッドの(publicなどの)修飾子、 戻り値、メソッド名、引数、およびthrows節を組み合わせた形式 (たとえば public int nantokaMethod(int param1, String param2) throws NantokakantokaException のようなもの)を言い、Javaにおけるオーバーライド(override) では、定義形式に一定のルールがあります。 オーバーライド(override)は(当メールマガジン3回目の オブジェクト指向の話のときに出てきましたが)Javaではサブ クラスとスーパークラスでメソッド名と引数が同じ(ここでいう 「引数が同じ」とは、引数の個数および各引数の型が同じである ことを意味する。つまり、引数のパターンが同じであることを 意味する)メソッドが存在すると、オーバーライドされたと みなされます。 つまり、同じ呼び出し形式(= シグネチャー(signature))のメ ソッドが存在すると、オーバーライドされたとみなされます。 このとき両者のメソッドで定義形式に食い違いがあると (たとえば戻り値の型に互換性がなかったり、throws節の例外 の型に互換性がないと)コンパイル・エラーになります。 (サブクラスにおける戻り値やthrows節の例外の型はそれぞれ スーパークラスのものと同一か、もしくはそれぞれスーパー クラスにおける戻り値やthrows節の例外のサブクラスになって いなければならない。つまり、サブクラスのものはスーパー クラスのものと同一の型かもしくはサブクラスになっていなけ ればならないのである。) また、アクセス制御の修飾子は同一か、もしくは、protectedを publicに書き換えるなど公開度を上げる方向に修飾子を書き換 えることはできますが、逆はできません。(「元々非公開だっ たものを公開するのは簡単だが、既に公開してしまって皆に 知れ渡ってしまったものを非公開にするのは簡単ではない。」 と覚えておきましょう。) なお、privateのメソッドをオーバーライドすることはできま せん。もしprivateのメソッドと同じメソッド名、同じ引数の パターン(同じシグネチャー)のメソッドをサブクラスに作成 した場合は、それは別のメソッドと見なされます。なぜなら、 privateはそのクラス外には非公開でサブクラスからさえも見え ないので、サブクラスにはスーパークラスにおける同じメソッ ドの存在がわからないからです。 つまり、ちょっと紛らわしいですが、スーパークラスの privateのメソッドと同じシグネチャーのメソッドをサブクラス に作成した場合は、それはオーバーライドとは呼ばないのです。 また、修飾子にstaticが指定されているメソッド(staticメソッド) の場合はサブクラスでstaticをはずすことはできませんし、 その逆もできません。 つまり、staticの指定の有無は両者で一致していなければなり ません。 └───────────────────────────┘ では、このreset()メソッドの外に例外の情報を伝えたいときにはどうすれば いいかと言うと、request(HttpServletRequest)オブジェクトやsession (HttpSession)オブジェクトの属性を通して情報を受け渡してやります。 では、実際にやってみましょう。 でもその前に、現時点でこのWebアプリケーションがどのような動きになるか 確認してみましょう。 例によってMySQLを停止してから、ItemListActionTestのJUnitテストを実行して みて下さい。テスト結果はOK(緑色)になりますね。 ところが、StrutsTestCaseではあくまでActionクラス(ここではItemListAction) のexecute()メソッド(およびexecute()メソッドの中で呼び出される各オブジェクト のメソッド)のテストだけをしていることに注意して下さい。ActionForm(ここでは ItemSelectForm)のreset()メソッドなどのテストはしてくれません。 そこで、ActionForm(のreset()メソッドなど)の動作に問題がないかどうか検証する ために、Tomcatを起動してテストします。 (1) 「サーバー」ビューの中の「ローカル・ホストのTomcat v5.5サーバー」を 右クリックし、「開始」を選択します。しばらくして、 ローカル・ホストのTomcat v5.5サーバー[始動済み,同期済み] というように、後ろに「始動済み」の表示が出たら、Tomcatの起動が完了しています。 (2) Webブラウザー(Internet Explorer)を起動して、URL http://localhost:8080/StrutsShop/itemSelect.jsp を入力しましょう。 すると「商品の検索」のWebページは開かずに何やらエラー・メッセージが出ますね。 その中に org.apache.jasper.JasperException: categoryList という名前のbeanが見つかりません というようなメッセージがあるはずです。 Eclipseのコンソールの中のログにも同様に「categoryList という名前のbeanが見つかりません」 というメッセージが出ているはずですが、そのスタック・トレースを追っかけていくと at org.apache.jsp.itemSelect_jsp._jspx_meth_html_005foptions_005f0(itemSelect_jsp.java:251) というような行がありますね。これはitemSelect.jspから自動生成されたJavaファイルの中で エラーが起こっていることを意味していますが、itemSelect.jspの中に指定されているcategoryList をItemSelectForm.javaのソース・コードの中で探せば、原因がわかります。 ItemSelectForm.javaの中にある -------------------------------------------------------- if (categoryList == null) { dbManager.connect(); categoryList = dbManager.getDataByName(""); serv.setAttribute("categoryList", categoryList); } -------------------------------------------------------- という部分の中の dbManager.connect(); のところでデータベースへの接続時に例外が発生したためにその下のcatch文のほうに 飛んでしまい、 categoryList = dbManager.getDataByName(""); serv.setAttribute("categoryList", categoryList); の行は実行されなかった訳ですね。 すると、categoryListはnullのままだし、いずれにしても serv.setAttribute("categoryList", categoryList); が実行されていないためにcategoryListの情報はJSPのほうには伝わりません。 というわけで、「categoryList という名前のbeanが見つかりません」というエラーに なるわけです。 そこで、データベースに接続できず例外が発生したときには(catchブロックの中などで) categoryList = new Vector<Category>(); Category aCategory = new Category(); aCategory.setName("すべて"); aCategory.setNum("99999"); categoryList.add(aCategory); serv.setAttribute("categoryList", categoryList); のようなコードで、categoryListをちゃんと用意するようにしておけば、 先ほどのエラーは出なくなります。 しかしながら、データベースに接続できないのであれば、結局のところ それから先の操作には進められないことになりますから、こういった小細工 をしても無駄になります。 というわけで、こういった無駄な抵抗は、やめることにしましょう。 そして、データベースに接続できず例外が発生したときには直ちに 前回のsystemerror.jspのWebページ、つまり まことに申し訳ございません。 現在システムがダウンしています。 数時間程度で回復する予定ですので、数時間のちに再度 初めから操作をやり直してみて下さい。 あるいは、お急ぎの場合はTel:123-456-7890までお問合せ下さい。 というようなメッセージ内容のWebページを表示するようにしましょう。 では、先ほどのrequest(HttpServletRequest)オブジェクトやsession(HttpSession) オブジェクトの属性を通して例外の情報を受け渡す話に戻り、このWebページを表示 するためのプログラミングをすることにします。 (話が長くなりましたので、いったんここで区切ります。) ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ (次回に続く) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ★ホームページ: http://www.flsi.co.jp/Java_text/ ★このメールマガジンは 「まぐまぐ(http://www.mag2.com)」 を利用して発行しています。 ★バックナンバーは http://www.flsi.co.jp/Java_text/ にあります。 ★このメールマガジンの登録/解除は下記Webページでできます。 http://www.mag2.com/m/0000193915.html ★このメールマガジンへの質問は下記Webページにて受け付けて います。わからない所がありましたら、どしどしと質問をお寄 せください。 http://www.flsi.co.jp/Java_text/ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Copyright (C) 2010 Future Lifestyle Inc. 不許無断複製 |