馬上年底了冯凹,滲透任務(wù)漸少,最近需要寫內(nèi)存馬工具了梢薪,讀了一下《深入剖析Tomcat》蹬铺,理解的還比較淺,做個(gè)記錄沮尿。第一次發(fā)這篇文章是在2021年一月丛塌,當(dāng)時(shí)讀完深入剖析Tomcat做了個(gè)筆記。現(xiàn)在的文章是2022年五月進(jìn)行了重新的修改畜疾,在寫完內(nèi)存馬工具之后赴邻,對(duì)于Tomcat又有了新的理解。另外啡捶,文章加入了讀李號(hào)雙深入拆解Tomcat的總結(jié)姥敛。
1. Servlet
一般我們寫個(gè)普通的Java程序Hello World,用到的是JavaSE(基于JDK的開發(fā))瞎暑,而要開發(fā)Java Web程序用到的是JavaEE(基于JavaSE彤敛,基于服務(wù)器的組件、API標(biāo)準(zhǔn))了赌。Servlet是JavaEE規(guī)范中的一個(gè)墨榄,所有支持Servlet API的Web服務(wù)器也稱為Servlet容器。Servlet容器有Tomcat勿她、Weblogic袄秩、Jboss、Jetty逢并、Resin之剧、TongWeb等。這篇文章寫的是Tomcat砍聊。同樣Tomcat也具備了HTTP請(qǐng)求的功能背稼,也就是它可以處理Socket連接(TCP/IP層)。
Servlet需要用到j(luò)avax.servlet和javax.servlet.http玻蝌。
javax.servlet.servlet接口中聲明了五個(gè)方法蟹肘,包括init\service\destory\getServletConfig\getServletInfo
词疼。前三個(gè)屬于生命周期,一旦實(shí)例化servlet類疆前,就會(huì)調(diào)用唯一一次init()方法寒跳。當(dāng)servlet的客戶端請(qǐng)求(request)到達(dá)后,servlet容器調(diào)用service方法將javax.servlet.servletRequest對(duì)象和javax.servlet.servletResponse對(duì)象作為參數(shù)傳入竹椒。service方法在servlet生命周期內(nèi)會(huì)被多次調(diào)用童太。載入servlet類可以使用java.net.URLClassLoader類的loadClass方法。
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}
javax.servlet.http中有HttpServlet類胸完,變量中包含了HTTP各種method书释,如DELETE、HEAD赊窥、GET爆惧、OPTIONS、POST锨能、PUT扯再、TRACE等。對(duì)于每種method都有doXXX的方法址遇,以get為例熄阻,doGet方法如下:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_get_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
這些doXXX的方法都被集成在該類的service方法中被調(diào)用。
我們想要寫一個(gè)servlet倔约,就需要繼承HttpServlet秃殉,然后重寫doGet或doPost方法。但是有些地址訪問的時(shí)候會(huì)出現(xiàn)重定向浸剩,那么要在doGet中加一句resp.sendRedirect(redirectToUrl);
钾军。重定向有兩種:一種是302響應(yīng),稱為臨時(shí)重定向绢要,一種是301響應(yīng)吏恭,稱為永久重定向。兩者的區(qū)別是重罪,如果服務(wù)器發(fā)送301永久重定向響應(yīng)樱哼,瀏覽器會(huì)緩存/hi到/hello這個(gè)重定向的關(guān)聯(lián),下次請(qǐng)求/hi的時(shí)候蛆封,瀏覽器就直接發(fā)送/hello請(qǐng)求了唇礁。還有一種內(nèi)部轉(zhuǎn)發(fā)勾栗,就是此servlet會(huì)交給另一個(gè)servlet來處理這個(gè)請(qǐng)求惨篱。
另外,需要提到的是支持servlet API的服務(wù)器除了Tomcat围俘,Jetty砸讳、GlassFish琢融、WebLogic、WebSphere這些服務(wù)器也支持簿寂。所以在內(nèi)存馬應(yīng)用的時(shí)候一般分為Tomcat內(nèi)存馬漾抬、Weblogic內(nèi)存馬。
2. 架構(gòu)
先放上一張網(wǎng)上的Tomcat總體架構(gòu)圖常遂∧闪睿可以看到主要分為兩部分:連接器connector
和容器container
。連接器解決的是HTTP請(qǐng)求和解析的問題克胳,將網(wǎng)絡(luò)字節(jié)流轉(zhuǎn)化成Request和Response對(duì)象平绩。容器解決的是加載、管理Servlet并具體Request請(qǐng)求漠另。連接器負(fù)責(zé)對(duì)外交流捏雌,容器則是內(nèi)部管理。
架構(gòu)圖轉(zhuǎn)換成具體的流程架構(gòu)圖如下
連接器Connector
連接器解決了網(wǎng)絡(luò)請(qǐng)求和解析的問題笆搓,無(wú)論協(xié)議是什么性湿,最終在容器中獲取到的都是ServletRequest對(duì)象。連接器具備的功能包括:
(1)網(wǎng)絡(luò)通信的功能:監(jiān)聽網(wǎng)絡(luò)端口满败、接收網(wǎng)絡(luò)請(qǐng)求肤频、讀取網(wǎng)絡(luò)請(qǐng)求字節(jié)流——EndPoint
(2)應(yīng)用層協(xié)議解析:根據(jù)協(xié)議(HTTP/AJP)解析字節(jié)流生成Tomcat Request對(duì)象、將Tomcat Response轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)流——Processor
(3)Tomcat和Servlet標(biāo)準(zhǔn)對(duì)象之間轉(zhuǎn)化:將Tomcat Request對(duì)象轉(zhuǎn)換成符合Servlet標(biāo)準(zhǔn)的ServletRequest對(duì)象葫录,調(diào)用Servlet容器得到ServletResponse轉(zhuǎn)換成Tomcat Response對(duì)象——Adapter
從網(wǎng)絡(luò)處理的邏輯來看着裹,EndPoint
提供字節(jié)流給Processor
,Processor
提供Tomcat Request
對(duì)象給 Adapter
米同,Adapter
提供ServletRequest
對(duì)象給容器Container骇扇。這里要提一句,為什么要對(duì)Request進(jìn)行轉(zhuǎn)換面粮。因?yàn)榻馕鯤TTP生成的是Tomcat自定義的Request少孝,但它不符合Servlet規(guī)范,所以無(wú)法調(diào)用Servlet容器中的內(nèi)容熬苍。Tomcat引入的CoyoteAdapter適配器就是為了解決這個(gè)問題稍走。
網(wǎng)絡(luò)通信的I/O模型可能是非阻塞 I/O、異步 I/O 或者 APR柴底。應(yīng)用層協(xié)議則可能是HTTP婿脸、HTTPS或AJP的,二者之間有多種組合柄驻,所以Tomcat設(shè)計(jì)了ProtocolHandler
接口來封裝這二者狐树。換言之ProtocolHandler
組件包含了EndPoint
和Processor
。根據(jù)不同的I/O模型和應(yīng)用協(xié)議對(duì)接口進(jìn)行不同的實(shí)現(xiàn)鸿脓,如Http11NioProtocol抑钟、AjpNioProtocol等涯曲。但是每種協(xié)議也有自己的抽象基類:AbstractHttp11Protocol、AbstractAjpProtocol在塔。
EndPoint
:是通信端點(diǎn)幻件,即通信監(jiān)聽的接口,接收和發(fā)送具體的Socket蛔溃,實(shí)現(xiàn)了TCP/IP 協(xié)議绰沥。其抽象實(shí)現(xiàn)類是 AbstractEndpoint,具體實(shí)現(xiàn)類根據(jù)IO模型的不同包括:NioEndpoint 和 Nio2Endpoint 贺待。監(jiān)聽接口的功能由其子類Acceptor來實(shí)現(xiàn)揪利,處理Socket請(qǐng)求則是SocketProcessor實(shí)現(xiàn),它會(huì)將請(qǐng)求交給線程池(也叫執(zhí)行器狠持,Executor)疟位。
Processor
:接收來自EndPoint的Socket,讀取字節(jié)流解析成Tomcat Request和Response喘垂。實(shí)現(xiàn)的是HTTP協(xié)議甜刻,根據(jù)協(xié)議的不同具體的實(shí)現(xiàn)類包括 AJPProcessor、 HTTP11Processor 等正勒,實(shí)現(xiàn)了協(xié)議的解析方法和請(qǐng)求處理方法得院。
容器Container
根據(jù)流程架構(gòu)圖可以看出,容器采用的不是平行的模塊設(shè)計(jì)章贞,而是父子關(guān)系Engine->Host->Context->Wrapper祥绞,即引擎->虛擬主機(jī)->WEB應(yīng)用程序->Servlet。父到子是一對(duì)多的關(guān)系鸭限,例如一個(gè)虛擬主機(jī)蜕径。這種層次關(guān)系也在Tomcat的server.xml 配置文件有所體現(xiàn)。所有這些容器組件都實(shí)現(xiàn)了Container接口败京,采用組合模式進(jìn)行管理兜喻。Container實(shí)現(xiàn)了Lifecycle接口,統(tǒng)一管理各組件的生命周期赡麦。
public interface Container extends Lifecycle {
public void setName(String name);
public Container getParent();
public void setParent(Container container);
public void addChild(Container child);
public void removeChild(Container child);
public Container findChild(String name);
}
對(duì)于多層次的設(shè)計(jì)結(jié)構(gòu)朴皆,有個(gè)問題是ServletRequest最終對(duì)應(yīng)的是那個(gè)Wrapper?Tomcat設(shè)計(jì)了Mapper結(jié)構(gòu)泛粹,該結(jié)構(gòu)保存了Web應(yīng)用的配置信息:組件與訪問路徑的映射關(guān)系遂铡。將用戶請(qǐng)求的URL定位到一個(gè)Servlet。
從Engine到Wrapper逐層傳遞請(qǐng)求的過程晶姊,是通過Pipeline-Valve來實(shí)現(xiàn)的扒接。它的設(shè)計(jì)思想是責(zé)任鏈模式。也就是請(qǐng)求處理過程中有多個(gè)Valve閥對(duì)請(qǐng)求進(jìn)行處理,每個(gè)Valve處理完自己的職責(zé)后傳遞給下一個(gè)Valve珠增。
public interface Valve {
public Valve getNext(); //獲取下一個(gè)節(jié)點(diǎn)
public void setNext(Valve valve);
public void invoke(Request request, Response response) //處理請(qǐng)求
}
public interface Pipeline extends Contained { //Pipeline維護(hù)valve鏈表
public void addValve(Valve valve);
public Valve getBasic();
public void setBasic(Valve valve);
public Valve getFirst();
}
可以看到Pipeline中并不存在invoke這種調(diào)用方法,實(shí)際的方法調(diào)用都是由Valve來執(zhí)行砍艾。Pipeline中有g(shù)etBasic和setBasic蒂教。這里的一個(gè)知識(shí)點(diǎn):Basic Valve是Valve鏈的末端節(jié)點(diǎn),負(fù)責(zé)調(diào)用下層容器組件Pipeline的第一個(gè)Valve脆荷。
通過上圖可以看到Basic調(diào)用的是下層容器的第一個(gè)Valve凝垛。而Engine作為頂層容器,它的Valve是由Adapter調(diào)用的
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
Wrapper容器的最后一個(gè) Valve會(huì)創(chuàng)建一個(gè)FilterChain蜓谋,并調(diào)用 doFilter() 方法梦皮,最終調(diào)用Servlet的service方法。
3.生命周期管理
Container中提到桃焕,它實(shí)現(xiàn)了Lifecycle接口剑肯,統(tǒng)一管理組件的生命周期。Tomcat有很多組件观堂,在服務(wù)啟動(dòng)時(shí)需要?jiǎng)?chuàng)建這些組件让网;服務(wù)停止時(shí)需要銷毀組件。也就是這些組件的生命周期需要被動(dòng)態(tài)的管理师痕。李號(hào)雙在討論Tomcat時(shí)提到溃睹,程序設(shè)計(jì)需要找到系統(tǒng)的變化點(diǎn)和不變點(diǎn)。不變點(diǎn)可以被抽象成一個(gè)接口胰坟。無(wú)論是哪個(gè)組件都要經(jīng)歷初始化因篇、啟動(dòng)、停止笔横、銷毀的過程竞滓,只是具體的實(shí)現(xiàn)方式不同。所以可以認(rèn)為初始化等過程是一個(gè)不變點(diǎn)吹缔,可以被抽象為L(zhǎng)ifeCycle(生命周期)虽界,包含init()、start()涛菠、stop()莉御、destory()
。上面的容器章節(jié)中還提到俗冻,Container的管理是采用的組合模式礁叔,在生命周期中可以理解為,當(dāng)父組件調(diào)用init()
時(shí)會(huì)調(diào)用其子組件的init()
迄薄。
組件的init()和start()都是由父組件狀態(tài)變化觸發(fā)的琅关,狀態(tài)的變化可以看作是一個(gè)事件。如果想要修改init()或start()的邏輯,可以通過事件監(jiān)聽器(觀察者模式)來實(shí)現(xiàn)涣易,而無(wú)需更改組件本身的代碼画机。也就是在LifeCycle中加入兩個(gè)方法:添加和刪除監(jiān)聽器(addLifecycleListener、removeLifecycleListener
)新症。另外需要定義一下組件生命周期有哪些狀態(tài)步氏,如NEW、INITIALIZING徒爹、INITIALIZED荚醒、 STARTING_PREP、STARTING隆嗅、STARTED
等界阁。
Lifecycle接口的實(shí)現(xiàn)類有很多,因?yàn)槊總€(gè)組件都有自己的生命周期胖喳。但是往往這些實(shí)現(xiàn)中又存在相同的邏輯泡躯,例如狀態(tài)的改變、事件的觸發(fā)等丽焊。這些公用邏輯就可以被放入到基類中精续。然后讓子類繼承這個(gè)基類,就實(shí)現(xiàn)了代碼的重用粹懒≈馗叮基類中會(huì)存在一些抽象方法,也就是基類中不作具體實(shí)現(xiàn)的方法凫乖,由子類來實(shí)現(xiàn)确垫。它是邏輯的骨架。
// LifeCycleBase.init
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {//狀態(tài)合法性檢查帽芽,例如當(dāng)前狀態(tài)必須是new才能進(jìn)行實(shí)例化
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
{
try{
setStateInternal(LifecycleState.INITIALIZING, null, false); //觸發(fā)INITIALIZING事件監(jiān)聽器
initInternal(); //調(diào)用具體子類的初始化方法删掀,進(jìn)而調(diào)用子組件的init方法
setStateInternal(LifecycleState.INITIALIZED, null, false); //觸發(fā)INITIALIZED事件監(jiān)聽器
}catch(Throwable t){...}
}
LifeCycleBase調(diào)用監(jiān)聽器的方法,那么這里還涉及到一個(gè)問題导街,監(jiān)聽器在什么時(shí)候如何被加入到系統(tǒng)中披泪?主要分為兩種情況:(1)Tomcat自定義監(jiān)聽器,在父組件創(chuàng)建子組件的過程中注冊(cè)到子組件(2)server.xml中用戶自定義的監(jiān)聽器搬瑰,Tomcat啟動(dòng)時(shí)自動(dòng)解析server.xml款票。
4. Server和Service
Tomcat的啟動(dòng)是通過/bin目錄下的startup.sh來啟動(dòng)JVM,運(yùn)行啟動(dòng)類Bootstrap泽论。Bootstrap初始化類加載器艾少,并實(shí)例化Catalina。Catalina解析server.xml并創(chuàng)建Server組件翼悴。Server啟動(dòng)Service組件缚够。Service啟動(dòng)容器和連接器。這些啟動(dòng)相關(guān)的類或組件并不處理具體請(qǐng)求,主要負(fù)責(zé)管理谍椅。
startup.sh -> Bootstrap -> Catalina -> Server -> Service
Catalina
public void start() {
if (getServer() == null) { // 如果Server 實(shí)例為空误堡,就解析 server.xml 創(chuàng)建Server
load();
}
if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer")); // 如果創(chuàng)建失敗,報(bào)錯(cuò)退出
return;
}
try {
getServer().start(); // 啟動(dòng) Server
} catch (LifecycleException e) {
return;
}
if (useShutdownHook) { // 創(chuàng)建并注冊(cè)關(guān)閉鉤子
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
if (await) { // 用 await 方法監(jiān)聽停止請(qǐng)求
await();
stop();
}
}
5.I/O模型
UNIX 系統(tǒng)下的 I/O 模型有 5 種:同步阻塞 I/O雏吭、同步非阻塞 I/O锁施、I/O 多路復(fù)用、信號(hào)驅(qū)動(dòng) I/O 和異步 I/O思恐。所謂的I/O就是就是計(jì)算機(jī)內(nèi)存與外部設(shè)備之間拷貝數(shù)據(jù)的過程。網(wǎng)絡(luò)I/O通信的過程中會(huì)涉及到兩個(gè)對(duì)象:調(diào)用I/O操作的用戶線程膊毁、操作系統(tǒng)內(nèi)核胀莹。用戶線程是不能直接訪問內(nèi)核空間的。用戶線程發(fā)起I/O操作后婚温,網(wǎng)絡(luò)數(shù)據(jù)讀取主要有兩步:
(1)用戶線程等待內(nèi)核將數(shù)據(jù)從網(wǎng)卡拷貝到內(nèi)核空間
(2)內(nèi)核將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間
而各類I/O模型的區(qū)別就是它們實(shí)現(xiàn)這兩個(gè)步驟的方式不同描焰。I/O模型圖示如下:
同步阻塞是用戶線程發(fā)起read后就阻塞了,讓出CPU栅螟。直到數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間荆秦,再把用戶線程喚醒。同步非阻塞就是用戶線程不斷發(fā)起read力图,數(shù)據(jù)如果沒到內(nèi)核空間就會(huì)返回失敗步绸。直到數(shù)據(jù)到了內(nèi)核空間,并向用戶空間拷貝的過程中阻塞吃媒。多路復(fù)用是線程先發(fā)起select瓤介,詢問內(nèi)核空間的數(shù)據(jù)是否準(zhǔn)備完成。一旦內(nèi)核數(shù)據(jù)準(zhǔn)備好了用戶線程發(fā)起read赘那。稱為多路復(fù)用的原因是刑桑,一次select掉用可以向內(nèi)核查詢多個(gè)數(shù)據(jù)通道Channel的狀態(tài)。異步是用戶線程發(fā)起read的同時(shí)注冊(cè)一個(gè)回調(diào)函數(shù)募舟,然后返回read祠斧。內(nèi)核數(shù)據(jù)準(zhǔn)備完成后再調(diào)用指定回調(diào)函數(shù)完成處理,這個(gè)過程中用戶線程一直沒有阻塞拱礁。
NioEndPoint
Tomcat的NioEndPoint組件實(shí)現(xiàn)的就是I/O多路復(fù)用模型琢锋。創(chuàng)建Selector后注冊(cè)相應(yīng)事件,然后調(diào)用select方法呢灶,等待事件發(fā)生吩蔑。一旦發(fā)生就創(chuàng)建一個(gè)新的線程從Channel中讀數(shù)據(jù)。
NioEndPoint共有五個(gè)組件:
LimitLatch填抬、Acceptor烛芬、Poller、SocketProcessor
和Executor
。
LimitLatch:控制最大連接數(shù)赘娄。NIO 模式下默認(rèn)是 10000仆潮,達(dá)到這個(gè)閾值后,連接請(qǐng)求被拒絕遣臼。
Acceptor:監(jiān)聽連接請(qǐng)求性置,在死循環(huán)中調(diào)用accept方法接收新的連接。一旦有新的連接就返回一個(gè)Channel對(duì)象并交給Poller處理揍堰。(位于單獨(dú)線程)
Poller:檢測(cè)Channel的I/O事件鹏浅,一旦有Channel可讀就創(chuàng)建SocketProcessor任務(wù)類給線程池Executor處理。(位于單獨(dú)線程屏歹,本質(zhì)是一個(gè)Selector)
Executor:線程池隐砸,負(fù)責(zé)運(yùn)行SocketProcessor任務(wù)類,SocketProcessor的run方法會(huì)調(diào)用Http11Processor(應(yīng)用層協(xié)議的封裝)來讀取和解析請(qǐng)求數(shù)據(jù)蝙眶。
Nio2EndPoint
Nio是同步非阻塞季希,NIO.2 則是異步。Java NIO.2實(shí)現(xiàn)服務(wù)器Demo如下
// 異步服務(wù)器
public class Nio2Server {
void listen(){
//1. 創(chuàng)建一個(gè)線程池幽纷,用來執(zhí)行來自內(nèi)核的回調(diào)請(qǐng)求
ExecutorService es = Executors.newCachedThreadPool();
//2. 創(chuàng)建異步通道群組式塌,并綁定一個(gè)線程池
AsynchronousChannelGroup tg = AsynchronousChannelGroup.withCachedThreadPool(es, 1);
//3. 創(chuàng)建服務(wù)端異步通道,綁定到異步通道群組
AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open(tg);
//4. 綁定監(jiān)聽端口
assc.bind(new InetSocketAddress(8080));
//5. 監(jiān)聽連接友浸,傳入回調(diào)類處理連接請(qǐng)求峰尝,另外傳入的this是Nio2Server對(duì)象本身
assc.accept(this, new AcceptHandler());
}
}
// 回調(diào)類
public class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Nio2Server> {
@Override
public void completed(AsynchronousSocketChannel asc, Nio2Server attachment) {
// 調(diào)用 accept 方法繼續(xù)接收其他客戶端的請(qǐng)求
attachment.assc.accept(attachment, this);
//1. 先分配好 Buffer,告訴內(nèi)核收恢,數(shù)據(jù)拷貝到哪里去
ByteBuffer buf = ByteBuffer.allocate(1024);
//2. 調(diào)用 read 函數(shù)讀取數(shù)據(jù)境析,除了把 buf 作為參數(shù)傳入,還傳入讀回調(diào)類
channel.read(buf, buf, new ReadHandler(asc));
}
public interface CompletionHandler<V,A> { //模板參數(shù) V 和 A派诬,分別表示 I/O 調(diào)用的返回值和附件類
void completed(V result, A attachment); // I/O 操作成功時(shí)調(diào)用
void failed(Throwable exc, A attachment); I/O 操作失敗時(shí)調(diào)用
}
回調(diào)類AcceptHandler
類實(shí)現(xiàn)了CompletionHandler
接口的completed
方法劳淆。它還有兩個(gè)模板參數(shù),第一個(gè)是異步通道默赂,第二個(gè)就是 Nio2Server 本身
比較Nio2Endpoint跟NioEndpoint運(yùn)行流程圖會(huì)發(fā)現(xiàn)沛鸵,Nio2Endpoint中沒有 Poller 組件,也就是沒有 Selector缆八。因?yàn)樵诋惒?I/O 模式下曲掰,Selector 的工作交給內(nèi)核來做了。
AprEndpoint
AprEndpoint也實(shí)現(xiàn)了非阻塞 I/O奈辰,但NioEndpoint 通過調(diào)用Java的NIO API來實(shí)現(xiàn)非阻塞I/O栏妖,而AprEndpoint是通過JNI調(diào)用APR本地庫(kù)而實(shí)現(xiàn)非阻塞I/O的。對(duì)于Tomcat來說奖恰,APR本地庫(kù)的性能是優(yōu)于Java的NIO API的吊趾,尤其是需要與操作系統(tǒng)進(jìn)行頻繁交互的場(chǎng)景宛裕,例如Socket、TLS加密傳輸论泛。在這些場(chǎng)景下Java和C語(yǔ)言的性能相比有差距揩尸。APR就是C語(yǔ)言編寫的。Java寫的Tomcat想要調(diào)用APR需要通過JNI(Java Native Interface)方式來調(diào)用屁奏,JNI可以使Java調(diào)用其他語(yǔ)言編寫的程序岩榆。
6. Java線程池
程序運(yùn)行時(shí)需要通過使用系統(tǒng)資源(CPU、內(nèi)存坟瓢、網(wǎng)絡(luò)勇边、磁盤等)來完成信息的處理。如果程序需要頻繁創(chuàng)建折联、銷毀對(duì)象粒褒,那么就會(huì)產(chǎn)生性能問題姐呐≡绨牛“池”就是用來解決這個(gè)問題。
常見的“池”包括:數(shù)據(jù)庫(kù)連接池、內(nèi)存池怕享、線程池、常量池镰踏。池將用過的對(duì)象保存起來函筋,等下一次需要用這個(gè)對(duì)象的時(shí)候,直接從對(duì)象池中取出奠伪,避免頻繁創(chuàng)建和銷毀跌帐。
Java線程是對(duì)操作系統(tǒng)線程的封裝,也是一個(gè)對(duì)象绊率。創(chuàng)建Java線程也消耗系統(tǒng)資源谨敛,因此有了線程池。Web容器一般會(huì)把處理請(qǐng)求的工作放到線程池中執(zhí)行滤否,Tomcat就擴(kuò)展了原生的Java線程池來滿足高并發(fā)的需求脸狸。
Java原生線程池內(nèi)部維護(hù)了一個(gè)線程數(shù)組和一個(gè)任務(wù)隊(duì)列。當(dāng)任務(wù)處理不過來時(shí)就把任務(wù)放到隊(duì)列里慢慢處理藐俺。Java線程池的一些核心類包括:ThreadPoolExecutor炊甲、FixedThreadPool/CachedThreadPool
。
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, //核心線程數(shù)
int maximumPoolSize, //最大線程數(shù)
long keepAliveTime, //超時(shí)時(shí)間
TimeUnit unit,
BlockingQueue<Runnable> workQueue, //工作隊(duì)列
ThreadFactory threadFactory,
RejectedExecutionHandler handler) // 線程拒絕策略
通過ThreadPoolExecutor
的構(gòu)造方法可以看出欲芹,如果線程數(shù)還沒達(dá)到corePoolSize卿啡,當(dāng)任務(wù)來臨時(shí)就創(chuàng)建新的線程來執(zhí)行。達(dá)到corePoolSize之后菱父,新增的任務(wù)就不創(chuàng)建線程而是放入工作隊(duì)列workQueue中颈娜。線程池從workQueue中獲取任務(wù)剑逃。當(dāng)workQueue隊(duì)列也滿了,線程池就創(chuàng)建臨時(shí)線程來處理揭鳞,但如果總的線程數(shù)大于maximumPoolSize就不能再創(chuàng)建新的線程炕贵,執(zhí)行拒絕策略handler。如果工作隊(duì)列中也沒有線程需要處理的任務(wù)了野崇,該線程可能會(huì)被銷毀称开。
FixedThreadPool/CachedThreadPool
這兩個(gè)類是Java中默認(rèn)的線程池實(shí)現(xiàn),本質(zhì)是給ThreadPoolExecutor設(shè)置了不同的參數(shù)乓梨。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()); //無(wú)界隊(duì)列
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, //對(duì)線程個(gè)書不做限制
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()); //隊(duì)列長(zhǎng)度為0
}
FixedThreadPool是固定長(zhǎng)度(nThreads)的線程數(shù)組鳖轰,多余的任務(wù)放到無(wú)限長(zhǎng)的隊(duì)列中處理。CachedThreadPool不對(duì)線程個(gè)數(shù)進(jìn)行限制扶镀,多余的任務(wù)會(huì)無(wú)限創(chuàng)建臨時(shí)線程進(jìn)行處理蕴侣。這兩個(gè)線程池的實(shí)現(xiàn)針對(duì)于參數(shù)是否限制線程個(gè)數(shù)或隊(duì)列長(zhǎng)度
Tomcat線程池
taskqueue = new TaskQueue(maxQueueSize); //任務(wù)隊(duì)列,限制最大長(zhǎng)度
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority()); //線程工廠
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf); //線程池臭觉,限制核心線程數(shù)minSpareThreads和最大線程池?cái)?shù)量maxThreads
Tomcat 線程池?cái)U(kuò)展了原生的 ThreadPoolExecutor昆雀,通過重寫 execute 方法實(shí)現(xiàn)了自己的任務(wù)處理邏輯。區(qū)別在于當(dāng)隊(duì)列滿了開始創(chuàng)建臨時(shí)線程總線程數(shù)達(dá)到maximumPoolSize后蝠筑,原生的線程池是執(zhí)行拒絕策略狞膘,而Tomcat線程池則是繼續(xù)嘗試將任務(wù)添加到隊(duì)列中。如果緩沖隊(duì)列也滿了什乙,插入失敗再執(zhí)行拒絕策略挽封。代碼如下:
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
...
public void execute(Runnable command, long timeout, TimeUnit unit) {
submittedCount.incrementAndGet();
try {
// 調(diào)用 Java 原生線程池的 execute 去執(zhí)行任務(wù)
super.execute(command);
} catch (RejectedExecutionException rx) { //總線程數(shù)達(dá)到maximumPoolSize,原生線程池拋出異常
// 如果總線程數(shù)達(dá)到 maximumPoolSize臣镣,Java 原生線程池執(zhí)行拒絕策略
if (super.getQueue() instanceof TaskQueue) {
final TaskQueue queue = (TaskQueue)super.getQueue();
try {
// 繼續(xù)嘗試把任務(wù)放到任務(wù)隊(duì)列中去
if (!queue.force(command, timeout, unit)) {
submittedCount.decrementAndGet();
// 如果緩沖隊(duì)列也滿了辅愿,插入失敗,執(zhí)行拒絕策略忆某。
throw new RejectedExecutionException("...");
}
7. WebSocket
網(wǎng)絡(luò)上雙向鏈路通信的兩個(gè)端點(diǎn)稱為Socket点待。每個(gè)Socket對(duì)應(yīng)一個(gè)IP地址和端口號(hào),是對(duì)TCP/IP協(xié)議抽象出來的API弃舒,Socket本身不是協(xié)議癞埠。但WebSocket是一個(gè)應(yīng)用層協(xié)議,類似HTTP協(xié)議棒坏。它通過HTTP協(xié)議進(jìn)行一次握手燕差,握手之后數(shù)據(jù)直接從TCP層的Socket傳輸,與HTTP協(xié)議不再相關(guān)坝冕。
WebSocket的數(shù)據(jù)傳輸會(huì)以frame形式傳輸徒探,將一條消息分為幾個(gè)frame,按照先后順序傳輸出去喂窟。這樣大數(shù)據(jù)可以分片傳輸测暗,不用考慮數(shù)據(jù)大小央串。并且和HTTP 的 chunk 一樣,可以邊生成數(shù)據(jù)邊傳輸碗啄,提高傳輸效率质和。
8. 熱部署和熱加載
熱加載的實(shí)現(xiàn)方式是 Web 容器啟動(dòng)一個(gè)后臺(tái)線程,定期檢測(cè)類文件的變化稚字,如果有變化饲宿,就重新加載類,在這個(gè)過程中不會(huì)清空 Session 胆描,一般用在開發(fā)環(huán)境瘫想。
熱部署原理類似,也是由后臺(tái)線程定時(shí)檢測(cè) Web 應(yīng)用的變化昌讲,但它會(huì)重新加載整個(gè) Web 應(yīng)用国夜。這種方式會(huì)清空 Session,比熱加載更加干凈短绸、徹底车吹,一般用在生產(chǎn)環(huán)境。
Jetty
Tomcat處理HTTP和Servlet的組件分別是Connector(多個(gè))和Container(一個(gè))醋闭。在Jetty中則是Connector(多個(gè))和Handler(多個(gè))窄驹,這兩個(gè)組件所需的資源都是從全局線程池ThreadPool中獲取。Handler根據(jù)不同的場(chǎng)景選取對(duì)應(yīng)的Handler目尖,如ServletHandler馒吴、SessionHandler扎运。這兩個(gè)組件的調(diào)用都是通過Server類來實(shí)現(xiàn)——?jiǎng)?chuàng)建并初始化Connector瑟曲、Handler、ThreadPool組件豪治,然后調(diào)用 start方法啟動(dòng)它們洞拨。
如果對(duì)Jetty和Tomcat的架構(gòu)進(jìn)行對(duì)比,(1)Jetty中沒有Service的概念负拟,Connector是被所有Handler共享的烦衣。(2)在 Tomcat中每個(gè)連接器都有自己的線程池,而Jetty中共享一個(gè)全局的線程池掩浙。
Connector
上文提到Tomcat的Connector的網(wǎng)絡(luò)通信功能支持多種IO模型花吟,而Jetty主要支持NIO。在介紹Connector工作流程之前厨姚,先總結(jié)一下NIO衅澈。
NIO
Java NIO的核心組件有三個(gè):Channels、Buffers谬墙、Selectors
今布。Channel通道表示一個(gè)連接经备,可以理解為一個(gè)Socket。通過Channel可以讀寫數(shù)據(jù)部默,但是這個(gè)讀寫過程并不直接操作侵蒙,需要通過Buffer來中轉(zhuǎn)。也就是將數(shù)據(jù)從Channel讀到Buffer傅蹂,也可以從Buffer寫到Channel纷闺。通道包含很多種如網(wǎng)絡(luò)IOSockerChannel
、文件IOFileChannel
等份蝴。Buffer根據(jù)傳遞數(shù)據(jù)的基本類型不同也有ByteBuffer急但、CharBuffer、IntBuffer等搞乏。Selector則是位于Thread和Channel中間波桩,允許單線程處理多個(gè) Channel,想要使用Selector请敦,需要向其中注冊(cè)Channel镐躲,調(diào)用select方法來調(diào)用Channel。NIO模式Demo如下:
ServerSocketChannel server = ServerSocketChannel.open(); //創(chuàng)建服務(wù)端 Channel
server.socket().bind(new InetSocketAddress(port)); //綁定監(jiān)聽端口
server.configureBlocking(false); //設(shè)置為非阻塞
Selector selector = Selector.open(); //創(chuàng)建Selector
server.register(selector, SelectionKey.OP_ACCEPT); //注冊(cè)事件OP_ACCEPT侍筛,如果有新的連接請(qǐng)求就通知
while (true) {
selector.select();// 查詢 I/O 事件
for (Iterator<SelectionKey> i = selector.selectedKeys().iterator(); i.hasNext();) {
SelectionKey key = i.next(); // 遍歷SelectionKey列表
i.remove();
if (key.isAcceptable()) { // 如果有注冊(cè)的事件
SocketChannel client = server.accept(); // 建立一個(gè)新連接
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ); // 反饋給Selector萤皂,可接收IO讀事件
}
}
}
總的來說上述代碼完成了三件事:監(jiān)聽連接、IO事件查詢匣椰、數(shù)據(jù)讀寫裆熙。這三個(gè)分別對(duì)應(yīng)于Jetty的Acceptor、SelectorManager禽笑、Connection
入录。
一些框架
服務(wù)接入層:反向代理 Nginx;API 網(wǎng)關(guān) Node.js佳镜。
業(yè)務(wù)邏輯層:Web 容器 Tomcat僚稿、Jetty;應(yīng)用層框架 Spring蟀伸、Spring MVC 和 Spring Boot蚀同;ORM 框架 MyBatis;
數(shù)據(jù)緩存層:內(nèi)存數(shù)據(jù)庫(kù) Redis啊掏;消息中間件 Kafka蠢络。
數(shù)據(jù)存儲(chǔ)層:關(guān)系型數(shù)據(jù)庫(kù) MySQL;非關(guān)系型數(shù)據(jù)庫(kù) MongoDB迟蜜;文件存儲(chǔ) HDFS刹孔;搜索分析引擎 Elasticsearch
RPC框架:Spring Cloud、Dubbo
網(wǎng)絡(luò)通信:Netty
分布式協(xié)調(diào):Zookeeper
Bootstrap
在org.apache.catalina.startup包下包含catalina類和Bootstrap類(引導(dǎo)類加載器)小泉。前者用于啟動(dòng)或關(guān)閉Server對(duì)象芦疏,并解析Tomcat配置文件冕杠。后者則是一個(gè)入口點(diǎn),負(fù)責(zé)創(chuàng)建Catalina實(shí)例酸茴,并調(diào)用其process方法分预。Catalina類封裝了一個(gè)server對(duì)象,該對(duì)象還有一個(gè)service對(duì)象薪捍。而service對(duì)象本身包含一個(gè)servlet容器和多個(gè)連接器笼痹。
Server
org.apache.catalina.core.StandardServer類是Server接口的實(shí)現(xiàn),提供了addService酪穿、removeService凳干、findServices等方法,其生命周期包括:initialize被济、start救赐、stop、await
Service接口的實(shí)現(xiàn)類StandardService的initialize方法用于初始化添加到其中的所有連接器只磷,start方法可以啟動(dòng)連接器和所有servlet容器
HTTP請(qǐng)求
HTTP請(qǐng)求在客戶端和服務(wù)端之間交互经磅,而套接字就是兩頭的端點(diǎn),使應(yīng)用程序可以連接钮追、發(fā)送或接收字節(jié)流预厌。客戶端由Socket類實(shí)現(xiàn)元媚,其getOutputStream方法可以獲取一個(gè)OutputStream對(duì)象轧叽。要發(fā)送文本給服務(wù)器的話還需要?jiǎng)?chuàng)建一個(gè)java.io.PrintWriter對(duì)象。如果客戶端想接收服務(wù)器端發(fā)送的字節(jié)流則需要調(diào)用getInputStream方法刊棕。相應(yīng)的炭晒,服務(wù)器端由ServerSocket類實(shí)現(xiàn)套接字。它與客戶端不同的是鞠绰,它需要等待來自客戶端的連接請(qǐng)求腰埂。而應(yīng)用程序交互過程中主要用三個(gè)類:HttpServer飒焦、Request蜈膨、Response。
四種容器
servlet容器是用來處理請(qǐng)求servlet資源牺荠,Tomcat中共有四種類型的容器:Engine翁巍、Host、Context休雌、Wrapper灶壶。一個(gè)容器可以又0個(gè)或多個(gè)低層級(jí)的子容器。
Engine:表示整個(gè)Catalina servlet引擎
Host:表示包含一個(gè)或多個(gè)Context容器的虛擬主機(jī)
Context:表示一個(gè)Web應(yīng)用程序杈曲。一個(gè)Context可以有多個(gè)Wrapper
Wrapper:表示一個(gè)獨(dú)立的servlet
上述四個(gè)接口都繼承自Container接口驰凛,其標(biāo)準(zhǔn)實(shí)現(xiàn)分別為StandardEngine胸懈、StandardHost、StandardContext恰响、StandardWrapper趣钱。再放上一張網(wǎng)圖:
以Engine接口為例:
public interface Engine extends Container {
String getDefaultHost();
void setDefaultHost(String var1);
String getJvmRoute();
void setJvmRoute(String var1);
Service getService();
void setService(Service var1);
}
Wrapper
Wrapper本身具有“包裝”的含義。org.apache.catalina.Wrapper接口的實(shí)現(xiàn)類主要負(fù)責(zé)管理其基礎(chǔ)servlet類的servlet生命周期胚宦,其中有兩個(gè)重要的方法:load和allocate首有。前者載入并初始化servlet類,后者分配一個(gè)已經(jīng)初始化的servlet實(shí)例枢劝。StandardWrapper對(duì)象的主要任務(wù)是載入它所代表的servlet類井联,并進(jìn)行實(shí)例化,但是StandardWrapper并不調(diào)用servlet的service方法您旁,而是由StandardWrapperValve對(duì)象完成烙常,該對(duì)象通過調(diào)用allocate方法從StandardWrapper實(shí)例中獲取servlet實(shí)例,然后再調(diào)用servlet實(shí)例的service方法鹤盒。StandardWrapperValve是StandardWrapper實(shí)例中的基礎(chǔ)閥军掂,要完成兩個(gè)操作:
1.執(zhí)行與該servlet實(shí)例關(guān)聯(lián)的全部過濾器
2.調(diào)用servlet實(shí)例的service方法
具體執(zhí)行過程大致如下:
1.調(diào)用StandardWrapper實(shí)例的allocate方法獲取該StandardWrapper實(shí)例所代表的servlet實(shí)例
2.調(diào)用私有方法createFilterChain,創(chuàng)建過濾器鏈
3.調(diào)用過濾器鏈的doFilter方法昨悼,其中包括調(diào)用servlet實(shí)例的service方法
4.釋放過濾器鏈
5.調(diào)用Wrapper實(shí)例的deallocate方法
6.若該servlet類再也不會(huì)被使用到蝗锥,就調(diào)用Wrapper實(shí)例的upload方法。
Filter
插入一小段Filter的介紹率触,F(xiàn)ilter即過濾器可以把一些公用邏輯從Servlet中抽離出來终议,在HTTP請(qǐng)求到達(dá)Servlet之前,先被filter預(yù)處理葱蝗。Filter類穴张,也在javax.servlet中,該接口有三個(gè)方法:init/doFilter/destory
public interface Filter {
void init(FilterConfig var1) throws ServletException;
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
void destroy();
}
如果我們自己實(shí)現(xiàn)一個(gè)過濾器两曼,就要重寫doFilter方法皂甘,并調(diào)用chain.doFilter
回到上面StandardWrapperValve的執(zhí)行過程,createFilterChain會(huì)創(chuàng)建一個(gè)ApplicationFilterChain實(shí)例悼凑,并將所有需要應(yīng)用到該Wrapper實(shí)例所代表的servlet實(shí)例的過濾器都添加到其中偿枕。了解ApplicationFilterChain類就要先了解FilterDef類和ApplicationFilterConfig類。
org.apache.catalina.deploy.FilterDef類表示一個(gè)過濾器的定義户辫。該類中的每個(gè)屬性表示在定義filter元素時(shí)聲明的子元素渐夸。Map類型的變量parameters存儲(chǔ)了初始化過濾器時(shí)所需要的所有參數(shù)。
org.apache.catalina.core.ApplicationFilterConfig類實(shí)現(xiàn)了javax.servlet.FilterConfig接口渔欢,用于管理web應(yīng)用程序第一次啟動(dòng)時(shí)創(chuàng)建的所有的過濾器實(shí)例墓塌。
一個(gè)Context對(duì)象(Web應(yīng)用程序)+FilterDef對(duì)象(過濾器)=構(gòu)建一個(gè)ApplicationFilterConfig(web應(yīng)用程序的過濾器實(shí)例)
org.apache.catalina.core.ApplicationFilterChain類實(shí)現(xiàn)了javax.servlet.FilterChain接口,StandardWrapperValue類則可以創(chuàng)建一個(gè)ApplicationFilterChain類的實(shí)例,調(diào)用其doFilter方法苫幢。
servlet請(qǐng)求過程:
請(qǐng)求servlet類->StandardWrapper.setServletClass獲取servlet類完全限定名/StandardWrapper.setName為servlet類指定一個(gè)名字->StandardWrapper載入servlet類->該servlet是否實(shí)現(xiàn)了SingleThreadModel接口访诱,實(shí)現(xiàn)了就只能載入一次。
HTTP連接過程:
connector->
StandardContext->
StandardContextPipeline->
StandardContextValue->
StandardWrapper->
StandardWrapperValue->
Servlet
Context
Context接口的實(shí)現(xiàn)表示一個(gè)Web應(yīng)用程序韩肝,其中較為重要的方法是addWrapper和createWrapper盐数。StandardContext對(duì)象可以讀取并解析默認(rèn)的web.xml文件,該文件位于%CATALINA_HOME%/conf目錄下伞梯,該文件的內(nèi)容會(huì)應(yīng)用到所有部署到tomcat中的應(yīng)用程序中玫氢。StandardContext構(gòu)造函數(shù)中會(huì)為其管道對(duì)象設(shè)置基礎(chǔ)閥,類型為org.apache.catalina.core.StandardContextValue谜诫,該基礎(chǔ)閥會(huì)處理從連接器中接收到的每個(gè)HTTP請(qǐng)求漾峡。它處理請(qǐng)求Wrapper實(shí)例的過程是調(diào)用Context容器的map方法,并傳入org.apache.catalina.Request對(duì)象喻旷,針對(duì)某個(gè)特定的協(xié)議調(diào)用findMapper方法返回一個(gè)映射器對(duì)象生逸,來獲取wrapper實(shí)例。
Host
Host容器是org.apache.catalina.Host接口的實(shí)例且预,其map方法可以處理引入的HTTP請(qǐng)求的context容器的實(shí)例槽袄。Host接口的實(shí)現(xiàn)是StandardHost類,它的構(gòu)造函數(shù)會(huì)將一個(gè)基礎(chǔ)閥的實(shí)例添加到管道對(duì)象中锋谐,基礎(chǔ)閥是org.apache.catalina.core.StandardHostValue遍尺。
管道
管道包含某個(gè)servlet容器將要調(diào)用的任務(wù),一個(gè)閥表示一個(gè)具體的執(zhí)行任務(wù)涮拗。在servlet容器的管道中乾戏,有一個(gè)基礎(chǔ)閥,還可以通過server.xml額外添加任意數(shù)量的閥三热。管道就像過濾鏈條鼓择,而閥就是其中一個(gè)一個(gè)的過濾器,基礎(chǔ)閥是最后執(zhí)行的就漾,負(fù)責(zé)處理request和response對(duì)象呐能。
| 閥1 | 閥2 | 閥3 |......|閥n |---->管道
管道有幾個(gè)重要的接口,如Pipeline抑堡、Value摆出、ValueContext等。Pipeline接口中的addValue夷野、removeValue分別代表向管道中添加和刪除閥懊蒸。setBasic方法則將基礎(chǔ)閥設(shè)置到管道中。Value接口中有個(gè)invoke方法悯搔,連接器調(diào)用容器的invoke方法后,容器會(huì)調(diào)用管道的invoke方法。ValueContext接口可以訪問管道的所有成員妒貌,具有invokeNext方法通危,調(diào)用后續(xù)的閥。
servlet容器中有一個(gè)名為驗(yàn)證器的閥來支持安全限制灌曙。當(dāng)servlet容器啟動(dòng)時(shí)菊碟,驗(yàn)證器閥會(huì)被添加到Context容器的管道中。在調(diào)用Wrapper閥之前會(huì)先調(diào)用驗(yàn)證器閥在刺,對(duì)當(dāng)前用戶身份進(jìn)行驗(yàn)證逆害,如果驗(yàn)證成功就繼續(xù)調(diào)用后續(xù)的閥,顯示請(qǐng)求的servlet蚣驼。
用戶身份驗(yàn)證依靠“領(lǐng)域”魄幕,它會(huì)對(duì)用戶輸入的用戶名密碼進(jìn)行判斷。領(lǐng)域?qū)ο笸ㄟ^調(diào)用Context容器的setRealm方法與一個(gè)Context容器相關(guān)聯(lián)颖杏。領(lǐng)域?qū)ο笫莖rg.apache.catalina.Realm接口的實(shí)例纯陨。該接口的基本實(shí)現(xiàn)類是org.apache.catalina.realm.RealmBase類,該類為抽象類留储。RealmBase還有一些繼承類:JDBCRealm翼抠、JNDIRealm、MemoryRealm获讳、UserDatabaseRealm等阴颖。默認(rèn)情況下會(huì)使用MemoryRealm類的實(shí)例作為驗(yàn)證用的領(lǐng)域?qū)ο蟆5顷懪渲檬峭ㄟ^org.apache.catalina.deploy.LoginConfig類的實(shí)例丐膝,其getRealmName方法獲取領(lǐng)域?qū)ο蟮拿直旄牵琯etAuthName方法獲取所使用驗(yàn)證方法的名字(如BASIC\DIGEST\FORM\CLIENT-CERT,分別對(duì)應(yīng)驗(yàn)證器BasicAutherticator\DigestAuthenticator\FormAuthticator\SSLAuthenticator)
而驗(yàn)證器則是org.apache.catalina.Authenticator接口的實(shí)例尤误。其實(shí)現(xiàn)類為AuthenticatorBase類侠畔,該類也是一個(gè)閥。
載入器
在catalina中损晤,載入器就是org.apache.catalina.Loader接口的實(shí)例软棺。如果要實(shí)現(xiàn)的是重載,那么實(shí)現(xiàn)的則是org.apache.cataline.loader.Reloader接口尤勋。這里有兩個(gè)術(shù)語(yǔ):repository倉(cāng)庫(kù)喘落、resource資源。倉(cāng)庫(kù)表示載入器會(huì)在哪里搜索要載入的類最冰,資源指的是一個(gè)類載入器中的DirContext對(duì)象瘦棋,它的文件根路徑指的是上下文的文件根路徑。
類載入器
每次創(chuàng)建Java類實(shí)例時(shí)都需要用載入器將類載入到內(nèi)存中暖哨,在這個(gè)過程中赌朋,類載入器會(huì)在一些核心Java類庫(kù)及環(huán)境變量CLASSPATH中指明的目錄中搜索相關(guān)類,如果找不到就會(huì)拋出java.lang.ClassNotFoundException異常。類載入器由上至下有三種:引導(dǎo)類載入器(bootstrap class loader)沛慢、擴(kuò)展類載入器(extension class loader)赡若、系統(tǒng)類載入器(system class loader)。
引導(dǎo)類加載器用于引導(dǎo)啟動(dòng)Java虛擬機(jī)团甲,載入運(yùn)行JVM所需的類及Java核心類逾冬,如java.lang包、java.io包等躺苦。擴(kuò)展類加載器載入標(biāo)準(zhǔn)擴(kuò)展目錄中的類身腻。系統(tǒng)類加載器是默認(rèn)的類加載器,搜索在環(huán)境變量CLASSPATH中指明的路徑和JVR文件匹厘。
每當(dāng)需要載入一個(gè)類時(shí)嘀趟,先調(diào)用系統(tǒng)類載入器,它會(huì)把任務(wù)再交給其父類加載器集乔,最終交到引導(dǎo)類加載器去件。如果引導(dǎo)類加載器找不到相關(guān)類,就再還給子加載器去尋找扰路。
但是類加載也有限制尤溜,servlet只能加載WEB-INF/classes目錄及其子目錄下的類,不能訪問其他路徑中的類汗唱,即使這些類包含在運(yùn)行當(dāng)前Tomcat的JVM的CLASSPATH環(huán)境變量中宫莱。
Tomcat中的載入器一般指的是WEB應(yīng)用程序載入器而不僅僅是指類載入器。載入器必須實(shí)現(xiàn)org.apache.catalina.Loader接口哩罪,重載則是org.apache.catalina.loader.Reloader授霸。Reloader接口中最重要的方法是modified方法,如果web中某個(gè)servlet或相關(guān)類被修改了际插,modified方法會(huì)返回true碘耳。addRepository方法可以用來添加倉(cāng)庫(kù)。web應(yīng)用程序的載入器由Loader接口實(shí)現(xiàn)類org.apache.catalina.loader.WebappLoader框弛。當(dāng)調(diào)用WebappLoader類的start方法時(shí)辛辨,會(huì)進(jìn)行如下工作:創(chuàng)建一個(gè)類載入器、設(shè)置倉(cāng)庫(kù)瑟枫、設(shè)置類路徑斗搞、設(shè)置訪問權(quán)限、啟動(dòng)一個(gè)新線程來支持自動(dòng)重載慷妙。
載入類時(shí)僻焚,webappClassLoader類要遵循如下規(guī)則:
1.所有已經(jīng)載入的類都會(huì)緩存起來,所以載入類時(shí)要先檢查本地緩存
2.若本地緩存中沒有就檢查上一層膝擂,調(diào)用java.lang.ClassLoader類的findLoadedClass方法
3.若啟用了SecurityManager虑啤,則檢查是否允許類載入器進(jìn)行加載隙弛,防止Web應(yīng)用程序中的類覆蓋J2EE的類。
4.若打開標(biāo)志位delegate或者待載入的類是術(shù)語(yǔ)包觸發(fā)器中的包名咐旧,則調(diào)用父類載入器來載入相關(guān)類驶鹉,如果父類載入器為null绩蜻,則使用系統(tǒng)的類載入器铣墨。
5.從當(dāng)前倉(cāng)庫(kù)中載入相關(guān)類
6.若當(dāng)前倉(cāng)庫(kù)中沒有需要的類,且標(biāo)志位delegate關(guān)閉办绝,則使用父類載入器伊约,若父類載入器為null,則使用系統(tǒng)的類載入器進(jìn)行加載
7.若仍未找到需要的類孕蝉,則拋出ClassNotFoundException異常屡律。
日志記錄器
所有的日志記錄器必須實(shí)現(xiàn)org.apache.catalina.logger接口
Session
由org.apache.catalina.Manager接口表示Session管理器來管理建立的Session對(duì)象。它必須與一個(gè)Context容器相關(guān)聯(lián)降淮,servlet實(shí)例可以通過調(diào)用javax.servlet.http.HttpServlet.Request接口的getSession方法來獲取一個(gè)Session對(duì)象
異常處理
tomcat處理錯(cuò)誤消息的方法是將其存儲(chǔ)在一個(gè)properties文件中超埋,便于讀取和編輯。每個(gè)properties文件都是用org.apache.catalina.util.StringManager類的一個(gè)實(shí)例來處理佳鳖。每個(gè)實(shí)例會(huì)讀取某個(gè)包下的指定properties文件霍殴。