第一個 Servlet


一個 Servlet 基本上必須繼承 javax.servlet.http.HttpServlet,如果你要處理的是 GET 請求,則重新定義 doGet() 方法。例如:

package cc.openhome;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name="HelloWorld", urlPatterns={"/helloworld"},
loadOnStartup=1)
public class HelloWorld extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
                        throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello! World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello! World!</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

當客戶端發出 HTTP GET 請求時,若最後是由上面這個 Servlet 來處理,則會呼叫 doGet() 方法,在 doGet() 方法中,javax.servlet.http.HttpServletRequest 是 HTTP 請求的 Java 代表物件,有關於 HTTP 請求的資訊都是由它來取得,而 javax.servlet.http.HttpServletResponse 則是 HTTP 回應的 Java 代表物件,在上例中,你透過 getWriter() 取得 PrintWriter,如果你要想對客戶端有任何純文字的回應,就使用它來進行輸出。

在這個例子中,只是輸出一連串的 HTML 標籤至客戶端,實際上並不會在 Servlet 中作 HTML 輸出,但這邊只是第一個 Servlet,這麼作只是為了簡化範例。

在 Servlet 中從 response 取得了 PrintWriter,最後要不要主動呼叫 close() 呢?中性的說法是,這取決於應用程式的需求,然而,一般的建議是,不需要主動呼叫 close(),容器負責 HttpServletRequestHttpServletResponse 物件的建立,也會處理最後的物件資源回收與銷毀,因此 close() 可由容器處理(自行 close() 在某些時候,像是過濾器(Filter)的處理,可能會造成一些麻煩)。

容器是怎麼知道要由哪個 Servlet 來處理請求?在這個例子中,是透過標註 @WebServlet 來告知容器,這個 Servlet 的名稱是 HelloWorld,這是由 name 屬性指定,若沒有指定,預設採用類別完整名稱,如果客戶端請求的 URL 是 /helloworld,則由這個 Servlet 來處理,這是由 urlPatterns 屬性來指定。

所以,假設這個 Servlet 的應用程式路徑是 ServletDemo,並使用 Tomcat 的話,若請求 http://localhost:8080/ServletDemo/helloworld,就會由這個 Servlet 處理。

當你的應用程式啟動後,事實上並沒有載入所有的 Servlet。容器會在你請求時,才將對應的 Servlet 類別載入、實例化、進行初始動作,然後再處理 你的請求。這意謂著第一次請求該 Servlet 的客戶端,必須等待 Servlet 類別載入、實例化、進行初始動作所必須花費的時間,才真正得到請求的處理。

如果你希望應用程式啟動時,就先將 Servlet 類別載入、實例化並作好初始化動作,則可以使用 loadOnStartup 設定。設定大於 0 的值(預設值 -1),表示在啟動應用程式後就要初始化 Servlet(而不是實例化幾個 Servlet)。數字代表了 Servlet 的初始順序,容器必須保證有較小數字的 Servlet 先初始化,如果有多個 Servlet 在設定 loadOnStartup 時使用了相同的數字,則容器實作廠商可以自行決定要如何載入哪個 Servlet。

使用標注來定義 Servlet 是在 Java EE 6 之後 Servlet 3.0 的功能,你也可以使用 web.xml 檔案來定義 Servlet,就像在 Java EE 5 中定義 Servlet 一樣。例如:

<?xml version="1.0" encoding="UTF-8"?>  
<web-app version="4.0" 
        xmlns="http://xmlns.jcp.org/xml/ns/javaee"   
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee  
         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">  
    <servlet>
        <servlet-name>HelloWorld</servlet-name>
        <servlet-class>cc.openhome.HelloWorld</servlet-class>
         <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloWorld</servlet-name>
        <url-pattern>/helloworld</url-pattern>
    </servlet-mapping>
</web-app>

使用 XML 來定義是比較麻煩一些,不過若 web.xml 中的 Servlet 設定時,<servlet-name> 定義的名稱相同,就會覆蓋 @WebServlet 標註時 name 設定為相同名稱之 Servlet,你可以使用標註來作預設值,而 web.xml 來作日後更改設定值之用。

另外,有些 Servlet 在定義時並不會使用 @WebServlet,像是使用框架時,會有擔任分派請求的 Servlet,這類 Servlet 基本上會在 web.xml 中定義。

web.xml 也會用來定義一些應用程式資源,像是初始參數、安全設定、JNDI 等。

在上面的 web.xml 中,若有客戶端請求 /helloworld,則是由 HelloWorld 這個Servlet來處理,這分別是由 <servlet-mapping> 中的 <url-pattern><servlet-name> 來定義,而 HelloWorld 名稱的Servlet,實際上是 HelloWorld 類別的實例,這分別是由 <servlet> 中的 <servlet-name><servlet-class> 來定義。如果有多個 Servlet 在設定 <load-on-startup> 時使用了相同的數字,則依其在 web.xml 中設定的順序來初始 Servlet。

可以在一個 Web 應用程式中混用標註與 web.xml 設定。

如果你使用的是 IDE,那基本上就可以執行應用程式並對 Servlet 發出請求了,實際上,IDE 會將你的應用程式包裝為 WAR(Web Archive),然後上傳至應用程式伺服器(Application Server)完成部署(Deployment)。所謂 WAR 檔,實際上是一個副檔名為 .war 的檔案,使用 zip 格式進行包裝壓縮,而當中的結構(就目前的功能而言)必須如下:

/ServletDemo.war/
                /WEB-INF/
                        /classes/
                        |       /cc/
                        |          /openhome/
                        |                   |HelloWorld.class
                        |web.xml

也就是說,一個 Web 應用程式中,編譯出來的 .class 檔案,必須放置在 /WEB-INF/classes/ 資料夾中,並必須按照套件(Package)層次放在對應的資料夾內,如果你使用 web.xml 設定檔,則 web.xm l必須放置在 /WEB-INF/ 中。 WAR 檔案是 zip 壓縮格式,所謂的部署,就是將 WAR 檔上傳至應用程式伺服器,由伺服器解壓縮並讀取設定、載入 Servlet 後進行服務。