■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 2008年04月20日 楽しいJava講座 - 初心者から達人へのパスポート vol.100 セルゲイ・ランダウ バックナンバー: http://www.flsi.co.jp/Java_text/ ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ [このメールマガジンは、画面を最大化して見てください。] ======================================================== ◆ 01.Tomcatのアプリケーション開発 ======================================================== では、前回の答えです。 今まで何度かお話してきたように、Webアプリケーションの開発 にあたって面倒なことの一つは、文字コードの取り扱いです。 というわけで、このアプリケーションで扱われる文字コードに ついて整理しておきましょう。 まず、Javaはプログラム内部の文字コードとしてUnicodeを採用 しているのでしたね。 つまり、vol.015でお話したように、Javaのchar型やString型で 保持される文字はUnicodeの形式になっています。 ところが、MySQLのデータベースに保持される文字はShift_JISに 設定していました(vol.084参照)。 さて、この間の文字コードの変換は如何? このJavaプログラム内のUnicodeとデータベース上のShift_JIS との間の変換はどうなっているのかというと、実は、MySQLの JDBCドライバーが自動的に変換してくれます。 (この変換はMySQLのJDBC URLを指定するときに明示的に指定する こともできますが、デフォルトで変換されることになっていますの で、明示的な指定は不要です。) というわけで、Javaプログラムとデータベースの間の文字コードの 変換については問題はないはずですな。 あと問題になるのは、Webブラウザー上の文字コードとサーバー側 (サーブレットやJSP = Javaプログラム)の文字コードとの間の 変換ですな。 そこで、vol.087を復習してみましょう。 サーブレットはその変換の機能を持っているのですが、あらかじめ どの文字コードで送られてくるのかをサーブレットに教えてあげな ければ変換できません。 そこで、vol.087でお話したように、たとえばWebブラウザーでの エンコーディングがShift_JISの場合(つまり、文字コードがShift_JIS の場合)は、HttpServletRequestオブジェクト(正確にはそのスーパー クラス)のsetCharacterEncoding("Shift_JIS")メソッドを呼び出すこと によって、文字コードがShift_JISであることをサーブレットに教え ます。 そうすると、サーブレットは、送られてきたデータをShift_JISから Unicodeに変換して取り込んでくれます。 ところが、もしsetCharacterEncoding()メソッドに指定する引数(エンコー ディング)が間違っていた場合はどうなるでしょう。 サーブレットは、そのまま間違った変換をしてしまいます。 すると、MySQLのJDBCドライバーがいくらUnicodeからShift_JISへの変換を 行っても、元のUnicodeのデータが間違っているわけですから、データベース に保管された文字は文字化けした状態になります。 そこで、各サーブレットを確認してみましょう。 まず、vol.087で説明したItemServletでは、setCharacterEncoding()メソッド の引数は"Shift_JIS"になっていますが、これは、このサーブレットを呼び出す HTML(itemSelect.html)がShift_JISでエンコーディングされているからです。 ところが、その後、JSPによって生成されるWebページは、Shift_JISではなく、 UTF-8でエンコーディングされます。 というのは、各JSPでは、 -------------------------------------------------------- <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="Shift_JIS" %> -------------------------------------------------------- というようにcharset=UTF-8を指定をしておきましたから、これらのJSPから 生成されるWebページのエンコーディングはUTF-8になります。 そうすると、これらのWebページから入力されるデータを受け取るサーブレット では、setCharacterEncoding()メソッドを呼び出すときの引数にはUTF-8を 指定しなければなりません。 ところが、今までのサーブレットはすべて、 req.setCharacterEncoding("Shift_JIS"); というように、Shift_JISを指定していました。 これは、最初のItemServletのソース・コードをコピーして利用し、そのまま 修正するのを忘れていたからなのですが、ユーザー登録の機能を組み込むまでは 気がつきませんでした。それまで文字化けが発生しなかったからです。 では、なぜそれまで文字化けが発生せず、ユーザー登録の機能を組み込んだ時点 で文字化けが発生したかというと、ユーザー登録の機能を組み込む前は、Webページ に入力されるデータは半角英数字のみだったからです。 実は、Shift_JISもUTF-8も半角英数字の部分だけに限ると同じコードを使用して います。半角英数字の部分はどちらもASCII(American Standard Code for Information Interchange)という文字コードと同じコードを使用しているので、 文字化けが発生しなかったのです。 しかし、ユーザー登録の機能では、漢字など(いわゆる全角文字)を入力します から、Shift_JISとUTF-8ではコード体系が違うために文字化けが発生したのです。 というわけで、今回の問題は、少なくとも、ユーザー登録の機能に関わるサーブレッ トの中で、 req.setCharacterEncoding("Shift_JIS"); を req.setCharacterEncoding("UTF-8"); に書き換えれば解決します。 では、UserRegServletを修正しましょう。 -------------------------------------------------------- package jp.co.flsi.lecture.webapp.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import jp.co.flsi.lecture.webapp.db.CustomerDbManager; import jp.co.flsi.lecture.webapp.entity.Customer; public class UserRegServlet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { req.setCharacterEncoding("UTF-8"); HttpSession session = req.getSession(); String customerNum = req.getParameter("userId"); String customerName = req.getParameter("name"); String customerZipCode = req.getParameter("zipCode"); String customerAddress = req.getParameter("address"); if (customerNum == null || customerName == null || customerZipCode == null || customerAddress == null) { getServletContext().getRequestDispatcher("/wrongUserInfo.jsp").forward(req,res); } else { Customer customer = new Customer(); customer.setNum(customerNum); customer.setName(customerName); customer.setZipCode(customerZipCode); customer.setAddress(customerAddress); session.setAttribute("CUSTOMER",customer); CustomerDbManager customerDb = new CustomerDbManager(); customerDb.selectData(customerNum); if (customerDb.getCustomerList().size() > 0) { getServletContext().getRequestDispatcher("/userReg2.jsp").forward(req,res); } else { customerDb.insertData(customer); getServletContext().getRequestDispatcher("/userInfo.jsp").forward(req,res); } } } public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { getServletContext().getRequestDispatcher("/bookmarkinvalid.html").forward(req,res); } } -------------------------------------------------------- (念のためにUserRegServletの全ソース・コードを提示しましたが、 修正したのは言うまでも無く req.setCharacterEncoding("UTF-8"); の部分だけです。) では、これでテストしてみましょう。 EclipseでTomcatを起動し、Webブラウザーを起動して、下記のURLを 入力してください。 http://localhost:8080/JStudy2/itemSelect.html (1) 「キーワード」欄、「カテゴリー」欄ともに何も入力せずに 「送信」ボタンをクリックしてみてください。 (2) 次のページ(商品リストのページ)で商品をどれか(たとえば「製品001」) 選択(チェック・マークを入れる)し、「選択した商品をカートに入れる」 ボタンをクリックします。 (3) 選択した商品が次のページ(ショッピング・カートのページ)で リストされていることを確認し、「選択した商品を購入する」ボタンを クリックします。 (4) 次のページ(購入手続きのページ)で、 「ユーザー登録がお済みでない方は、先にここをクリックしてユーザー登録を行ってください。 」 をクリックします。 (5) 次のページ(ユーザー登録のページ)で、ユーザーIDに UUUU0、パスワードはxxxxx 、氏名に山本山太郎、郵便番号は222-2222、住所はほにゃら県ほにゃら町1-1-1と入力 して、「規約に同意しユーザー登録をする」ボタンをクリックしましょう。 (6) 次のページ(ユーザー登録のページ)で「注文手続きに進む」ボタンをクリック しましょう。 (7) 次のページで、購入する商品がちゃんとリストされていることを確認し、 商品の送付先を確認しましょう。今度は文字化けしていないですね。 めでたし、めでたし。 ・・・・と思ったら、「購入(注文確定)する」ボタンをクリックすると、 何やら訳のわからないエラーが出ますね。エラーの文を読んでいくと、 The requested resource (/JStudy2/completeorder) is not available. というのが書かれているはずです。これは、/JStudy2/completeorder というURL(最初から全部書くと http://localhost:8080/JStudy2/completeorder というURL)で獲得できるはずの物が見つからないという意味です。 何でこんなことになったかというと、これまでは、 Webページ → サーブレット → JSP(またはHTML)[これがWebブラウザーにWebページを返す] という順番で呼び出しを行っていたのですが、今回は Webページ → JSP (具体的にはuserInfo.jspが生成するWebページ → confirmOrder.jsp) というようにサーブレットをすっ飛ばしてJSPを呼び出したために、 相対的なURLの指定にずれが生じてしまったのです。 サーブレットの場合は、 /JStudy2/servlet/xxxxxx というようなURLで呼び出すところが、JSPの場合は /JStudy2/xxxxxx.jsp というようなURLで呼び出しますので、サーブレットを経由してconfirmOrder.jsp を呼び出す場合と、サーブレットをすっ飛ばしてconfirmOrder.jspを呼び出す 場合では、servlet/というディレクトリー名が付くか付かないかで相対的な URLに違いが生じてしまいます。 というわけで、confirmOrder.jspの中で指定されている <FORM action="completeorder" method="POST"> のcompleteorderという相対的URLが、サーブレット経由のときには http://localhost:8080/JStudy2/servlet/completeorder を意味していたのに対して、今回のようにサーブレットをすっ飛ばす と http://localhost:8080/JStudy2/completeorder を意味することになり、completeorderという相対的URLで指定され るサーブレットが見つからないというエラーになってしまうのです。 うーん、何だか面倒ですな。サーブレットをすっ飛ばさなければ よかったですな。では、サーブレットを経由するように修正しま しょか。 ・・・・いやいや、そうではなくて、サーブレットを経由したのか 経由しなかったのかを区別して相対URLを変更するようにすればいいわけ です。 では、対応策としてconfirmOrder.jspを下記のように修正してください。 -------------------------------------------------------- <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="Shift_JIS" %> <HTML> <HEAD> <TITLE>購入(注文)内容の確認</TITLE> </HEAD> <BODY bgcolor="#77ffff" text="#fa5a00"> <H1>購入する商品:</H1> <%@ page import="java.util.*" %> <% String directory = ""; Enumeration paramNames = request.getParameterNames(); while(paramNames.hasMoreElements()) { String aParamName = (String)paramNames.nextElement(); if (aParamName.equals("checkout")) directory = "servlet/"; } %> <FORM action="<%= directory %>completeorder" method="POST"> <TABLE border="1" width="100%"> <TBODY> <TR> <TH>商品番号</TH> <TH>商品名</TH> <TH>価格</TH> <TH>カテゴリー</TH> <TH>購入個数</TH> </TR> <%@ page import="jp.co.flsi.lecture.webapp.entity.*" %> <% int totalPrice = 0; %> <jsp:useBean class="jp.co.flsi.lecture.webapp.entity.Order" id="ORDER" scope="session" /> <% OrderItem anOrderItem; for (int i = ORDER.getOrderItems().size() - 1; i >= 0 ; i--) { anOrderItem = ORDER.getOrderItems().elementAt(i); %> <TR> <TD><%= anOrderItem.getNum() %></TD> <TD><%= anOrderItem.getName() %></TD> <TD><%= anOrderItem.getPrice() %>円</TD> <TD><%= anOrderItem.getCategory() %></TD> <TD align="right"><%= anOrderItem.getOrderQuantity() %></TD> </TR> <% totalPrice += anOrderItem.getPrice() * anOrderItem.getOrderQuantity(); } %> </TBODY> </TABLE> <BR> <TABLE border="1"> <TR> <TH>合計金額:</TH> <TH><%= totalPrice %>円</TH> </TR> </TABLE> <BR> <BR> <H1>商品の送付先:</H1> <jsp:useBean class="jp.co.flsi.lecture.webapp.entity.Customer" id="CUSTOMER" scope="session" /> <TABLE border="1"> <TR> <TH>郵便番号</TH> <TH>住所</TH> <TH>氏名</TH> </TR> <TR> <TD><%= CUSTOMER.getZipCode() %></TD> <TD><%= CUSTOMER.getAddress() %></TD> <TD><%= CUSTOMER.getName() %></TD> </TR> </TABLE> <BR> <BR> この内容で購入(注文)を確定するには、下の「購入(注文確定)する」ボタンを押してください。 <BR> <FONT color="#ff0000"><STRONG> なお、「購入(注文確定)する」ボタンを押すと注文が確定してしまい、取り消しはできませんのでご注意ください。 </STRONG></FONT> <BR> <BR> <INPUT TYPE="submit" NAME="completeOrder" VALUE="購入(注文確定)する"> </FORM> </BODY> </HTML> -------------------------------------------------------- どこをどう修正したのかはわかりますね。 これでテストしてみると、今度は、さっきの問題は発生しませんね。 めでたし。めでたし。 このように、サーブレットを経由しなくても、JSPも所詮はサーブレットの 一種なので臨機応変にプログラミングできるわけです。 ですが、これはあくまで技術上の話であり、本来はサーブレット経由でJSPを 呼び出すようにすべきであり、上記のようなJSPのプログラミングは好ましく ありません。 では、今度は、ユーザー登録のページを開くためのボタンを最初の商品検索の Webページにも付けてみましょう。 これは読者の皆様への課題としておきます。 自力で課題を解けないという人は、下記のWebページにて質問してください。 では、いよいよ、このWebアプリケーションをデプロイしてみましょう。 (次回に続く) 次回は、デプロイを行ったあと、Strutsのお話に入ります。 Strutsは、これまでに作ってきたようなWebアプリケーションの開発を 楽にするためのフレームワーク(Framework、元々はアプリケーション・ フレームワーク(Application Framework)と呼ばれていたものが、最近は たんにフレームワークと略されるようになってきたもの)で、これを 使うと、Webページ→サーブレット(→Bean)→JSP型のアプリケーション開発 が整理されて、開発が楽になります。(もちろん、Strutsを使っても これまでに学んできた知識は必要になります。) 我々がこれまで作成してきたWebアプリケーションでは、サーブレット がやたらにごちゃごちゃたくさん出てきましたが、Strutsでは既成の サーブレット一つだけになります。 という話をすると、非常に簡単になるような感じがするかも知れません が、実際はそんなに生易しいわけでもありませんけど。 でも、とにかく現在のWebアプリケーション開発では、Strutsを使う ことが主流になりつつありますので、これを使いながら、Webアプリ ケーション開発の話をさらに詳しくしていくことにします。 なお、フレームワークとは何ぞや、というお話も次回いたします。 日本語の翻訳書を見ると、Frameworkを枠組みと訳しているものが 多いようですが、これでは何だかよくわかりませんでしょうな。 では、今日はここまでにします。 何か、わからないところがありましたら、下記の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) 2008 Future Lifestyle Inc. 不許無断複製 |