2012年10月13日 星期六

Java Servlet 入門


Java Servlet 入門

Many examples in this document are adapted from Java: How To Program (3rd Ed.), written by Deitel and Deitel, and Thinking in Java (2nd Edition), written by Bruce Eckel. All examples are solely used for educational purposes. Hopefully, I am not violating any copyright issue here. If so, please do email me.
Please install JDK 1.3.1_02 or later with Java Plugin to view this page. Also, this page is best viewed with browsers (for examples, Mozilla 0.99 or later, IE 6.x or later) with CSS2 support. This document is provided as is. You are welcomed to use it for non-commercial purpose.
在以下的範例中,我們假設你已經在你自己的電腦上面安裝好了 Apache Tomcat 而且 port 為 8080。
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

目錄

  1. Hello World -- Servlet
  2. Servlet vs. CGI
  3. Servlet 的生命週期
  4. Greeting -- Servlet
  5. Servlet and DB
  6. 從 web.xml 讀取初始值
  7. 從 servlet 讀取一個檔案
  8. Cookie
  9. Session


Hello World -- Servlet

簡單的說, Java servlets 就像我們所寫的 CGI 程式一樣,它們是在 伺服器端執行的程式(它們也是 Java 的 applications 而不是 applets),也就是因此當後端 servlet 要連結資料庫(或者存取 檔案)時,它們並沒有像 applet 一樣的受到限制,也由於它們是在後端處理 而所處理後的結果是 HTML,servlets 的使用也不受 browser 的限制。 首先我們先看一個小程式 -- Hello World -- servlet 版。為了能夠 compile 以下程式, 我們假設你們已經安裝好了 Apache Tomcat 5.0.28 版,並已經設定了 CLASSPATH 的環境變數 set CLASSPATH=%CLASSPATH%;d:\tomcat\lib\common\servlet-api.jar.
//
// Hello World Servlet Version
//
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class HelloServ extends HttpServlet
{
  // HttpServletRequest 是用來存放由使用者傳過來的資料
  // HttpServletResponse 是用來存放要回應給客戶端的資料
  // doGet 是給 GET 方法用的,而 doPost 是給 POST 方法用的。
  // 現在大多使用 service() 來取代 doGet and doPost
  public void doGet(HttpServletRequest req, HttpServletResponse res)
         throws ServletException, IOException
  {
    PrintWriter output;

    // 若要輸出中文字,記得要加 charset=Big5
    res.setContentType("text/html;charset=Big5");
    output = res.getWriter();

    StringBuffer buf = new StringBuffer();
    buf.append("<html><head><title>\n");
    buf.append("Hello World\n");
    buf.append("</title></head><body>\n");
    buf.append("<h1>大家好</h1>\n");
    buf.append("</body></html>\n");
    output.println(buf.toString());
    output.close();
  }
}
如果你已經把 Apache Tomcat 安裝好,而且你能正確無誤的把這個程式 在 tomcat\webapps\ROOT\WEB-INF\classes 的目錄 compile 好, 你可以

Servlet vs. CGI

有如之前所說的,Java Servelet 是因為 CGI 非常沒效率而設計出來的, 為了提昇效率,不論同時有多少服務請求(requests),只有一份該服務 的 Java Servelet 會載入 JVM 中。每一次當有服務請求時,則會產生一個 執行緒(thread)而不是一個新的處理程序(process),因此記憶體使用 減少,速度變快。另外,Java Servelet 具有永續性(persistent),即使 服務請求已經完成,Java Servelet 依舊存在。讓我們利用 HelloServ.java 來試著表現出永續性。請宣告一個 HelloServ 的屬性 int count = 0;請注意,不是幫 main 宣告一個 local variable),並在 <h1></body> 之間加入
buf.append("accessed " + count++ + " times.");
並試著執行
  • 用 browser 多執行幾次該 servlet 並觀察 count 的變化。
  • 將 Tomcat 關掉並重開,並觀察 count 的變化。

Servlet 的生命週期

The life cycle of a servlet is controlled by the container in which the servlet has been deployed. When a request is mapped to a servlet, the container performs the following steps.
  1. If an instance of the servlet does not exist, the Web container
    1. Loads the servlet class.
    2. Creates an instance of the servlet class.
    3. Initializes the servlet instance by calling the init method. 你可以覆載 init() 方法,而 init() 內的 statements 在生命週期中只會被執行一次。 注意, init() 不是每一次收到服務請求時都會執行。 要如何測試這個 敘述為真?試著在 init() 宣告 int count2 = 5;,並在這之後,加上 count += count2;。想想看,如果 init() 在生命週期中只執行一次, 和每一次被呼叫時便執行一次的差別在哪裡?最後,執行程式然後驗證結果。
  2. Invokes the service method, passing a request and response object.
If the container needs to remove the servlet, it finalizes the servlet by calling the servlet's destroy method.

Greeting -- Servlet

以下只是使用 form 元件的部分範例,其他的元件請自行學習!
  • 網頁: 使用 post 方法
  • 網頁:
    <form method="post" action="http://localhost:8080/servlet/Greeting">
    <input type="text" value="老呂" name="data">
    <input type="submit" value="確定">
    </form>
    
  • 程式:
    //
    // getParameter example
    //
    import java.io.*;
    import java.text.*;
    import java.util.*;
    import javax.servlet.*;
    import javax.servlet.http.*;
    
    public class Greeting extends HttpServlet {
      // 除了使用 service 之外,你也可以利用 doGet 和 doPost
      // 互相呼叫的方式來達到一個 servet 能同時處理 GET 和 POST
      // 的方法
      public void doGet(HttpServletRequest req, HttpServletResponse res)
             throws IOException, ServletException
      {
        doPost(req, res);
      }
    
      public void doPost(HttpServletRequest req, HttpServletResponse res)
             throws IOException, ServletException
      {
        // 若要輸出中文字,下列兩種方法任選一種,而且必須先設定之後才
        // 能實體化 PrintWriter
        // (1)
        //res.setLocale(new Locale(new String("zh"), new String("TW")));
        //res.setContentType("text/html");
        // (2)
        res.setContentType("text/html;charset=Big5");
        PrintWriter out = res.getWriter();
    
    
        // 將中文的輸入轉成適當的大五碼,這個是因為 Tomcat 會將非英文的
        // 的資料 encode 成 ISO-8859-1,所以我們這個 servlet 是為 Tomcat
        // 而寫的就必須把它從 ISO-8859-1 轉回 Big5
        // 如果 data 不存在,則回傳 null。
        String person = new String(
                        req.getParameter("data").getBytes("ISO-8859-1"), "Big5");
    
        out.println("<html><head><title>\n");
        out.println("Hello World\n");
        out.println("</title></head><body><h1>\n");
        out.println("大家好");
    
        // 注意要如何輸出雙引號
        out.println("</h1><h3><font color=\"blue\">");
        out.println( person );
        out.println("向你問好");
        out.println("</font></h3>\n");
    
        out.println("</body></html>\n");
      }
    }
    
  • 練習題:請將之前的 Worker 範例轉換成 servlet 版。使用者可以從 網頁輸入工作時數後,然後由 servlet 計算出薪資。注意,為這個範例所產生 網頁需要放置於 webapps/ROOT 目錄之下,而不是與其他類別放在一起。 解答

Servlet and DB

  • 網頁:假設 DBServ.java 已經安裝於 tomcat/webapps/ROOT/WEB-INF/classes 這個目錄內,而且 你已經設定好了 JDBC Driver。
    Database:
    Table:
    User ID:
    Password:
  • 網頁
    <form method="post" action="http://localhost:8080/servlet/DBServ">
    Database: <input type="text" value="jlu" name="dsn" size="20"><br>
    Table: <input type="text" value="Product" name="table" size="20"><br>
    User ID: <input type="text" name="uid" size="20"><br>
    Password: <input type="password" name="pwd" size="20"><br>
    <input type="submit">
    </form>
    
  • 程式
    //
    // DB example
    //
    import java.io.*;
    import java.text.*;
    import java.util.*;
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.sql.*;
    
    public class DBServ extends HttpServlet {
      public void service (HttpServletRequest req, HttpServletResponse res)
             throws IOException, ServletException
      {
        // get DSN, UID, and PWD from Form
        // You should put some error checking here or prevent receiving
        // inappropriate information from web page by using Javascript.
        String dsn = req.getParameter("dsn");
        String table = req.getParameter("table");
        String uid = req.getParameter("uid");
        String pwd = req.getParameter("pwd");
    
        res.setContentType("text/html;charset=Big5");    
        PrintWriter out = res.getWriter();
    
        // initialize query string
        String aQuery = null;
        Connection conn = null;
    
        try {
          // load the JDBC bridge driver
          Class.forName("com.mysql.jdbc.Driver");
    
          // connect to Database
          aQuery = "select * from " + table;
          conn = DriverManager.getConnection("jdbc:mysql://localhost/"+ dsn, 
                                             uid, pwd);
    
          // display heading; you should define this as a Static class
          out.println("<html><head><title>");
          out.println("Database " + dsn + " Query");
          out.println("</title></head><body>");
    
          // Construct a SQL statement and submit it
          Statement aStatement = conn.createStatement();
          ResultSet rs = aStatement.executeQuery(aQuery);
    
          // Get info about the query results
          ResultSetMetaData rsmeta = rs.getMetaData();
          int cols = rsmeta.getColumnCount();
    
          // contruct table  
          out.println("<table align=\"center\" border=\"1\">");
          out.println("<tr>");
    
          // Display column headers
          for(int i=1; i<=cols; i++) {
            out.print("<th>");
            out.print(rsmeta.getColumnLabel(i));
            out.print("</th>");
          }
          out.println("\n</tr>");
    
          // Display query results.
          while(rs.next()) {
            out.print("<tr>");
            for(int i=1; i<=cols; i++) {
              out.print("<td>");
              out.print(new String(rs.getString(i).getBytes("ISO-8859-1"), 
                        "BIG5"));
              out.print("</td>"); 
            }
            out.println("</tr>");
          }
    
          // Clean up
          rs.close();
          aStatement.close();
          conn.close();
        } catch (Exception e) {
          System.out.println("Exception Occurs: " + e);
        }
      }
    } 
    
  • 請跟之前 Worker 的範例,請將 control 以及 view 分開!
  • 想想看,在這個 servlet 中,每一次接收服務請求時,他都會產生 一個 connection,這樣子一來,由於 (1) 產生 connection 的成本很高, 且 (2) 每一個資料庫伺服器能接受的 connection 數又有上限的限制, 這會造成這個 servlet 非常沒有效率。根據之前所說明的 servlet 生命週期,能不能將這個程式重新修改?


從 web.xml 讀取初始值

在 WEB-INF 的目錄內可以自訂一個 web.xml 檔,在這個檔案內, 你可以設定一些參數來控制 servlet 和 JSP 的執行。在以下的 範例中,我們利用 servlet 來讀取 web.xml 的初始值。
  • web.xml: 請將這個檔案放到 tomcat/webapps/ROOT/WEB-INF 的目錄內。
    <?xml version="1.0" encoding="Big5"?>
    <!DOCTYPE web-app
        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
        "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
    
    <web-app>
        <servlet>
            <servlet-name>HelloParam</servlet-name>
            <servlet-class>HelloParam</servlet-class>
            <init-param>
                <param-name>dsn</param-name>
                <param-value>dbms</param-value>
            </init-param>
            <init-param>
                <param-name>param2</param-name>
                <param-value>你好嗎</param-value>
            </init-param>
        </servlet>
    </web-app>
    
  • HelloParam.java
    //
    // Read Initial Parameters from web.xml
    //
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    
    public class HelloParam extends HttpServlet
    {
      private String p1, p2;
    
      public void init( ServletConfig con ) throws ServletException
      {
        p1 = con.getInitParameter("param2");
        p2 = con.getInitParameter("dsn");
      }
    
      public void service(HttpServletRequest req, HttpServletResponse res)
             throws ServletException, IOException
      {
        PrintWriter output;
    
        res.setContentType("text/html;charset=Big5");
        output = res.getWriter();
    
        StringBuffer buf = new StringBuffer();
        buf.append("<html><head><title>\n");
        buf.append("Hello Parameter\n");
        buf.append("</title></head><body>\n");
        buf.append("dsn = " + p2 + "<br/>\n");
        buf.append("param2 = " + p1 + "<br/>\n");
        buf.append("</body></html>\n");
        output.println(buf.toString());
        output.close();
      }
    }
    
  • 這個做法的缺點是每一次你更改 web.xml 的內容時,你必須重新 啟動 Tomcat,蠻麻煩的!

從 servlet 讀取一個檔案

假設你的 servlet 伺服器是 tomcat(安裝於 /usr/local/tomcat),又假設你所安裝 的 context 名稱為 myproject 而需要被讀取的檔案名稱為 test.txt,那麼請將 test.txt 置放於 /usr/local/tomcat/webapps/myproject 的目錄下,並在讀檔的那一行 更改成 File file=new File(getServletContext().getRealPath("/")+"test.txt");
其中 getServletContext().getRealPath("/") 會回傳 /usr/local/tomcat/webapps/myproject.

Cookie

The HTTP protocol does not support persistent information that could help a web server determine that a request is from a particular client. 例如,某些網頁需要知道使用者是否 已經登入,若已經登入便可以看網頁的內容,否則要求使用者登入。 一般來說,有兩種方式可以讓 web server 經由 persistent information 來判斷,而這兩種方法為 Cookie 和 Session。Cookie 是儲存於客戶端(browser 內)的資料。由於這是一種由 伺服器端的程式傳送資料到客戶端的一種簡易的方式,因此被大量 的使用於 web 為主的系統中,例如客戶輸入的資料,客戶的偏好 等等。這些資料先由伺服器端的程式寫到客戶端,然後之後的聯繫便可以 從這些資料來提供不同的服務。例如我們可以將使用者的帳號以及 密碼存放在 cookie 內,下一次使用者要登入的時候,就可以從 cookie 來判斷,如果已經存在就不需要再輸入一次。
不過,cookie 也有一些限制,例如一個 cookie 不可以超過 4KB, 而瀏覽器也會對 cookie 的總數作限制,cookie 有可能會有不見的 時候,所以程式寫作的時候要謹慎。另外,由於 cookie 大家都能 讀取,因此也有安全上的考量。
  • 讀取 cookie:建議先執行這個程式來了解究竟 cookie 寫進去了沒, 等到下一個程式(SetCookies)執行完後,再執行一次並比較兩次的不同。
    //
    // Use Cookies
    //
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    
    public class UseCookies extends HttpServlet
    {
      public void service(HttpServletRequest req, HttpServletResponse res)
             throws ServletException, IOException
      {
        PrintWriter output;
    
        res.setContentType("text/html;charset=Big5");
        output = res.getWriter();
    
        StringBuffer buf = new StringBuffer();
        buf.append("<html><head><title>\n");
        buf.append("Use Cookies\n");
        buf.append("</title></head><body>\n");
    
        Cookie [] cookie = req.getCookies();
        if (cookie.length == 0)
          buf.append("<h1>沒有 Cookie</h1>\n");
        else
        {
          buf.append("<h1 align=\"center\">大家好</h1>\n");
          buf.append("<ul>\n");
          for(int i=0; i<cookie.length; i++)
            buf.append("<li> " + cookie[i].getName() + "=" + 
                       cookie[i].getValue());
          buf.append("</ul>\n");
        }
    
        buf.append("</body></html>\n");
        output.println(buf.toString());
        output.close();
      }
    }
    
  • 寫入 cookie
    //
    // Set Cookies
    //
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    
    public class SetCookies extends HttpServlet
    {
      public void service(HttpServletRequest req, HttpServletResponse res)
             throws ServletException, IOException
      {
        PrintWriter output;
    
        res.setContentType("text/html;charset=Big5");
        output = res.getWriter();
    
        Cookie myck1 = new Cookie("UID", "123");
        res.addCookie(myck1);
        Cookie myck2 = new Cookie("PWD", "abc");
        res.addCookie(myck2);
    
        StringBuffer buf = new StringBuffer();
        buf.append("<html><head><title>\n");
        buf.append("Write Cookies\n");
        buf.append("</title></head><body>\n");
        buf.append("<h1>寫入 Cookie</h1>\n");
        buf.append("</body></html>\n");
        output.println(buf.toString());
        output.close();
      }
    }
    

Session

  • 第一個網頁:請將該網頁命名為 testsession.html,並置放於 tomcat/webapps/ROOT
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html;charset=Big5">
    <meta name="DC.Creator" content="Lu, Eric Jui-Lin">
    <title>
    Test Servlet Sessions
    </title>
    </head>
    
    <body>
    <form method="POST" action="http://localhost:8080/servlet/Session1">
    <strong>請選擇你想進一步了解的電腦語言:</strong><p/>
    <input type="radio" name="lang" value="C">C</input><br/>
    <input type="radio" name="lang" value="C++">C++</input><br/>
    <input type="radio" name="lang" value="Java" checked>Java</input><br/>
    <input type="radio" name="lang" value="VB">Visual Basic</input><p/>
    <input type="submit" value="確定"/>
    <input type="reset"/>
    </form>
    </body>
    </html>
    
  • 由第一個網頁所啟動的 servlet,這個 servlet 會產生一個 session, 而且產生一個名稱為 language 的 key。至於這個 key 的值 是由之前的網頁輸入而得。
    //
    // Use Session
    //
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    
    public class Session1 extends HttpServlet
    {
      public void service(HttpServletRequest req, HttpServletResponse res)
             throws ServletException, IOException
      {
        PrintWriter output;
    
        String lang = req.getParameter("lang");
    
        // create a session if one does not exist
        // 需要使用 true 當作 getSession() 的參數
        HttpSession session = req.getSession(true);
    
        // add a value for user's choice to session
        session.setAttribute("language", lang);
    
        res.setContentType("text/html;charset=Big5");
        output = res.getWriter();
    
        StringBuffer buf = new StringBuffer();
        buf.append("<html><head><title>\n");
        buf.append("Recommendation\n");
        buf.append("</title></head><body>\n");
    
        buf.append("請按\"Recommended Books\"來顯示建議清單<br/>\n");
        buf.append("<form method=\"POST\" " +
                   "action=\"http://localhost:8080/servlet/Session2\">\n");
        buf.append("<input type=\"submit\" value=\"Recommended Books\">\n");
        buf.append("</form>\n");
    
        buf.append("</body></html>\n");
        output.println(buf.toString());
        output.close();
      }
    }
    
  • 由第一個 servlet 所啟動的 servlet,這個 servlet 會利用之前的 session 的 key 值來判斷下一個步驟要如何進行。你也可以新開啟一個瀏覽器的視窗, 並直接輸入 Session2 的 URL,這次由於之前並沒有 session, Session2 會要求你重新回到第一個網頁 testsession.html你也可以利用 HTML 語法內的 meta 標籤讓網頁自動跳到第一個網頁。
    //
    // Use Session
    //
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    
    public class Session2 extends HttpServlet
    {
      private final static String names[] =
              {"C", "C++", "Java", "VB"};
      private final static String books[] =
              {"C: How to Program", "C++: How to Program",
               "Java: How to Program", "Visual Basic: How to Program"};
    
      public void service(HttpServletRequest req, HttpServletResponse res)
             throws ServletException, IOException
      {
        PrintWriter output;
        res.setContentType("text/html;charset=Big5");
        output = res.getWriter();
        StringBuffer buf = new StringBuffer();
        buf.append("<html><head><title>\n");
        buf.append("Recommended Book List\n");
        buf.append("</title></head><body>\n");
    
        // do NOT create a session if one does not exist
        // 需要使用 false 當作 getSession() 的參數
        HttpSession session = req.getSession(false);
    
        // 如果沒有之前的 session,則要求使用者到第一個網頁
        if(session == null)
        {
           buf.append("<h1>請重新選擇</h1>\n");
           buf.append("<a href=\"http://localhost:8080/testsession.html\">"+
                      "請按這裡</a>\n");
           // Or 直接將結果轉向第一個網頁
           //res.sendRedirect("http://localhost:8080/testsession.html");
        }
        else
        {
          // get values from session
          String lang = (String) session.getAttribute("language");
          for(int i=0; i<names.length; i++)
            if(lang.equals(names[i]))
              buf.append("<h1>" + books[i] + "</h1>\n");
        }
    
        buf.append("</body></html>\n");
        output.println(buf.toString());
        output.close();
      }
    }
    



Last Updated:
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu









沒有留言:

張貼留言