• JSP、Servlet 與 JavaBean 的組合應用


    http://sun.cis.scu.edu.tw/~nms9115/articles/java/WebAppTutor/JspServletBean/JspServletBean.htm

    JSP、Servlet 與 JavaBean 的組合應用

    作者:蔡煥麟
    日期:Jan-22-2003
    更新:Feb-4-2003 


    1.0 簡介

    上次的討論中,最後有一個範例是判斷質數的 JSP 程式,該程式在 JSP 中嵌入許多 Java code,我們也說過這是不好的設計方式,這次就來看看怎麼樣把這些 Java code 從 JSP 中抽離出來,成為獨立的類別(稱為 JavaBeans),並且示範如何在 JSP 裡面呼叫這些 JavaBeans 。另外,也會一併介紹由 Servlet 呼叫 JSP 的方式,之前看的範例程式,其流程、邏輯、和資料展現都放在 JSP,這種設計方式稱為 page-centric 架構,或 Model-1 架構(圖 1),現在開始撰寫的範例會將控制權交給 servlet,以 servlet 為控制中心,掌控程式的流程以及 HTML/JSP 網頁的分派,這是一種 servlet-centric 的架構,也稱為 Model-2 架構(圖 2),其實也就是 MVC(Model-View-Controller)架構的基礎。

    圖 1. page-centric 架構

    圖 2. servlet-centric 架構

    這次的學習重點:

    • 了解如何設計 JavaBeans。
    • 了解如何在 JSP 中使用 JavaBeans(存取 JavaBeans 的屬性和方法)。
    • 了解 servlet 如何分派 JSP 網頁。

    2.0 範例:JSP 呼叫 JavaBeans

    2.1 基礎知識

    這裡所說的 JavaBeans 只是一般的 Java 類別,跟 EJB(Enterprise JavaBeans)是兩種不同的東西,請勿混淆了。那麼,servlet 也是 Java 類別,它跟 JavaBeans 又有什麼不同呢? 

    JavaBeans 只是普通的類別

    Servlet 的 Java 類別是繼承自 javax.servlet.HttpServlet,因此具有接收 HTTP request 和送出 HTTP response 等網站應用程式的基本功能,而 JavaBeans 則只是單純的類別,它可以繼承自任何類別,但無法處理 HTTP 訊息,它在網站應用程式中的角色通常是作為參數物件(在 JSP 和 servlet 之間傳遞,以共享資訊)或工具類別,作為參數物件時,通常代表種資料,因此被稱為 value bean,作為工具類別時,則稱為 utility bean。

    怎樣的類別可以稱為 JavaBeans?

    只要你遵守 JavaBeans 規範中所建議的命名和設計慣例,而且你以 bean 的方式使用它,那麼它就可以稱為一個 bean。[1]

    類別通常以 "動詞+Bean" 的方式命名,例如:UserInfoBean, CheckStockBean....等。這是一種慣例,雖然沒有強制非這樣命名不可,但是它有好處:清楚,別的程式設計師一眼就可以看出這是個 bean。

    JSP 如何使用 JavaBeans?

    要讓 JSP 能夠使用你的 bean,你的 bean 必須提供一組屬性,JSP 便可以透過特殊的標籤來存取這個 bean 的屬性。所謂的屬性,其實是一組 getter 和 setter methods,兩者統稱為 access methods(存取方法),透過這組存取方法來間接地存取類別的私有成員,當然,這組存取方法必須宣告為 public。例如,有個 bean 類別 EmployeeBean,它要提供一個年齡的屬性給外界(JSP)存取,此類別的定義如下:

    public class EmployeeBean {
    private int age;
    public int getAge() {
    return age;
    }
    public void setAge(int age) {
    self.age = age;
    }
    }

    在 JSP 裡面使用時,是這麼個寫法:

    <jsp:useBean id="emp" class="com.huanlin.EmployeeBean" scope="request"/>
    <jsp:setProperty name="emp" property="age" value="25" />
    員工的年齡是: <jsp:getProperty name="emp" property="age" />

    其中

    • <jsp:useBean> 標籤就是指明要使用一個 bean 物件,id 代表該物件的名稱,class 指明了要使用什麼類別的 bean,scope 則代表物件的生命週期。
    • <jsp:setProperty> 標籤用來設定 bean 的屬性值,property 指明了要設定哪個屬性,value 就是數值,要別注意的是,HTML 表單傳入的資料一定是字串,但我們的 age 屬性卻是整數,這個部分的轉換會由 Web container 幫我們處理掉。
    • <jsp:getProperty> 標籤是用來取得 bean 的屬性值。

    請特別注意兩點:

    1. 屬性的大小寫。在 JSP 裡面,屬性的名稱是完全小寫的 "age",但是 getter 和 setter methods 的名稱卻是 getAge() 和 setAge(),這種名稱的轉換對應規則是固定的,照這個規則來命名,Web container 就能夠找到正確的存取方法。
    2. 對於 OOP 觀念不熟的人來說,可能會誤以為在 JSP 裡面存取的 "age" 屬性,就是類別定義裡面的那個宣告為 private 成員(age),其實兩者只有字面上相同而已,實際運作是可以毫無關聯的,因為 JSP 完全是透過 getter 和 setter 方法來存取屬性,況且外界本來就無法存取類別的私有成員。

    基本知識介紹到此,接下來是實作,如果有未詳盡之處,請自行參閱相關書籍。

    2.2 撰寫 JavaBean

    我們把上次的教學文件的最後一個範例,也就是判斷質數的 JSP 程式拿來修改,其中的 isPrimeNumber 函式很明顯可以獨立出來(以便重複使用),放到一個類別裡面,我把這個類別命名為 CheckPrimeBean。程式碼如表 1 所示。

    表 1. PrimeValidator.java

    // 檔名:CheckPrimeBean.java
                // 編譯:javac -d ..\classes CheckPrimeBean.java
                //===============================================
                package com.huanlin.util;
                public class CheckPrimeBean {
                private int number;
                public String getNumber() {
                return Integer.toString(number);    // 整數轉成字串
                }
                public void setNumber(String s) {
                try {
                number = Integer.parseInt(s);   // 字串轉成整數
                }
                catch (NumberFormatException e) {
                number = -1;
                }
                }
                public boolean isValidNumber() {    // 檢查輸入的數字是否合法
                if ((number < 2) || (number > 10000))
                return false;
                return true;
                }
                public boolean isPrimeNumber() {    // 判斷是否為質數
                for (int i = 2; i <= number/2; i++) {
                if (number % 2 == 0)
                return false;
                }
                return true;
                }
                }

    幾點說明:

    1. 這裡使用了具名的套件(named package),套件名稱是 com.huanlin.util,表示你將來佈署的 .class 檔案也要有相同的路徑結構,也就是編譯出來的檔案及路徑名稱會是 "com\huanlin\util\CheckPrimeBean.class"。在編譯這個檔案時,編譯器會根據你的 package 名稱幫你自動建立好對應的目錄。(
    2. 在安排檔案目錄的結構時,我把原始碼和編譯過的類別檔分開目錄存放,檔案目錄結構像是這個樣子:

      sources\CheckPrimeBean.java
                      classes\com\huanlin\util\CheckPrimeBean.class
                      

      也就是這個範例的目錄下會有兩個目錄:sources 和 classes,分別存放原始碼和編譯過的檔案。因為這個緣故,在編譯時必須特別指定輸出的檔案目錄,這部分請參考表 1 的第 2 行註解。  

    3. 這個類別的使用方式,是先設定 number 這個屬性,然後呼叫 isValidNumber() 檢查輸入的數字是否為有效的整數,最後才由 isPrimeNumber() 判斷是否為質數。
    關於 package

    你也許會發現,即使不寫 package 那行,程式也可以通過編譯,但由於這個 bean 是要用在 JSP 裡面的,如果你不為  package 命名的話,在 JSP 裡面使用這個 bean 時,Web container 會找不到這個 bean。請到相關書籍中找尋 package 的相關說明。

    2.3 在 JSP 中使用 JavaBeans

    原本在 JSP 裡面的一些 Java 程式碼被抽離成獨立的 CheckPrimeBean 類別之後,程式碼就清爽些了,修改後的 JSP 檔名取做  CalcPrime2.jsp,參考表 2。

    表 2. CalcPrime2.jsp

    <%-- 檢查某個數字是否為質數的 JSP 程式 --%>
                <%@ page language="java" contentType="text/html;charset=big5" %>
                <%
                request.setCharacterEncoding("big5");
                String num = request.getParameter("number");  // 取得 HTTP request 的參數
                %>
                <html>
                <body>
                <jsp:useBean id="checker" class="com.huanlin.util.CheckPrimeBean" scope="request"/>
                <jsp:setProperty name="checker" property="number" value="<%= num %>" />
                <% if (!checker.isValidNumber()) { %>
                <% response.setHeader("Refresh", "5; URL=prime2.htm"); %>
                請輸入 2~10000 之間的整數。<p>
                五秒後將自動回到 prime2.htm。
                <% return; } %>   <%-- 顯示錯誤訊息後結束,亦即後續的指令不會被處理 --%>
                <% if (checker.isPrimeNumber()) { %>
                <%= num %> 是質數
                <% } else { %>
                <%= num %> 不是質數
                <% } %>
                </body>
                </html>

    關於在 JSP 使用 bean 的方法,之前都有提過了,只有一點值得特別說明,就是 <jsp:setProperty> 這行的 value 屬性(attribute),請注意它使用了 <%= .. %> 標籤來將一個變數的值傳入 value 屬性。其實它還可以這樣寫:

    <jsp:setProperty name="checker" property="number" param="number" />

    也就是不明白指定 value,而改用 param 這個屬性,讓 Web container 在處理 JSP 指令時自動幫我們帶入 "number" 這個 HTML 表單傳入的參數。由於我們的 HTML 表單的參數名稱和 bean 的屬性名稱都叫做 "number",JSP 也允許我們將  param 省略不寫,像這樣:

    <jsp:setProperty name="checker" property="number" />

    這樣就更簡潔了。如果你覺得這樣寫語意不明,或者考慮到某些程式設計師不知道有這種寫法,那就還是把 param 寫上去好了。

    2.4 佈署與執行

    1. 把 sources 目錄下的 prime2.htm 和 CalcPrime.jsp 複製到 Tomcat 的 webapps\myapp\ 目錄下。
    2. 把 classes 目錄整個複製到 Tomcat 的 webapps\myapp\WEB-INF\ 目錄下。
    3. 在瀏覽器的網址列輸入 "http://127.0.0.1:8080/myapp/prime2.htm"。

    3.0 範例:Servlet 呼叫 JSP

    Servlet 要呼叫(說得精確一點應該是:分派 JSP 頁面)JSP,跟在 JSP 中使用 JavaBeans 比起來要簡單多了,主要只是網頁轉送的技巧而已,此技巧在設計 圖 2 的架構時會用得著。

    3.1 撰寫 servlet

    Servlet 程式碼列於表 3。

    表 3. HelloWorldServlet.jsp

    import java.io.*;
                import javax.servlet.*;
                import javax.servlet.http.*;
                public class HelloWorldServlet extends HttpServlet {
                public void service(HttpServletRequest request,
                HttpServletResponse response)
                throws ServletException, IOException {
                response.setContentType("text/html; charset=big5");
                request.setCharacterEncoding("big5");
                String theMessage = "Hello, World!";
                String targetURL = "/HelloFromServlet.jsp";
                request.setAttribute("message", theMessage);
                RequestDispatcher rd;
                rd = getServletContext().getRequestDispatcher(targetURL);
                rd.forward(request, response);
                }
                }

    程式碼有幾個地方值得特別注意:

    1. 之前的 servlet 範例程式都是用 doGet 來處理用戶端的 HTTP request,這裡則改用 service。
    2. request.setAttribute()。
    3. RequestDisatcher 類別。
    4. getServletContext()。
    5. ServletContext 類別的 getRequestDispatcher() 方法。
    6. RequestDispatcher 類別的 forward() 方法。

    請試著從你手邊的書籍或網路資源中尋找相關的說明,以了解程式運作的原理。

    3.2 撰寫 JSP

    表 3. HelloFromServlet.jsp

    <%@ page language="java" contentType="text/html;charset=big5" %>
                <% String msg = (String)request.getAttribute("message"); %>
                <html>
                <body>
                從 servlet 傳來的訊息: <%= msg %>
                </body>
                </html>

    之前的 servlet 程式中有用到 request.setAttribute(),這裡則使用了 request.getAttribute(),從這裡可以看得出來,servlet 和 JSP 之間是透過 request 物件來儲存及傳遞給對方的參數。

    程式的運作過程如下:

    1. 用戶端送出 HTTP request,請求的網址為 "http://127.0.0.1:8080/myapp/HelloWorldServlet"。
    2. HelloWorldServlet 收到請求之後,透過 request.setAttribute() 把要傳遞給 JSP 的參數字串 "Hello, World!" 儲存在 request 物件裡。
    3. HelloWorldServlet 透過 ServletContext 建立 RequestDispatcher 物件,並指定欲分派的網址。
    4. HelloWorldServlet 呼叫  RequestDispatcher 的 forward() 方法,把這次的 HTTP request 轉送至另一個網頁,也就是 HelloFromServlet.jsp。
    5. JSP 先透過 request.getAttribute() 取得 request 物件中的屬性值,該屬性值是在步驟 2 中,由 servlet 指定的。最後再該屬性值搭配 HTML 標籤顯示出來。

    我把整個過程畫成一個 UML 循序圖(圖 3),你可以搭配上面的文字描述來了解程式的運作過程。

    圖 3. Servlet 分派 JSP 網頁的過程(sequence diagram)

    3.3 佈署與執行

    1. 在 Tomcat 的 webapps 目錄下建立目錄結構:myapp\WEB-INF\classes\。請注意大小寫是有區別的。
    2. 將 HelloFromServlet.jsp 複製到 myapp 目錄下。
    3. 編譯 HelloWorldServlet.java,並將編譯後產生的 HelloWorldServlet.class 複製到 myapp\WEB-INF\classes\ 目錄下。
    4. 編輯 myapp\WEB-INF\web.xml 檔案,內容如下:

      表 4. web.xml
      <?xml version="1.0" encoding="ISO-8859-1"?>
                      <!DOCTYPE web-app
                      PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
                      "http://java.sun.com/dtd/web-app_2_3.dtd">
                      <web-app>
                      <servlet>
                      <servlet-name>
                      HelloWorldServlet
                      </servlet-name>
                      <servlet-class>
                      HelloWorldServlet
                      </servlet-class>
                      </servlet>
                      <servlet-mapping>
                      <servlet-name>HelloWorldServlet</servlet-name>
                      <url-pattern>/HelloWorldServlet</url-pattern>
                      </servlet-mapping>
                      </web-app>
    5. 開啟瀏覽器,在網址列輸入 URL "http://127.0.0.1:8080/myapp/HelloWorldServlet"。

    4.0 範例:Servlet、JavaBeans、與 JSP 的組合

    此範例跟 3.0 的範例其實很像,都是由 Servlet 傳遞參數給 JSP,再由 JSP 取出參數並顯示出來,只是 3.0 的範例的參數是字串,而這裡要示範的是以 JavaBeans 物件當作參數來傳遞共享資訊。

    程式的目錄結構如圖 4 所示:

    圖 4. 目錄結構

    編譯後的 Java class 檔案都輸出至 classes 目錄下,而由於 HelloServlet2.java 需要參考 UserInfoBean.java,所以在編譯時要必須使用 -class 參數,否則會找不到類別,為了方便起見,我們用一個批次檔 Make.bat 幫我們編譯所有的 Java 類別。

    4.1 撰寫 JavaBean

    我們打算用一個 UserInfoBean 類別來儲存一個使用者的相關資訊,並且在 servlet 和 JSP 之間傳遞這個物件,以達到溝通和資訊共享的目的。為了示範方便,這個類別只提供了一個屬性:userName,程式碼列在表 5。

    表 5. UserInfoBean.java

    // 檔名:UserInfoBean.java
                // 編譯:javac -d ..\classes UserInfoBean.java
                package com.huanlin;
                public class UserInfoBean {
                private String userName;
                public void setUserName(String userName) {
                this.userName = userName;
                }
                public String getUserName() {
                return this.userName;
                }
                }

    4.2 撰寫 Servlet

    表 6. HelloServlet2.jsp

    // 檔案:HelloServlet2.java
                // 編譯:參考 Make.bat
                import java.io.*;
                import javax.servlet.*;
                import javax.servlet.http.*;
                import com.huanlin.UserInfoBean;
                public class HelloServlet2 extends HttpServlet {
                public void service(HttpServletRequest request,
                HttpServletResponse response)
                throws ServletException, IOException {
                // 下面兩行讓中文字能正確顯示
                response.setContentType("text/html; charset=big5");
                request.setCharacterEncoding("big5");
                // 建立 userInfo 物件,並指定一個 session 的 attribute 與之繫結
                UserInfoBean userInfo = new UserInfoBean();
                userInfo.setUserName("令狐沖");
                HttpSession session = request.getSession();
                session.setAttribute("userInfo", userInfo);
                // 前往指定的網頁
                RequestDispatcher rd;
                rd = getServletContext().getRequestDispatcher("/HelloFromServlet2.jsp");
                rd.forward(request, response);
                }
                }

    session.setAttribute() 會將 UserInfoBean 物件的參考存入 session 裡面。

    4.3 撰寫 JSP

    <%@ page contentType="text/html;charset=big5" %>
                <jsp:useBean id="userInfo" class="com.huanlin.UserInfoBean" scope="session"/>
                <html>
                <body>
                <p>從 servlet 傳入的 UserInfoBean.userName 是:
                <b>
                <jsp:getProperty name="userInfo" property="userName"/>
                </b>
                </body>
                </html>

    有個地方要特別注意,如果在 servlet 儲存參數時是呼叫 session.setAttribute() 方法,也就是將參數存入 session 中,那麼在 JSP 裡面的 <jsp:useBean> 標籤的 scope 就必須指明為 "session",否則會發生取不到參數的情形。

    由於使用者登入之後,其帳號等相關資訊必須一直存在,直到這名使用者登出或將瀏覽器關閉之後才清除,因此我們把 UserInfoBean 物件存放在 session 中。一般來說,為了節省記憶體資源,非必要時不要將變數存在 session 中,如果 bean 傳送到 JSP 中用完即丟,可以將它存放在 request 裡面。

    4.4 佈署與執行

    1. 執行 Make.bat 產生所有的 .class 檔。
    2. 將 Make.bat 產生的 .class 檔案,也就是整個 classes 目錄複製到 myapp\WEN-INF\classes\ 目錄下。
    3. 將 HelloFromServlet2.jsp 複製到 myapp 目錄下。
    4. 編輯 myapp\WEB-INF\web.xml 檔案,加入下列內容如下:

      表 4. web.xml
      <web-app>
                      <servlet>
                      <servlet-name>
                      HelloServlet2
                      </servlet-name>
                      <servlet-class>
                      HelloServlet2
                      </servlet-class>
                      </servlet>
                      <servlet-mapping>
                      <servlet-name>HelloServlet2</servlet-name>
                      <url-pattern>/HelloServlet2</url-pattern>
                      </servlet-mapping>
                      </web-app>
    5. 開啟瀏覽器,在網址列輸入 URL "http://127.0.0.1:8080/myapp/HelloServlet2"。

    5.0 學習評量

    1. 解釋何謂 page-centric 和 servlet-centric 架構,並比較兩者的優缺點。
    2. 什麼是 JavaBeans?
    3. JSP 提供哪些標籤可以讓我們存取 JavaBeans 的屬性?
    4. JavaBeans 如何提供屬性讓 JSP 存取?
    5. 為什麼要使用具名的 package?有什麼好處?
    6. 表 3 的 HelloWorldServlet 類別改寫(override)了 service() 方法,這和之前改寫 doGet() 方法有什麼差別?
    7. 說明 HttpServletRequest.setAttribute() 的用途。
    8. 說明 RequestDispatcher.forward() 的用途。
    9. <jsp:useBean> 標籤的 scope 作用是什麼?其值除了可設定為 "session" 之外,還有哪些值可以設定?各有何不同?

    參考文獻

    [1] Web Developement with JavaServer Pages. Duane K. Fields, Mark A Kolb, Shawn Bayern. Manning, 2002.
    [2] UML 精華第二版,Martin Fowler 著,趙光正、薛琇文 譯,基峰,2000。
  • 相关阅读:
    Mysql:Changes in MySQL 5.6.6 (2012-08-07, Milestone 9):Group-Commit等等:重大变化版本!
    Mysql:Changes in MySQL 5.6.9 (2012-12-11, Release Candidate):GTID-based variables have been 【renamed】
    Mysql:Changes in MySQL 5.6.13 (2013-07-31, General Availability):不再支持可能引起混乱的【选项缩略】写法!
    Mysql:Changes in MySQL 5.6.22 (2014-12-01, General Availability):【sql-log-bin】
    Mysql:Changes in MySQL 5.6.30 (2016-04-11, General Availability):--ssl-mode:
    Mysql:Changes in MySQL 5.6.34 (2016-10-12, General Availability):secure-file-priv
    Windows Linux子系统Windows 10安装指南
    WSL2-参考的对象类型不支持尝试的操作。
    Win10开启Hyper-V后无法运行VMware虚拟机的解决方法
    Kubernetes---高可用的 K8S 集群构建
  • 原文地址:https://www.cnblogs.com/cy163/p/1209579.html
Copyright © 2020-2023  润新知