Servlet 和 Servlet容器
Servlet
很多同學可能跟我一樣始終沒有搞清楚到底什麼是 Servlet,什麼是 Servlet 容器。網上看了很多帖子,或許人家說的很清楚,但是自己的那個彎彎就是拐不過來。
想了很久說一下自己的理解。
Java web 開發中為啥要有 Servlet 呢?是否可以不要。
web開發的本質就一句話:客戶端和伺服器交換資料。於是你使用 Java 的 Socket 套接字進行程式設計,去處理客戶端來的 tcp 請求,經過編解碼處理讀取請求體,獲取請求行,然後找到請求行對應的處理邏輯步入伺服器的處理中,處理完畢把對應的結果返回給當前的 Socket 連結,響應完畢,關閉 Socket。
以上過程,你有沒有發現其實是兩個部分:
-
建立連線,傳輸資料,關閉連線,你肯定知道這些步驟不是你所開發的web服務去處理的,而是tomcat容器幫你做了這些事情。
-
拿到請求行之後去找對應的 url 路由,這一部分是誰做的呢?在如今 SpringBoot 橫行的時代,去配置化已經成為趨勢,程式設計越來越簡單導致的後果就是越來越難以理解事物最開始的樣子。還記得 SpringMVC工程中的 web.xml檔案嗎?是否還記得在web.xml中有這麼一段配置呢:
<servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:/spring/SpringMVC-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
Spring 的核心就是一個 Servlet,它攔截了所有的請求,將請求交給 DispatcherServlet 去處理。
我們再來問一遍,Servlet 到底是什麼,它就是一段處理 web 請求的邏輯,並不是很高深的東西。
再來看 Java 中的 Servlet,它只是一個介面:
package javax.servlet; import java.io.IOException; 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(); }
Servlet 介面規定請求從容器到達 web 服務端的規範,最重要的三個步驟是:
- init():初始化請求的時候要做什麼;
- service():拿到請求的時候要做什麼;
- destory():處理完請求銷燬的時候要做什麼。
所有實現 Servlet 的實現方都是在這個規範的基礎上進行開發。那麼 Servlet 中的資料是從哪裡來的呢?答案就是 Servlet 容器。容器才是真正與客戶端打交道的那一方。Servlet容器只有一個,而 Servlet 可以有多個。常見的Servlet容器Tomcat,它監聽了客戶端的請求埠,根據請求行資訊確定將請求交給哪個Servlet 處理,找到處理的Servlet之後,呼叫該Servlet的 service() 方法,處理完畢將對應的處理結果包裝成ServletResponse 物件返回給客戶端。
Servlet 容器
上面說過,Servlet 只是一個處理請求的應用程式,光有Servlet是無法執行起來的,需要有一個 main 方法去呼叫你的這段 Servlet 程式才行。所以這裡出現了Servlet 容器的概念。Servlet容器的主要作用是:
- 建立連線;
- 呼叫Servlet處理請求;
- 響應請求給客戶端;
- 釋放連線;
這上面的四步,如果是你來設計的話是否可以用一個模板方法搞定,1,3,4都是固定的步驟,不會因為請求不同而有很大的變化。2卻會因為對應的請求不同需要業務邏輯自己去實現不同的處理。所以這裡抽象出來了 Servlet,Servlet想怎麼玩就怎麼玩,這是你自己的事情。容器幫你做的是你不想做的髒活累活。
另外,既然叫做容器肯定是能裝多個Servlet,並且可以管理Servlet的宣告週期。這些功能應該是容器必備的。
上面提到了 web.xml 中的 DispatcherServlet,它是 Spring 中定義的一個 Servlet,實現了 Servlet 介面,本質也是一個 Servlet。只是它是 HttpServlet 的繼承者,主要處理 http 請求。所以 Spring 程式本質是就是一個 Servlet。SpringMVC 幫你做了本該你去實現的邏輯,你看不到並不代表它不是。
好啦,以上通俗的語言解釋了什麼是 Servlet,什麼是 Servlet 容器,以及 Servlet 和 Servlet 容器之間的關係。
Tomcat
Tomcat是啥呢?本質上是一個 Servlet 容器,實現了對 Java Servlet 規範的支援。同時 Tomcat 也提供了處理HTTP請求的能力,所以也可以作為一個Web伺服器。瞭解到Tomcat有 Web伺服器和 Servlet容器的功能,那麼 Tomcat總體是如何設計的呢?我們來看一張簡圖:
Java web 應用如果部署到 Tomcat 中,一個Tomcat就表示一個服務。一個 Server 伺服器可以包含多個 Service 服務,Tomcat 預設的 Service 服務是 Catalina,而一個 Service 服務可以包含多個聯結器,因為 Tomcat 支援多種網路協議,包括 HTTP/1.1、HTTP/2、AJP 等等,一個 Service 服務還會包括一個容器,容器外部會有一層 Engine 引擎所包裹,負責與處理聯結器的請求與響應,聯結器與容器之間通過 ServletRequest 和 ServletResponse 物件進行交流。
Tomcat容器的設計提現在一個核心檔案中:server.xml。這個檔案充分展示了Tomcat的高度抽象設計:
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"/>
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>
其中:
-
Server 元件是管理 tomcat 例項的元件,可以監聽一個埠,從此埠上可以遠端向該例項傳送 shutdown 關閉命令。
-
Service 元件是一個邏輯元件,用於繫結 connector 和 container,有了 service 表示可以向外提供服務,就像是一般的 daemon 類服務的 service。可以認為一個 service 就啟動一個JVM,更嚴格地說,一個 engine 元件才對應一個 JVM (定義負載均衡時,jvmRoute 就定義在 Engine 元件上用來標識這個 JVM ),只不過 connector 也工作在 JVM 中。
小故事:
是否關注到 Service name = Catalina,實際上 Tomcat 的前身就是 Catalina,這是一個島的名字,而
Catalina 只是一個 Servlet 容器,為Servlet和 JavaServer Pages(JSP)實現了Sun Microsystems的規範。
Tomcat 的作者 詹姆斯·鄧肯·戴維森,Sun Microsystems 的軟體架構師在後來 Sun Microsystems 向 Apache Software Foundation 捐贈該專案中發揮了重要作用。當時他認為許多開源專案都有與 O'Reilly 相關的書籍,封面上有動物,所以他想以動物命名。後來這位老哥想到了貓