淺談servlet原始碼
寫在開頭:眾所周知,對於Java web專案來說,servlet是第一步,無論你使用什麼框架,都是基於servlet而進行封裝或者衍生的,所以很有必要研究一下servlet是個什麼東東。
一.servlet的架構圖
如上圖所示,可以看出servlet是一個介面,有一個基礎的實現類,基本上所有的servlet都是基於這個介面展開的,接下來就來看看這個介面有什麼東東。
二.servlet及其相關介面和相關實現類
1.servlet及其相關介面
如上圖所示,servlet可以認為是一個web專案中的入口,首先init()初始化容器,接收一個ServletConfig引數,由容器傳入,ServletConfig就是Servlet的配置,在web.xml中定義Servlet時通過init-param標籤配置的引數由ServletConfig儲存;然後service()處理不同的請求。
接下來看ServletConfig介面
如上圖所示,ServletConfig是Servlet級別,而ServletContext是Context(也就是Application)級別,ServletContext通常利用setAttribute方法儲存Application的屬性。
然後就是ServletContext介面,首先要明確,一個web應用對應一個ServletContext,所以ServletContext的作用範圍是整個應用,明確這點很重要,這是基礎中的基礎。如下是ServletContext介面的原始碼:
public interface ServletContext { // 返回web應用的上下文路徑,就是平時我們部署的應用的根目錄名稱,如部署在Tomcat上的webapps目錄下的demo應用,返回的就是/demo,如果部署在ROOT目錄下的話,返回空字串“”。 此外,這個路徑可以再Tomcat的server.xml裡面修改path屬性。 String getContextPath(); // 方法入參是uriPath,是一個資源定位符的路徑。返回一個ServletContext例項,其實就是根據資源的路徑返回其servlet上下文。 ServletContext getContext(String var1); // 返回當前servlet容器支援的servlet規範的最高版本 int getMajorVersion(); // 返回當前servlet容器支援的servlet規範的最低版本 int getMinorVersion(); // 返回檔案的MIME型別,MIME型別是容器配置的,可以通過web.xml進行配置 String getMimeType(String var1); // 根據傳入的路徑,列出該路徑下的所有資源路徑,返回的路徑是相當於web應用的上下文根或者相對於WEB-INF/lib目錄下的各個JAR包裡面的/META-INF/resources目錄。 Set getResourcePaths(String var1); // 將指定路徑的資源封裝成URL例項並返回 URL getResource(String var1) throws MalformedURLException; // 獲取指定路徑資源的輸入流InputStream並返回 InputStream getResourceAsStream(String var1); // 將指定的資源包裝成RequestDispatcher例項並返回,根據資源路徑(該路徑相對於當前應用上下文根) RequestDispatcher getRequestDispatcher(String var1); // 將指定的資源包裝成RequestDispatcher例項並返回,根據資源的名稱(通過伺服器控制檯或者web.xml裡面配置的,比如web.xml裡面配置servlet的名稱)。 RequestDispatcher getNamedDispatcher(String var1); /** @deprecated */ Servlet getServlet(String var1) throws ServletException; /** @deprecated */ Enumeration getServlets(); /** @deprecated */ Enumeration getServletNames(); void log(String var1); /** @deprecated */ void log(Exception var1, String var2); // 記錄日誌到servlet日誌檔案,servlet日誌檔案的路徑由具體的 servlet容器自己去決定。如果你是在MyEclipse、STS這類的IDE中跑應用的話,那麼日誌資訊將在控制檯(Console)輸出。如果是 釋出到Tomcat下的話,日誌檔案是Tomcat目錄下的/logs/localhost.yyyy-MM-dd.log。 void log(String var1, Throwable var2); // 根據資源虛擬路徑,返回實際路徑;比如getRealPath("index.jsp"),返回index.jsp在系統中的絕對路徑 String getRealPath(String var1); String getServerInfo(); // 用來獲取應用的初始化引數相關資料的,根據引數名獲取引數值,引數的作用域是整個應用。這個引數是在web.xml中配置的,如<context-param>標籤的內容等等。 String getInitParameter(String var1); // 用來獲取應用的初始化引數相關資料的,獲取引數名集合。 Enumeration getInitParameterNames(); Object getAttribute(String var1); Enumeration getAttributeNames(); void setAttribute(String var1, Object var2); void removeAttribute(String var1); String getServletContextName(); }
既然上面說到了RequestDispatcher這個介面,接下來也看看這個介面
public interface RequestDispatcher {
void forward(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
void include(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}
這個介面只有兩個方法,相信大家都比較熟悉這兩個方法了。一個是轉發,一個是包含,有點類似於jsp裡面的轉發和包含。當我們要進行轉發的時候,只有使用這個方法即可,它會通過servlet容器去呼叫相關的介面實現轉發,找到對應的資原始檔,比如在上面介紹的一些相關的方法。
此外,還有ServletRequest & ServletResponse這兩個介面。對於每一個HTTP請求,servlet容器會建立一個封裝了HTTP請求的ServletRequest例項傳遞給servlet的service方法。ServletResponse則表示一個Servlet響應,其影藏了將響應發給瀏覽器的複雜性。通過ServletRequest的方法你可以獲取一些請求相關的引數,而ServletResponse則可以將設定一些返回引數資訊,並且設定返回內容。返回內容之前一般會呼叫setContentType方法設定響應的內容型別,如果沒有設定,大多數瀏覽器會預設以html的形式響應,不過為了避免出問題,我們一般都設定該項。
值得注意的是ServletResponse中定義的getWriter方法,它返回可以將文字傳給客戶端的java.io.PrintWriter。在預設的情況下,PrintWriter物件使用ISO-8859-1編碼,這有可能引起亂碼。
2.servlet相關實現
說了幾個比較常見的介面,接下來就來看看一些相關的實現類吧。第一個當然是GenericServlet,這是第一個實現了Servlet介面的類。GenericServlet是Servlet的預設實現,是與具體協議無關的。
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameter(name);
}
}
public Enumeration getInitParameterNames() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getInitParameterNames();
}
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletContext();
}
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletName();
}
}
}
實現實現了ServletConfig,可以呼叫這裡面的一些方法,比如上面說的,可以獲取servlet應用名字,獲取web.xml的一些引數值。然後還提供了有參的的init()方法,將init方法中的ServletConfig賦給一個類級變數,使得可以通過getServletConfig來獲取。同時為避免覆蓋init方法後在子類中必須呼叫super.init(servletConfig),GenericServlet還提供了一個不帶引數的init方法,當ServletConfig賦值完成就會被帶引數的init方法呼叫。這樣就可以通過覆蓋不帶引數的init方法編寫初始化程式碼,而ServletConfig例項依然得以儲存。
此外,還有兩個log方法:一個記錄日誌,一個記錄異常 。
然後就是HttpServlet,這是一個基於http協議實現的Servlet基類,也在我們平時開發過程中比較常見的一個類。
首先是定義了http協議相關的幾個方法名字變數,然後又根據這些http方法,定義了相應的處理方法,比較常見的就是doGet,doPost方法了,這些方法都基本都跟下面的程式碼差不多,只是改了一下方法名而已。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
此外,HttpServlet還重寫service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 對request和response進行型別強轉
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
//呼叫Http的請求方法處理
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取請求型別
String method = req.getMethod();
long lastModified;
//判斷請求型別進行不同的http方法處理,即路由
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 / 1000L * 1000L) {
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);
}
}
相關連線:
http://www.cnblogs.com/nantang/p/5919323.html