最近在看《看透Spring MVC 源代碼分析與實踐》這本書,以爲真心不錯。這本書不僅僅講的是Spring MVC,第一章爲網站基礎知識,包括網站架構、常見協議、Servlet和Tomcat等內容。雖然在作開發的時候並不會直接用到,不過理解了以後可讓咱們在進行具體開發的時候更加駕輕就熟,還能夠經過對具體內容的學習學到一些優秀思想。此次我想先從最簡單的Servlet內容談起,畢竟每一個Java Web開發人員應該對Servlet都不陌生。文章內容主要是對書中知識點的概括總結,外加一部分本身的理解。
你們都知道Servlet是Server+Applet的縮寫,表示一個服務器應用。其實Servlet就是一套規範,按照這套規範寫的代碼就能夠直接在Java的服務器上運行。Servlet 3.1中Servlet的結構以下圖所示(來源:www.54tianzhisheng.cn/2017/07/09/… ,你們能夠沒事看看這個博客,有些內容講的很好): java
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
複製代碼
init方法在容器啓動時被容器調用且只會調用一次(當load-on-startup設置爲負數或不設置時會在Servlet第一次被用到時被調用)。init方法被調用時,由容器傳給它一個ServletConfig類型的參數,ServletConfig顧名思義指的是Servlet的配置,咱們在web.xml中定義Servlet時經過init-param標籤配置的參數就是經過ServletConfig來保存的;
getServletConfig方法用於獲取這個ServletConfig對象;
service方法用於具體處理一個請求,它處理的請求是不限請求類型的。具體怎麼處理呢,一會講到實現它的方法的時候會說;
getServlet方法能夠獲取一些Servlet相關的信息,須要本身實現,默認返回空字符串;
destroy方法主要用於在Servlet銷燬(通常指關閉服務器)時釋放一些資源,也只會調用一次。web
ServletConfig接口定義以下:服務器
public interface ServletConfig {
public String getServletName();
public ServletContext getServletContext();
public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
}
複製代碼
getServletName用於獲取Servlet的名字,也就是咱們在web.xml中定義的servlet-name;
getInintParameter用於獲取init-param配置的參數;
getInitParameterNames用於獲取配置的全部init-param的名字集合;
getServletContext的返回值ServletContext表明這個應用自己。 我身邊不少人都不明白「表明應用自己」是什麼意思,這是說ServletContext裏設置的參數,能夠被當前應用的全部Servlet共享。你們常常在Session或Application中保存參數,然後者不少時候就是保存在了ServletContext中。能夠把ServletConfig理解成Servlet級的,而ServletContext是Context(也就是Application)級的。固然,ServletContext的功能要強大不少,並非保存一下配置參數。
可能上面說的比較抽象,下面來舉一個例子。在web.xml文件中寫以下配置信息:架構
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>application-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>DemoServlet</servlet-name>
<servlet-class>com.hawk.DemoServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>demo-servlet.xml</param-value>
</init-param>
</servlet>
複製代碼
上面經過context配置的contextConfigLocation配置到了ServletContext中,而經過servlet下的init-param配置的contextConfigLocation配置到了ServletConfig中。在Servlet中能夠分別經過它們的getInitParameter方法進行獲取:app
String contextLocation = getServletConfig().getServletContext().getInitParameter("contextConfigLocation");
String servletLocation = getServletConfig().getInitParameter("contextConfigLocation");
複製代碼
爲了操做方便,GenericServlet定義了getInitParameter方法,內部返回getServletConfig().getInitParameter的返回值,所以若是須要獲取SevletConfig中的參數,能夠直接調用getInitParameter,而沒必要再調用getServletConfig()。
ServletContext中常見的用法就是保存Application級的屬性,經過調用setAttribute方法:框架
getServletContext().setAttribute("contextConfigLocation", "new path");
複製代碼
而ServletConfig不可設置屬性。學習
GenericServlet是Servlet的默認實現(注意:GenericServlet是一個抽象類,service方法並未被實現),是與具體協議無關的Servlet,主要作了三件事:(1)實現ServletConfig接口,能夠直接調用ServletConfig裏的方法;(2)提供了無參的init方法;(3)提供了log方法。下面分別來解釋一下:
GenericServlet實現了ServletConfig接口,在須要調用ServletConfig中方法的時候能夠直接調用,而沒必要先獲取ServletConfig。好比,獲取ServletContext的時候能夠直接調用getServletContext,而無需調用getServletConfig().getServletContext(),不過和剛纔說的getInitParameter同樣,其底層實現實際上是在內部調用了:網站
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
複製代碼
GenericServlet實現了Servlet的init(ServletConfig config)方法,在裏面將config設置給了內部屬性config,而後調用了無參的init方法(這個無參的init方法是GenericServlet新增的,專門用於被它的子類覆蓋):this
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
複製代碼
這麼寫的做用很明顯:首先將參數config設置給了內部屬性config,這樣就能夠在ServletConfig的接口方法中直接調用config的相應方法來執行;其次,以後咱們在寫Servlet的時候就能夠只處理本身的初始化邏輯(即只需覆蓋無參的init方法),而不須要再關心config,也不須要再調用super.init(config)了。一開始說到容器啓動時會調用init(ServletConfig config)方法,而該方法會調用無參的init方法,從而實現咱們本身的初始化邏輯。須要注意的是,若是在本身的Servlet中重寫了帶參數的init方法,必定要記着調用init(config),不然這裏的config屬性接收不到值,相應的ServletConfig接口方法就不能執行了。
GenericServlet提供兩個log方法,一個記錄日誌,一個記錄異常,具體實現是經過傳給ServletContext的日誌實現的。通常咱們都有本身的日誌處理方式,因此這兩個log方法用得不是不少。spa
HttpServlet是用HTTP協議實現的Servlet的子類,通常咱們在寫Servlet的時候就是直接繼承這個類,Spring MVC中最重要的DispatcherServlet也是繼承的這個類。分析這個類主要關心的是如何處理請求,HttpServlet主要重寫了service方法,首先將ServletRequest和ServletResponse轉換成HttpServletRequest和HttpServletResponse,而後根據Http請求的類型不一樣將請求路由到了不一樣的處理方法,代碼以下:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
// 轉換request和reponse的類型
// 若是請求類型不相符,則拋出異常
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 獲取請求類型
String method = req.getMethod();
long lastModified;
// 將不一樣的請求類型路由到不一樣的處理方法
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
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);
}
}
複製代碼
而像doGet、doPost這樣的具體處理方法相信你們都很熟悉了,這裏就再也不概述,重點是HttpServlet將不一樣的請求方式路由到不一樣的處理方法這個實現思想。 最後呢,想談一下我我的對Servlet這種底層事物的見解。有的人在學了各類框架後,就對Servlet、JDBC這種底層事物很不屑,畢竟實際開發中Servlet是用不到的。但我以爲,不管多麼高級的框架,本質上都是對這些底層事物的封裝,區別在於封裝的程度不一樣,瞭解這些底層事物的實現細節,對於框架的學習能起到事半功倍的做用。 最後的最後,牆裂推薦《看透Spring MVC 源代碼分析與實踐》這本書,我以爲很OK。