Spring mvc源碼分析系列–Servlet的前世今生
Spring mvc源碼分析系列–Servlet的前世今生
概述
上一篇文章Spring mvc源碼分析系列–前言挖了坑,但是由於最近需求繁忙,一直沒有時間填坑。今天暫且來填一個小坑,這篇文章我們來說說Servlet的發展歷史。所以這篇文章還是比較輕鬆,不涉及太多的源碼分析,簡單介紹Servlet的由來和發展。
Servlet是什麼
傳說在上世紀90年代,因為nternet和瀏覽器的飛速發展,使得基於瀏覽器的B/S模式隨之火爆發展起來。最初,用戶使用瀏覽器向WEB伺服器發送的請求都是請求靜態的資源,比如html、css等。 但是可以想像:根據用戶請求的不同動態的處理並返回資源是理所當然必須的要求,例如用戶提交一些東西,伺服器就能按提交的內容回饋用戶不同的效果。所以人們應該非常迫切想要推出一項技術來實現動態的處理, java 為了應對上述需求,促進了servlet技術誕生。
Servlet 是在伺服器上運行的小程式。這個詞是在 Java applet的環境中創造的,Java applet 是一種當作單獨文件跟網頁一起發送的小程式,它通常用於在客戶端運行,結果得到為用戶進行運算或者根據用戶互作用定點陣圖形等服務。伺服器上需要一些程式,常常是根據用戶輸入訪問資料庫的程式。這些通常是使用公共網關介面(Common Gateway Interface,CGI)應用程式完成的。然而,在伺服器上運行 Java,這種程式可使用 Java 程式語言實現。在通訊量大的伺服器上,JavaServlet 的優點在於它們的執行速度更快於 CGI 程式。各個用戶請求被激活成單個程式中的一個執行緒,而無需創建單獨的進程,這意味著伺服器端處理請求的系統開銷將明顯降低。不清楚CGI是什麼?這篇文章CGI是什麼可以解答你的疑問。
Servlet與 CGI 比較存在的優點如下:
-
與傳統的 CGI 和許多其他類似 CGI 的技術相比,Java Servlet 具有更高的效率,更容易使用,功能更強大,具有更好的可移植性,更節省投資。在未來的技術發展過程中,Servlet 有可能徹底取代 CGI。
-
在傳統的 CGI中,每個請求都要啟動一個新的進程,如果 CGI 程式本身的執行時間較短,啟動進程所需要的開銷很可能反而超過實際執行時間。而在 Servlet 中,每個請求由一個輕量級的 Java 執行緒處理(而不是重量級的作業系統進程)。
-
在傳統 CGI 中,如果有 N 個並發的對同一 CGI程式的請求,則該CGI程式的程式碼在記憶體中重複裝載了 N 次;而對於 Servlet,處理請求的是 N 個執行緒,只需要一份 Servlet 類程式碼。在性能優化方面,Servlet 也比 CGI 有著更多的選擇。
Servlet可以說是Java技術中最早的Web解決方案,Servlet與普通Java類的編寫非常類似。在Servlet中可以通過挨著行輸出Html等語句來實現頁面的樣式和輸出,數據的動態功能當然也就實現了。表現、邏輯、控制、業務全部混在Servlet類中。下面給出一個簡單例子來直觀感受一下。
public void doGet(HttpServletRequest request,HttpServletResponse)
throws IOException,ServletException
{
response.setContentType("text/html;charset=gb2312");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>Hello World!</title></head>");
out.println("<body>");
out.println("<p>Hello World!</p>");
out.println("</body></html>");
}
Servlet是怎麼運行的
上一小節介紹到,Servlet是用於處理動態響應客戶端請求的。那麼Servlet是運行在哪裡的呢?
最早支援 Servlet 技術的是 JavaSoft 的 Java Web Server。此後,一些其它的基於 Java 的 Web Server 開始支援標準的 Servlet API。Servlet 的主要功能在於互動式地瀏覽和修改數據,生成動態 Web 內容。
還記得上一篇文章里的靈魂拷問嗎? 瀏覽器的一個請求,是如何精確到達你的web伺服器里的業務邏輯里的,其中經歷的流程能說個所以然嗎 ,這個過程為:
- 客戶端發送請求至伺服器端。
- 伺服器將請求資訊發送至 Servlet。
- Servlet 生成響應內容並將其傳給伺服器。響應內容動態生成,通常取決於客戶端的請求。
- 伺服器將響應返回給客戶端。
以上的每一步都包含著大量的細節,現在廣泛使用的web伺服器是Tomcat,以Tomcat為例,簡單分析一下以上的四步:
- 客戶端發送請求至伺服器端。這部分涉及的是電腦網路的基礎知識,主要涉及各種協議,例如:ARP、DNS、TCP,HTTP等。
- 伺服器將請求資訊發送至 Servlet。這裡就涉及的是具體的web伺服器實現了,以Tomcat為例,這裡不展開細說,請求到達Tomcat後,會經過各種閥門的處理,然後最終進入到我們的Servlet裡面,這裡附上Tomcat的整體處理流程圖。

- Servlet 生成響應內容並將其傳給伺服器。這部分沒啥好說,就是具體的業務邏輯。
- 伺服器將響應返回給客戶端。跟第一點類似。
Servlet與Tomcat的關係
Tomcat是一個web伺服器,又有人稱其為Servlet容器,那麼顧名思義,Tomcat運行時會包含很多的Servlet在其中,當請求到達Tomcat時,Tomcat會幫我們將請求封裝成一個Request對象,經過不同層級的閥門處理後,轉發到了具體的Servlet里。
所以可以看到二者的關係為:Servlet的運行依賴於Tomcat,Tomcat會為其提供很多基礎功能的支援。同時Tomcat對請求的業務處理是由具體的Servlet去實現,二者的結合有條不紊,實現了一個完整的web伺服器功能。
我們來看一下Servlet的發展歷史,可以看到Servlet的第一個版本發布在1997年。
| 版本 | 日期 | JAVA EE/JDK版本 | 特性 |
|---|---|---|---|
| Servlet 4.0 | 2017年10月 | JavaEE 8 | HTTP2 [1] |
| Servlet 3.1 | 2013年5月 | JavaEE 7 | Non-blocking I/O, HTTP protocol upgrade mechanism |
| Servlet 3.0 | 2009年12月 | JavaEE 6, JavaSE 6 | Pluggability, Ease of development, Async Servlet, Security, File Uploading |
| Servlet 2.5 | 2005年10月 | JavaEE 5, JavaSE 5 | Requires JavaSE 5, supports annotation |
| Servlet 2.4 | 2003年11月 | J2EE 1.4, J2SE 1.3 | web.xml uses XML Schema |
| Servlet 2.3 | 2001年8月 | J2EE 1.3, J2SE 1.2 | Addition of Filter |
| Servlet 2.2 | 1999年8月 | J2EE 1.2, J2SE 1.2 | Becomes part of J2EE, introduced independent web applications in .war files |
| Servlet 2.1 | 1998年11月 | 未指定 | First official specification, added RequestDispatcher, ServletContext |
| Servlet 2.0 | JDK 1.1 | Part of Java Servlet Development Kit 2.0 | |
| Servlet 1.0 | 1997年6月 |
再看Tomcat的發展歷史,可以看到Tomcat的第一個版本是晚於Servlet的,所以Tomcat也被認為是最早比較完善的對Servlet支援的web伺服器。
| 版本 | 日期 | JAVA EE/JDK版本 |
|---|---|---|
| tomcat-10 | 2021-06-16 | JDK 11 |
| tomcat-9 | 2015-11-19 | JDK 1.8 |
| tomcat-8 | 2013-08-05 | JDK 1.7 |
| tomcat-7 | 2010-06-13 | JDK 1.6 |
| tomcat-6 | 2006-10-21 | JDK 1.5 |
| tomcat-5 | 2004-08-29 | JDK 1.4 |
| tomcat-4 | 2003-09-06 | JDK 1.3 |
| tomcat-3 | 2003-09-06 | JDK 1.1 |
再論Servlet是什麼
打開程式碼,可以看到Servlet其實是一個介面,介面意味著什麼?意味著是規範,任何對它的合理實現都可以認為是一個Servlet,以我們常用的http為例,對http的支援是HttpServlet,看一下它的類繼承圖,可以看到它就是實現了Servlet介面。

簡單看一下Servlet介面定義的方法,可以看到只有五個方法,包含了初始化,執行業務邏輯,銷毀等重要過程。

其中重點的是service()方法。那麼這個方法是在哪裡被執行了呢?上面我們說過,Servlet是依賴於Tomcat運行的,所以這個方法應該是在Tomcat里被調用了,我們看一下程式碼。
發現service()方法會在org.apache.catalina.core.ApplicationFilterChain#internalDoFilter(ServletRequest request, ServletResponse response)里被調用。看到這裡,大家應該清楚Servlet如何跟Tomcat串聯起來了吧。
至於我們寫的Servlet是怎麼塞到了ApplicationFilterChain裡面,可以去看後續系列Tomcat的原理分析(又在挖坑,我直接好傢夥)。
小試牛刀
前面說了辣么多,那Servlet項目是什麼結構,以及如何運行的,下面我們返璞歸真搞個簡單的Servlet項目來試試看。
新建一個項目,勾上。

過程省略,可參考文章,最終項目結構如下。

MyServlet程式碼如下。
/**
* @author Codegitz
* @date 2022/9/28
**/
@WebServlet({"/myServlet"})
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("invoke MyServlet#doGet() method");
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("invoke MyServlet#doPost() method");
resp.getWriter().write("<h1>Hello World</h1>");
}
}
啟動Tomcat就可以訪問了。麻雀雖小五臟俱全,這就是一個簡單的Servlet項目構建過程。可以看到這個純粹的Servlet項目,沒有涉及到Spring mvc的東西,那麼如何涉及到Spring mvc後,項目會變成什麼樣呢?這個我們下一篇文章會介紹。

總結
這篇文章簡單介紹了一下Servlet的發展歷史,然後順帶簡單介紹了Tomcat的主要版本已經他們之間的關係。最後是簡單實現了一個Servlet,這裡還沒真正涉及到Spring mvc的內容。
下一篇就會真正的開始Spring mvc的分析,會簡單介紹一下mvc的發展歷史,隨後通過一個小demo引入,然後開始源碼分析。
這篇文章太簡單了,you水一篇。哈哈。
如果有人看到這裡,那在這裡老話重提。與君共勉,路漫漫其修遠兮,吾將上下而求索。


