一個 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()
,容器負責 HttpServletRequest
、HttpServletResponse
物件的建立,也會處理最後的物件資源回收與銷毀,因此 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 後進行服務。