Servlet
掌握Servlet API 是成爲一名技術高超的JAVA Web開發者的基礎.必須非常熟悉Servlet API中所定義的70多種類型.
本篇博客將介紹Servlet API,並教你編寫第一個Servlet應用程序.
1.Servlet API 概述
Servlet API中有4個Java包,包括:
- javax.servlet. 包含定義Servlet與Servlet容器之間契約的類和接口.
- javax.servlet.http. 包含定義HTTP Servlet與Servlet容器之間契約的類和接口.
- javax.servlet.annotation. 包含對Servlet Filter和Listener進行標註的註解.它還爲標註元件指定元數據.
- javax.servlet.descriptor. 包含爲Web應用程序的配置信息提供編程式訪問的類型.
Servlet技術的核心是Servlet接口,這是所有Servlet類都必須直接或者間接實現的一個接口.Servlet接口定義了Servlet與Servlet容器之間的一個契約,Servlet容器會把Servlet類加載到內存中,並在Servlet實例中調用特定的方法.每個Servlet類型只能有一個實例.
Servlet容器還爲每個應用程序創建一個ServletContext實例.這個對象封裝context(應用程序)的環境細節.每個context只有一個ServletContext.每個Servlet實例還有一個封裝Servlet配置信息的ServletConfig.
2.Servlet
Servlet接口中定義了以下5個方法:
public void init(ServletConfig servletConfig)throws ServletException
public void service(ServletRequest request,ServletResponse response) throws ServletException,IOException
public void destroy()
public ServletConfig getServletConfig()
public String getServletInfo()
編寫Java方法簽名的規則是:與包含該方法的類型不在同一個包中的類型,要使用全類名.
init() service() destroy()方法是Servlet的生命週期方法,調用規則如下:
- init(): 第一次請求Servlet時,Servlet容器會調用這個方法.在後續的請求中不會在調用.
- service(): 每次請求Servlet時都會調用此方法,必須在這裏編寫要Servlet完成的響應代碼. 第一次調用Servlet時會調用init()和service()方法,之後請求只調用service()方法.
- destroy(): Servlet要銷燬時調用此方法,通常發生在卸載應用程序,或者關閉Servlet容器的時候.
- getServletInfo(): 該方法返回Servlet的描述,可以是任意字符串,甚至爲null.
- getServletConfig(): 該方法返回由Servlet容器傳給init方法的ServletConfig.爲了讓getServletConfig返回非null值,應該爲傳給init方法的ServletConfig賦給一個類級變量.
必須注意線程安全.一個應用程序中的所有用戶將共用一個Servlet實例,因此 不建議使用類級變量,除非它們是隻讀的,或者是java.util.concurrent.atomic包中的成員.
3.編寫基礎的Servlet應用程序
Servlet應用程序編寫起來非常簡單,只需要創建一個目錄結果,並將Servlet類放在某一個目錄下即可.
3.1編寫和編譯Servlet類
在本節中,將學習如何編寫一個簡單的Servlet應用程序,將它命名爲app01a.最初它只包含一個Servlet:MyServlet,其會給用戶發送一條問候信息.
package app01a;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
@WebServlet(name = "MyServlet", urlPatterns = { "/my" })
public class MyServlet implements Servlet {
private transient ServletConfig servletConfig;
@Override
public void init(ServletConfig servletConfig)
throws ServletException {
this.servletConfig = servletConfig;
}
@Override
public ServletConfig getServletConfig() {
return servletConfig;
}
@Override
public String getServletInfo() {
return "My Servlet";
}
@Override
public void service(ServletRequest request,
ServletResponse response) throws ServletException,
IOException {
String servletName = servletConfig.getServletName();
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print("<html><head></head>"
+ "<body>Hello from " + servletName
+ "</body></html>");
}
@Override
public void destroy() {
}
}
查看代碼首先會注意到下面這個註解:
@WebServlet(name = “MyServlet”, urlPatterns = { “/my” })
註解中:name屬性是可選的,一般用來提供Servlet類的名字,關鍵是urlPatterns它也是可選的,在MyServlet中,urlPatterns告訴容器,/my模式應該調用這個Servlet.
- URL模式必須一條正斜線開頭.
3.2應用程序的目錄結構
Servlet應用程序必須以特定目錄結果進行部署,應用程序包含WEB-INF目錄
它有兩個子目錄:
- classes目錄:Servlet類和其他的Java類都必須放在這裏.類下方的目錄反映了類的包結構.
- lib目錄:在這裏部署Servlet應用程序所需要的Jar文件.
所有的圖像文件可以放在一個image目錄下,所有的JSP頁面可以放在一個jsp目錄下,以此類推.
放在應用程序目錄下的任何資源,用戶可以通過輸入該資源的URL而直接進行訪問.如果希望某個資源可以被Servlet訪問,但是不能被用戶訪問,那麼應該把它放在WEB-INF目錄下面.
把應用程序部署到Tomcat中:
- 一種部署方法是將應用程序目錄直接複製到Tomcate的webapps目錄下
- 可以通過在Tomcat的conf目錄下編輯server.xml文件來部署應用程序
- 爲了不用編輯server.xml,而單獨部署一個XML文件到conf\Catalina\localhost目錄下.
- 將應用程序打包成war文件來進行部署.war文件是指以war作爲擴展名的jar文件.
3.3調用Servlet
運行MyServlet應用程序,通過以下地址:
允許結果:
恭喜!你的第一個Servlet應用程序已經運行!
4.ServletRequest
下面是ServletRequest接口中的部分方法:
public int getContentLength()
返回請求主體中的字節數,如果不知道字節長度,則返回-1;
public java.lang.String getContentType()
返回請求主體的MIME類型,如果不知道類型,則返回null;
public java.lang.String getProtocol()
返回這個HTTP請求的協議名稱和版本號;
public java.lang.String getParameter(java.lang.String name)
返回指定請求參數的值,返回一個HTML表單域的值,獲取查詢字符串的值.如果不存在返回null;
利用getParameterNames getParameterMap和getParameterValues來獲取表單域的名稱和值,以及查詢字符串.
5.ServletResponse
ServletResponse中定義了getWriter()方法,它返回可以將文本傳給客戶端的java.io.PrintWriter.默認情況下,PrintWriter對象採用ISO-8859-1編碼.
在發送任何HTML標籤之前,先通過調用setContentType來設置響應的內容類型,比如:將text/html作爲參數傳遞,告訴瀏覽器內容類型爲HTML.
6.ServletConfig
- getInitParameter(java.lang.String name)
從一個Servlet內部獲取某個初始參數的值,由傳入init方法的ServletConfig中調用getInitParameter.
- java.util.Enumeration getInitParameterNames()
返回所有初始參數名稱的一個Enumeration.
- getServletContext()
從Servlet內部獲取ServletContext.
舉個ServletConfig的例子.添加一個ServletConfigDemoServlet:
package app01a;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
@WebServlet(name = "ServletConfigDemoServlet",
urlPatterns = { "/servletConfigDemo" },
initParams = {
@WebInitParam(name="admin", value="Harry Taciak"),
@WebInitParam(name="email", value="[email protected]")
}
)
public class ServletConfigDemoServlet implements Servlet {
private transient ServletConfig servletConfig;
@Override
public ServletConfig getServletConfig() {
return servletConfig;
}
@Override
public void init(ServletConfig servletConfig)
throws ServletException {
this.servletConfig = servletConfig;
}
@Override
public void service(ServletRequest request,
ServletResponse response)
throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
String admin = servletConfig.getInitParameter("admin");
String email = servletConfig.getInitParameter("email");
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print("<html><head></head><body>" +
"Admin:" + admin +
"<br/>Email:" + email +
"</body></html>");
}
@Override
public String getServletInfo() {
return "ServletConfig demo";
}
@Override
public void destroy() {
}
}
7.ServletContext
ServletContext表示Servlet應用程序.每個Web應用程序只有一個context.
用ServletConfig中調用getServletContext方法可以獲得ServletContext.
保存在ServletContext中的對象稱作屬性.
ServletContext中包含如下常用屬性:
- getAttribute()
- getAttributeNames()
- setAttribute()
- removeAttribute()
8.GenericServlet
通過Servlet接口編寫Servlet很麻煩,需要把它的所有方法都實現及時不用的方法,所以,我們有GenericServlet抽象類,它實現了Servlet和ServletConfig,並做了以下工作:
- 將init方法中的ServletConfig賦給一個類級變量,使它可以通過調用getServletConfig來獲取.
- 爲Servlet接口中的所有方法提供默認實現.
- 提供方法來包裝ServletConfig中的方法.
在類中如果覆蓋了init()方法,則必須調用super.init(servletConfig)來保存ServletConfig.
可以通過覆蓋無參的init方法來編寫初始化代碼,ServletConfig仍然由GenericServlet實例保存.
接下來舉個繼承GenericServlet的Servlet栗子:
package app01a;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.GenericServlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
@WebServlet(name = "GenericServletDemoServlet",
urlPatterns = { "/generic" },
initParams = {
@WebInitParam(name="admin", value="Harry Taciak"),
@WebInitParam(name="email", value="[email protected]")
}
)
public class GenericServletDemoServlet extends GenericServlet {
private static final long serialVersionUID = 62500890L;
@Override
public void service(ServletRequest request,
ServletResponse response)
throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
String admin = servletConfig.getInitParameter("admin");
String email = servletConfig.getInitParameter("email");
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print("<html><head></head><body>" +
"Admin:" + admin +
"<br/>Email:" + email +
"</body></html>");
}
}
9.1 HTTPServlet
javax.servlet.http包是Servlet API中的第二個包,其包含了編寫Servlet應用程序的類和接口.
HttpServlet類覆蓋javax.servlet.GenericServlet類.
HttpServlet中有兩項特性是GenericServlet所沒有的:
- 不覆蓋service方法,而是覆蓋doGet doPost,或者兩者都覆蓋掉.在極少數情況下,還要覆蓋以下某個方法:doHead() doPut() doTrace() doOptions()或doDelete().
- 將用HttpServletRequest和HttpServletResponse代替ServletRequest和ServletResponse.
9.2 HttpServletRequest
HttpServletRequest表示HTTP環境中的Servlet請求.它繼承java.servlet.ServletRequest接口,並增加了幾個方法:
- String getContextPath()
返回表示請求context的請求URI部分.
- Cookie[] getCookies()
返回一個Cookie對象數組.
- String getHeader(String name)
返回指定HTTP標頭的值.
- String getMethod()
返回發出這條請求的HTTP方法的名稱.
- String getQueryString()
返回請求URL中的查詢字符串.
- HttpSession getSession()
返回與這個請求有關的session對象.如果沒有找到,則創建新的session對象.
- HttpSession getSession(boolean create)
返回與這個請求有關的session對象.如果沒有找到,並且create參數爲true,那麼將創建新的session對象.
9.3 HttpServletResponse
HttpServletResponse表示HTTP環境下的Servlet響應.
- void addCookie(Cookie cookie) 給這個響應對象添加cookie
- void addHeader(String name,String value) 給這個響應對象添加標頭.
- void sendRedirect(String location) 發送響應代號,將瀏覽器重定向到指定的位置.
10.處理HTML表單
每個Web應用程序中幾乎都會包含一個或者多個HTML表單,用來接收用戶輸入.可以輕鬆地將一個HTML表單從Serlet發送到瀏覽器.
HTML輸入域或文本域中的值將被當作字符串發送到服務器.空白的輸入域或文本域將發送一條空白的字符串,因此,帶有一個輸入域名稱的ServletRequest.getParameter將永遠不會返回null.
- 接下來舉個栗子:FormServlet類示範了處理HTML表單的方法.它的doGet方法發送了一個給瀏覽器.它的doPost方法獲取輸入的值,並輸出它們.
package app01b;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
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 = "FormServlet", urlPatterns = { "/form" })
public class FormServlet extends HttpServlet {
private static final long serialVersionUID = 54L;
private static final String TITLE = "Order Form";
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<head>");
writer.println("<title>" + TITLE + "</title></head>");
writer.println("<body><h1>" + TITLE + "</h1>");
writer.println("<form method='post'>");
writer.println("<table>");
writer.println("<tr>");
writer.println("<td>Name:</td>");
writer.println("<td><input name='name'/></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Address:</td>");
writer.println("<td><textarea name='address' "
+ "cols='40' rows='5'></textarea></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Country:</td>");
writer.println("<td><select name='country'>");
writer.println("<option>United States</option>");
writer.println("<option>Canada</option>");
writer.println("</select></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Delivery Method:</td>");
writer.println("<td><input type='radio' " +
"name='deliveryMethod'"
+ " value='First Class'/>First Class");
writer.println("<input type='radio' " +
"name='deliveryMethod' "
+ "value='Second Class'/>Second Class</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Shipping Instructions:</td>");
writer.println("<td><textarea name='instruction' "
+ "cols='40' rows='5'></textarea></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td> </td>");
writer.println("<td><textarea name='instruction' "
+ "cols='40' rows='5'></textarea></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Please send me the latest " +
"product catalog:</td>");
writer.println("<td><input type='checkbox' " +
"name='catalogRequest'/></td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td> </td>");
writer.println("<td><input type='reset'/>" +
"<input type='submit'/></td>");
writer.println("</tr>");
writer.println("</table>");
writer.println("</form>");
writer.println("</body>");
writer.println("</html>");
}
@Override
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<head>");
writer.println("<title>" + TITLE + "</title></head>");
writer.println("</head>");
writer.println("<body><h1>" + TITLE + "</h1>");
writer.println("<table>");
writer.println("<tr>");
writer.println("<td>Name:</td>");
writer.println("<td>" + request.getParameter("name")
+ "</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Address:</td>");
writer.println("<td>" + request.getParameter("address")
+ "</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Country:</td>");
writer.println("<td>" + request.getParameter("country")
+ "</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Shipping Instructions:</td>");
writer.println("<td>");
String[] instructions = request
.getParameterValues("instruction");
if (instructions != null) {
for (String instruction : instructions) {
writer.println(instruction + "<br/>");
}
}
writer.println("</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Delivery Method:</td>");
writer.println("<td>"
+ request.getParameter("deliveryMethod")
+ "</td>");
writer.println("</tr>");
writer.println("<tr>");
writer.println("<td>Catalog Request:</td>");
writer.println("<td>");
if (request.getParameter("catalogRequest") == null) {
writer.println("No");
} else {
writer.println("Yes");
}
writer.println("</td>");
writer.println("</tr>");
writer.println("</table>");
writer.println("<div style='border:1px solid #ddd;" +
"margin-top:40px;font-size:90%'>");
writer.println("Debug Info<br/>");
Enumeration<String> parameterNames = request
.getParameterNames();
while (parameterNames.hasMoreElements()) {
String paramName = parameterNames.nextElement();
writer.println(paramName + ": ");
String[] paramValues = request
.getParameterValues(paramName);
for (String paramValue : paramValues) {
writer.println(paramValue + "<br/>");
}
}
writer.println("</div>");
writer.println("</body>");
writer.println("</html>");
}
}
- 空白的訂單表單:
- 填寫完表單,並單擊Submit(提交)按鈕.在表單中輸入的值將會通過HTTP POST方法發送到服務器,這樣就會調用Servlet的doPost方法.在訂單表單中輸入的值:
11.使用部署描述符
使用部署描述符是配置Servlet應用程序的另一種方法,部署描述符總是命名爲web.xml,並放在WEB-INF目錄下.
栗子:有兩個沒有@WebServlet進行標註的Servlet:分別爲:SimpleServlet和WelcomeServlet.以及部署描述符文件:web.xml:
- 未標註的SimpleServlet類:
package app01c;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SimpleServlet extends HttpServlet {
private static final long serialVersionUID = 8946L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print("<html><head></head>" +
"<body>Simple Servlet</body></html");
}
}
- 未標註的WelcomeServlet類
package app01c;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class WelcomeServlet extends HttpServlet {
private static final long serialVersionUID = 27126L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print("<html><head></head>"
+ "<body>Welcome</body></html>");
}
}
- 部署描述符web.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>SimpleServlet</servlet-name>
<servlet-class>app01c.SimpleServlet</servlet-class>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SimpleServlet</servlet-name>
<url-pattern>/simple</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>WelcomeServlet</servlet-name>
<servlet-class>app01c.WelcomeServlet</servlet-class>
<load-on-startup>20</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>WelcomeServlet</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>
</web-app>
部署描述符的好處:
- 可以包含@WebServlet中沒有的元素,如load-on-startup表示在應用程序啓動時加載Servlet,而不是第一次調用Servlet時加載.
- 如果需要修改配置值,如Servlet路徑,就不需要重新編譯Servlet類.
- 可以將初始參數傳給一個Servlet,並且不需要重新編譯Servlet類就可以對它們進行編譯.
- 部署描述符還允許覆蓋Servlet註解中指定的值.可以在標註完Servlet之後,又在同一個應用程序的部署描述符中聲明Servlet.
未完待續...