Servle詳解
文章目錄
1. Servlet
Servlet的規範是有javax包下的servlet接口制定的,它提供了幾個方法(這幾個方法代表了一個servlet的生命週期)。
package javax.servlet;
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
1.1 自定義Servlet
Java提供了一個javax.servlet
接口,我們自定義的servlet類需要實現這個接口,並覆寫其中的方法。
public class HelloServlet implements Servlet {
//構造方法,第一次訪問該servlet時調用的
public HelloServlet(){
System.out.println("constructor");
}
//初始化方法,在構造方法後調用
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init");
}
//獲得Servlet配置信息的方法
@Override
public ServletConfig getServletConfig() {
System.out.println("getServletConfig");
return null;
}
//servlet處理前臺請求的方法
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service");
}
////獲得Servlet的信息的方法
@Override
public String getServletInfo() {
System.out.println("getServletInfo");
return null;
}
//銷燬Servlet對象的方法,正常關閉服務器的時候執行
@Override
public void destroy() {
System.out.println("destroy");
}
}
- web.xml中配置自定義的servlet
<!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>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.yogie.lesson.HelloServlet</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
<!--通常在eclipse中,web.xml文件中xml約束添加在web-app標籤中,如果要使用註解方式配置servlet,則需要將約束中添加metadata-complete="false"-->
1.2 Servlet生命週期
①先執行構造方法 - 第一次訪問
②執行初始化方法(init) - 第一次訪問
③執行的服務方法(service) - 第n次訪問
④對於tomcat來說,Servlet只有一個(它是單例的,使用的是緩存)
⑤當我們正常關閉tomcat的時候,執行destroy方法
⑥當瀏覽訪問的時候纔開始創建Servlet
1.3 ServletConfig接口
ServletConfig只封裝了4個方法:
String getInitParameter(String name) 根據web.xml中<init-param>標籤下的參數名稱獲取對應的值。
Enumeration getInitParameterNames() 獲取所有的參數名稱。
ServletContext getServletContext() 獲取servlet的上下文對象。
String getServletName() 獲取servlet的名稱(配置的servlet名稱,不是類名)
- Enumeration類:類似於迭代器,用於迭代獲取到的參數名稱。
boolean hasMoreElements() 測試此枚舉類是否還有下一個元素
E nextElement() 返回此枚舉的下一個元素。
1.4 鉤子方法的設計
鉤子方法的設計主要是模板方法設計模式的使用。模板方法設計模式:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。更進一步來說,模板方式模式就是通過不變的行爲搬移到超類,去除子類中的重複代碼來體現它的優勢。
在應用方面上,當功能的內部一部分實現是確定的,但同時也有一些是不確定的,這時就可以將不確定的部分暴露出去,讓子類去實現。例如HttpServlet中service方法中確定的功能就是要將ServletRequest
強制轉換成HttpServletRequest
,而不確定的功能就是對請求的處理。
- HttpServlet類的部分源碼:
/**
* public abstract class GenericServlet implements Servlet, ServletConfig, Serializable
* 抽象類GenericServlet實現了Servlet、ServletConfig、Serializable接口
*/
public abstract class HttpServlet extends GenericServlet {
//對方法的參數進行強轉
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
this.service(request, response);
} else {
throw new ServletException("non-HTTP request or response");
}
}
//提供給子類覆寫的方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取前臺form表單的提交方式
String method = req.getMethod();
long lastModified;
//如果前臺form表單以get方式提交,則調用doGet方法
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
//以下同理
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
doGet()...doPost()...doDelete()...
//doGet方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取http協議和版本,例如:HTTP/1.1
String protocol = req.getProtocol();
//定義異常信息
String msg = lStrings.getString("http.method_get_not_supported");
//如果是http1.1版本
if (protocol.endsWith("1.1")) {
//拋出405異常
resp.sendError(405, msg);
} else {
//拋出400異常
resp.sendError(400, msg);
}
}
}
- 我們定義的子類僅僅需要覆寫前臺提交對應的方法即可。
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doDelete(req, resp);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
}
}
代碼的執行流程分析:
當瀏覽器訪問到當前的servlet類時,首先訪問的是類中帶ServletRequest
和ServletResponse
參數的service方法,但是此時子類沒有,就執行從父類(HttpServlet)
繼承來的service方法,此時HttpServlet
中的service方法將參數強轉爲HttpServletRequest
與HttpServletResponse
,並且調用重載一個帶HttpServletRequest
與HttpServletResponse
參數類型的service方法。在這個重載的方法內部會根據前臺的請求方法(get/post...)
調用對應的(doGet/doPost...)
方法。此時調用的方法即是子類覆寫父類的doXXX()
方法,如果沒有覆寫對應的方法,那麼還是調用從父類繼承來的方法,由於父類的方法是直接返回的錯誤。
2. 處理請求
2.1 HttpServerRequest
接口
該接口繼承自ServletRequest
,ServletRequest
對象提供包括瀏覽器傳遞的參數名稱、參數值、屬性和輸入流的數據。HttpServletRequest
在ServletRequest
類的基礎上提供了包含HTTP協議請求的相關信息。HttpServletRequest
對象封裝了客戶端提交過來的一切數據。
Map getParameterMap() 以map集合的形式返回請求參數、表單提交的數據(結合beanutils使用)。
String getParameter(String name) 根據請求參數名獲取前臺傳遞的參數值。如果參數可能擁有一個以上的值,則使用getParameterValues ()方法。
String[] getParameterValues(String name):根據參數名稱,獲取該參數的多個值。
Enumeration<String> getParameterNames():獲取所有請求參數的名字。
Map<String,String[]> getParameterMap():返回請求參數組成的Map集合。
String getContextPath() 返回上下文路徑(<Context path="上下文" ../>)。
String getCharacterEncoding() 返回此請求正文使用的字符編碼。
void setAttribute(String name, Object o) 返回指定名稱的屬性值。
void removeAttribute(String name) 從此請求中移除指定屬性。
void setCharacterEncoding(String env) 重寫此請求正文中使用的字符編碼的名稱。必須在使用 getReader() 讀取請求參數或讀取輸入之前調用此方法。
Cookie[] getCookies() 返回包含客戶端隨此請求一起發送的所有 Cookie 對象的數組。如果沒有發送任何 cookie,則此方法返回 null。
String getProtocol() 獲取協議及版本號。
String getMethod() 獲取請求提交的方法。
String getRequestURI() 返回當前請求的資源名稱,上下文路徑/資源名
StringBuffer getRequestURL() 返回瀏覽器地址欄的內容。
String getRemoteAddr():返回請求服務器的客戶端的IP
HttpSession getSession() 返回與此請求關聯的當前會話,如果該請求沒有會話,則創建一個會話。
HttpSession getSession(boolean create) 返回與此請求關聯的當前 HttpSession,如果沒有當前會話並且 create 爲 true,則返回一個新會話。 如果 create 爲 false 並且該請求沒有有效的 HttpSession,則此方法返回 null。
public RequestDispatcher getRequestDispatcher(String path)
2.2 HttpServletResponse
接口
該接口繼承了ServletResponse
接口,ServletResponse
對象提供了將響應發送到瀏覽器的方法。
void setCharacterEncoding(String charset) 設置響應的編碼方式(寫在最前面)。
resp.setContentType("text/html;charset=UTF-8"); 甚至的內容類型。
ServletOutputStream getOutputStream() 返回適用於在響應中編寫的二進制輸出流。
PrintWriter getWriter() 將字符文本返回給瀏覽器。
void addCookie(Cookie cookie) 將指定的cookie添加到響應。
void sendRedirect(String location) 重定向。
3. Servlet配置細節
3.1 配置多個映射路徑
1、在同一個servlet-mapping標籤中配置多個url-pattern。
2、同一個name的servlet對應多個servlet-mapping。
3.2 通配符
/* 統配所有路徑。
/system/* 權限驗證。
*.do或者*.action 通配特有後綴的servlet請求。
3.3 初始化配置
<!--服務器一開啓就創建-->
<servlet>
<servlet-name></servlet-name>
<servlet-class>com.yogie.MyServelt</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
4. 請求轉發與重定向
4.1 轉發
//可以訪問靜態資源
request.getRequestDispatcher("/jsp/show.jsp").forward(request,response);
//可以訪問WEB-INF下的資源(訪問路徑不用跟項目名)
request.getRequestDispatcher("/WEB-INF/list.jsp").forward(request,response);
//跳轉到另一個請求
request.getRequestDispatcher("/forward/a").forward(request,response);
- 由request對象發起。
- 地址欄不變化。
- 瀏覽器只發起一次請求。
- 轉發是同一個請求對象(請求頭共享),即請求攜帶的數據是相同的。
- 按照請求的順序,最後一個
servlet
中的response
對象才起作用。 forward
不可以跨域訪問。- 可以訪問到
WEB-INF
中的資源。
4.2 重定向
//訪問路徑要跟項目名,且不能訪問WEB-INF目錄下的資源
response.sendRedirect("/projectname/jsp/show.jsp");
//等同於瀏覽器再一次發起一次請求
response.sendRedirect("/projectname/redirect/a");
- 由response對象發起。
- 地址欄會發生變化。
- 瀏覽器發起多次請求,每重定向一次就發送一次請求。
- 請求對象不一樣,請求頭不共享。
- 重定向也是最後一個
servlet
中的response
對象才起作用。 - 重定向可以跨域訪問。
- 不能訪問到
WEB-INF
下的資源。
4.3 使用
如果我們需要做請求的數據共享使用forward,如果我們需要訪問WEB-INF裏面只有使用forward,如果我們要跨域,必須使用Redirect,其他情況,隨便(多數使用Redirect)。
4.4 請求包含
request.getRequestDispatcher("/include/b").include(req, resp);
//結果會使得兩個servlet包含在一起。
5. 四大作用域對象
域對象 | 類 | 作用域 |
---|---|---|
pageContext | PageContext | 當前頁面 |
Request | HttpRequest | 一次請求 |
Session | HttpSession | 一次會話 |
Application | ServletContext | 整個應用 |
pageContext
:每次訪問的時候創建,可以有多個;Request
:每次請求的時候創建,可以有多個;Session
:每次會話的時候創建,不同人打開不同的瀏覽器,就創建多個,可以有個多個;Application
:整個應用有且只有一個對象,tomcat啓動的創建,關閉的時候銷燬;
5.1 PageContext
abstract ServletRequest getRequest() 獲取請求對象。
abstract ServletResponse getResponse() 獲取響應對象。
abstract ServletConfig getServletConfig() 獲取當前請求的配置對象。
abstract ServletContext getServletContext() 獲取Application域對象。
abstract HttpSession getSession() 返回session對象。
5.2 HttpRequest
String getContextPath() 返回上下文路徑(默認是單斜槓+項目名),實際上是tomcat服務器中的server.xml文件中<Context path="上下文" ../>。
Cookie[] getCookies() 返回所有的cookie對象
String getHeader(String name) 根據請求頭名字獲取對應的值。
Enumeration getHeaderNames() 獲取所有的請求頭名字。
Enumeration getHeaders(String name) 根據請求頭的名字獲取對應的值。
String getMethod() 獲取表單提交的方法(POST/GET...)。
String getRequestURI() 以String類型返回前臺訪問的資源的路徑。
StringBuffer getRequestURL() 以StringBuffer類型返回前臺訪問的資源的路徑。
HttpSession getSession() 如果有,返回;如果沒有,返回新創建的。
HttpSession getSession(boolean create) 如果有,返回;如果沒有,返回null。
5.3 HttpSession
void setAttribute(String name, Object value) 將對象添加到session中。
Object getAttribute(String name) 從session中獲取指定名稱的對象。
Enumeration getAttributeNames() 返回所有的session屬性名稱。
long getCreationTime() 以毫秒值返回session創建的時間。
String getId() 獲取session對象的id。
ServletContext getServletContext() 獲取Application域對象。
void removeAttribute(String name) 移除指定名稱的session屬性。
5.4 ServletContext
ServletContext
接口:服務器啓動的時候,會爲託管的每一個web應用程序,創建一個ServletContext
對象,從服務器移除託管,或者是關閉服務器。
Object getAttribute(String name) 根據指定的屬性名獲取屬性值。
void removeAttribute(String name) 移除指定屬性。
void setAttribute(String name, Object object) 設置屬性。
Enumeration getAttributeNames() 獲取所有的屬性名。
ServletContext getContext(String uripath) 根據容器中另一個Web應用程序的上下文路徑返回Application域對象。
String getServletContextName()返回web.xml中<display-name>標籤配置的名字。
String getContextPath() 返回上下文路徑(項目的根路徑)。
String getRealPath(String path) 根據虛擬路勁返回絕對路徑。
Set getResourcePaths(String path) 返回指向Web應用程序中資源的所有路徑的類似目錄的清單,這些路徑中最長的子路徑與提供的 path 參數匹配。
URL getResource(String path)返回指向映射到指定路徑的資源的URL。
String getInitParameter(String name) 返回web.xml中<context-param>標籤配置的信息。
Enumeration getInitParameterNames() 返回web.xml中<context-param>標籤配置的信息。
RequestDispatcher getNamedDispatcher(String name) 返回充當指定servlet的包裝器的 RequestDispatcher對象。
RequestDispatcher getRequestDispatcher(String path) 請求轉發使用
InputStream getResourceAsStream(String path) 以流的形式返回指定路徑的資源。
String getServerInfo() 返回正在其上運行 servlet 的 servlet 容器的名稱和版本。
6. Servlet線程安全問題
Servlet是線程不安全的,解決方案:
1、在Servlet中不定義字段。
2、在繼承HttpServlet的同時,實現SingleThreadModel接口
3、將操作字段的代碼提取出來構造一個加鎖的靜態方法。