摘要:在組件樹中前面啟動的過程中提到過的的線程負責接收連接,接收請求之后調用方法,把包裝成,創建一個任務,從線程池中獲取一個線程處理該任務。
概念
Java Web,是基于Java語言實現web服務的技術總和。介于現在Java在web客戶端應用的比較少,我把學習重點放在了JavaWeb服務端應用。雖然用Springboot就可以很快地搭建一個web項目了,但是如果想要深入了解JavaWeb的實現原理,就不得不先學習Servlet和Servlet容器的相關知識。
首先什么是Servlet?
Servlet從廣義上講是Sun公司提供的一門用于開發動態Web資源的技術,而狹義上指的是實現了javax.servlet.Servlet接口的類的統稱。Servlet接口很簡單,只有init、getServletConfig、service、getServletInfo和destroy這5個方法,它們構成了實現Servlet功能的規范。像Spring的DispatcherServlet,都是一種具體的Servlet。
當然了,光有Servlet這一個類可沒什么用,它沒有main方法,不能獨立運行,就像光有子彈沒有槍,子彈的價值就發揮不出來。要想實現Servlet的功能,就必須有一個Servlet容器。
Servlet容器,也叫做Servlet引擎,是web服務器的一部分,用于接收網絡請求,把請求轉發給對應的Servlet,并把Servlet處理的結果返回給網絡。
它是web服務器和Servlet之間的媒介。
它建立服務端socket、監聽端口、創建流。
它管理著Servlet的生命周期,如加載部署Servlet,實例化初始化Servlet,調用Servlet方法(處理業務),以及銷毀Servlet。
Tomcat就是一個獨立運行的Servlet容器。
Tomcat組織結構
下面就以Tomcat8為例,看看一個具體的Servlet容器是如何實現上述功能的。
先來一張Tomcat的結構圖:
Server:Tomcat頂層容器,代表著整個服務器。包含一個或多個Service組件;
Service:存活在Server內部的中間組件,包含Connector和Container這兩個核心組件,負責將一個或多個Connector組件綁定到一個Container上;
Connector:監聽端口,處理與客戶端基于某種協議的通信,提供Socket與request和response的轉換;
Container:封裝和管理Servlet,負責對請求進行處理,并生成響應。
(先介紹這幾個大的組件,小組件在后面會細講)
這樣展示可能比較抽象,我們可以打開我們安裝的Tomcat目錄下的conf/server.xml,看下Tomcat是如何配置這些組件的:
...
先看Connector,可以看到一個Service里是可以配置多個Connector的,這里我們主要關心HTTP協議的Connector。
Connector使用持有的ProtocolHandler類型對象來處理請求,它包含的三個部件:
Endpoint:綁定端口、監聽請求;
Processor:將Endpoint接收到的Socket封裝成Request;
Adapter:將Request交給Container進行具體的處理。
再看Container,它內部包含了4個子容器
Engine:Servlet引擎,Container最上層,每個Service只能包含一個,表示一個特定的Service的請求處理流水線,從Connector接收處理所有的請求并返回響應;
Host:虛擬主機,一個引擎可以包含多個Host;一個Host可以包含多個Context;
Context:一個Context表示了一個Web應用程序(Web工程)。Context直接管理Servlet在容器中的包裝類;
Wrapper:每一個Servlet在容器中的包裝類。
我們可以通過Tomcat文件夾里的文件結構來幫助理解,上面提到的conf/server.xml里配置Host時有個appBase的屬性是webapps,是不是很眼熟?我們在Tomcat安裝目錄下總是有一個webapp文件夾,整個webapps就是一個Host站點,里面放著的每個文件夾目錄就對應一個Context,其中ROOT目錄中存放著主應用,其他目錄存放著子應用。
Tomcat類加載
先看Tomcat是如何加載類的。
Tomcat啟動時創建的類加載器有
BootstrapClassLoader:加載JVM提供的基本運行類,和$JAVA_HOME/jre/lib/ext里的jar包;
SystemClassLoader:加載tomcat啟動的類,即Catalina.bat中指定位置的類;
CommonClassLoader:加載tomcat以及應用通用的類,位于CATALINA_HOME/lib下。父加載器是AppClassLoader;
WebAppClassLoader:每個應用在部署后都創建一個唯一的類加載器,加載位于WEB-INF/lib中的jar包和WEB-INF/classes下的class文件。父加載器是CommonClassLoader。
Tomcat默認類加載邏輯:
1、先在本地緩存中查找,如果已經加載即返回,否則繼續下一步
2、嘗試Bootstrap加載,如果加載到即返回,否則
3、WebApp自行加載,先/WEB-INF/classes,再/WEB-INF/lib/*.jar,如果加載到即返回,否則
4、委托WebApp父類加載器(Common ClassLoader)去加載。。。
注意:第3、4兩步違反了雙親委托機制,但也只是Tomcat自定義的ClassLoader加載順序違反了,頂層還是相同的。
Tomcat的這種加載邏輯保證了每個應用程序的同名類庫是獨立的,同時可以共享共有類庫。
每一個JSP文件對應一個Jsp類加載器,當一個jsp文件修改了,就直接卸載這個jsp類加載器,重新創建類加載器,重新加載jsp文件。
Tomcat啟動流程
下面開始分析Tomcat大致啟動流程,建議配合源碼食用。
Tomcat傳統的啟動入口通過startup.bat和catalina.bat腳本調用org.apache.catalina.startup.Bootstrap.main(),分為兩部分:
一、init():初始化main線程的daemon(一個Bootstrap對象)。初始化Tomcat類加載器,通過反射來實例化Catalina對象;
二、daemon執行三個方法setAwait(true)、load(args)和start():
1、setAwait:通過反射調用catalina的setAwait方法設置await屬性,后面會用到;
2、load(args):通過反射調用catalina的load方法,創建xml解析器,解析conf/server.xml創建出了StandardServer對象并init,繼而調用內部包含的service的int,以此逐層初始化所有組件;
3、start():通過反射調用catalina的start()方法,和init方法一樣逐層start所有組件;最后利用前面設置的await屬性調用await方法,繼而調用server的await方法,保證主線程運行并持續監聽8005端口的SHUTDOWN指令,接收到后調用stop方法關閉Tomcat。
上面各個組件的init和start都是一筆帶過,那么他們實際完成了什么樣的工作呢?
Server.init():調用包含的Service的init;
Service.init():初始化Engine,初始化Executor(所有Connector共享的線程池),初始化mapperListener(用來保存容器映射),調用Connector.init;
Connector.init():初始化ProtocolHandler、Adapter,Endpoint創建ServerSocket并綁定監聽端口
Server.start():調用包含的Services的start;
Service.start():與初始化對應,調用Engine.start,啟動Executor,啟動mapperListener(作為監聽者加到容器和它們的子容器中),調用Connector.start
Connector.start():Endpoint創建acceptor線程來接收客戶端的連接以及poller線程來處理連接中的讀寫請求
Engine.start():逐一啟動Host、Context、Wrapper
Context.start():步驟很多,這里列舉幾個重要的:
*)創建讀取資源文件的對象
*)創建ClassLoader對象,就是上面提到過的每個應用唯一的WebAppClassLoader
*)設置應用的工作目錄
*)啟動相關輔助對象,如Logger、realm、resources等
*)通知監聽者ContextConfig讀取和解析Web應用web.xml和注解
*)啟動web.xml解析到的子容器(解析時將Servlet包裝成StandardWrapper)
*)啟動Pipeline(一種責任鏈設計模式后面會講)
*)獲取或創建ServletContext,并設置必要的參數
*)創建Context中配置的Listener;
*)創建和初始化配置的Filter;
*)創建和初始化loadOnStartup大于等于0的Servlet
Tomcat處理請求過程
現在我們知道Tomcat是如何啟動的,那么啟動之后Tomcat如何處理一次請求的呢?
下面以一次Http請求為例來說明,請求URL=http://hostname:port/contextpath/servletpath。
在Connector組件樹中:
前面啟動的過程中提到過Connector的Endpoint的acceptor線程負責接收Socket連接,acceptor接收請求之后調用processSocket方法,把socket包裝成SocketWrapper,創建一個SocketProcessor任務,從線程池中獲取一個線程處理該任務。run方法中調用AbstractEndpoint.Handler.process方法,根據請求的協議類型(con/server.xml中connector元素的protocol屬性值)創建相應的類型處理類Processor,對SocketWrapper的輸入流和輸出流進行包裝,根據SocketWrapper創建輕量級的coyote.Request和coyote.Response,解析http請求的請求頭和請求行,最后Adapter.service(Request, Response),將coyote.Request和coyote.Response轉化成Connector.Request和Connector.Response,調用connector.getService().getMapper().map(),根據hostname、contextpath和servletpath找到對應的host、context和Wapper(前面利用mapperListener保存的容器完整關系),設置到Request中去;再調用connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)將請求傳遞給與Connector關聯的Container逐級傳遞下去(Engine->Host->Context->Wapper)。
在Container組件樹中:
Container容器按照責任鏈的設計模式,使用管道Pipeline和Value的方式來傳遞請求。
第一層是Engine,先通過conf/server.xml中配置的value,最后總會流到StandardEngineValue,調用host.getPipeline().getFirst().invoke(request, response)將請求傳遞給request中保存的Host;
第二層是Host,同樣先通過配置的value,最后流到StandardHostValue,再傳遞給request中保存的Context;
第三層是Context,流到StandardContextValue傳遞給request中保存的Wapper;
最后是Wapper,流到StandardWapperValue,獲取Servlet單例(雙檢查鎖機制),獲取FilterChain執行Filter鏈,也是一種責任鏈模式,執行完所有配置的Filter后執行Servlet.service,即我們希望其完成的業務邏輯。
(在進入Filter的時候,傳入的是Connector.Request的門面類RequestFacade,和Request一樣都是HttpServletRequest和HttpServletResponse的實現類)
返回過程略。
第一次寫文章,條理排版不是很清晰,以后慢慢改進。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72314.html
摘要:瀏覽器的中文數據提交給服務器,以編碼對中文編碼,當我在讀取數據的時候,拿到的當然是亂碼。接下來使用方式傳遞中文數據,把表單的方式改成即可當我們訪問的時候,又出現亂碼了于是我按照上面的方式,把對象設置編碼為試試結果還是亂碼。 什么是HttpServletRequest HttpServletRequest對象代表客戶端的請求,當客戶端通過HTTP協議訪問服務器時,HTTP請求頭中的所有信...
閱讀 1628·2021-10-12 10:11
閱讀 3746·2021-09-03 10:35
閱讀 1438·2019-08-30 15:55
閱讀 2122·2019-08-30 15:54
閱讀 991·2019-08-30 13:07
閱讀 1003·2019-08-30 11:09
閱讀 568·2019-08-29 13:21
閱讀 2644·2019-08-29 11:32