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