tomcat總體結構
Server:接受請求並解析,完成相關任務,返回處理結果。通常情況下使用Socket監聽服務器指定端口來實現該功能,一個最簡單的服務設計如下:
Start():啟動服務器,打開socket連接,監聽服務端口,接受客戶端請求、處理、返回響應
Stop():關閉服務器,釋放資源
缺點:請求監聽和請求處理放一起擴展性很差(協議的切換 tomcat獨立部署使用HTTP協議,與Apache集成時使用AJP協議
改進一:網絡協議與請求處理分離:一個Server包含多個Connector(鏈接器)和Container(容器)
Connector:開啟Socket並監聽客戶端請求,返回響應數據;
Container:負責具體的請求處理
缺點:Connector接受的請求由那個Container處理,需要建立映射規則
改進二:一個Server可以包含多個Service,每一個Service都是獨立的,他們共享一個JVM以及系統類庫。一個Service負責維護多個Connector和一個Container,這樣來自Connector的請求只能有它所屬的Service維護的Container處理。
在這里Container是一個通用的概念,為了明確功能,並與Tomcat中的組件名稱相同,可以將Container命名為Engineer
在Engine容器中需要支持管理WEB應用,當接收到Connector的處理請求時,Engine容器能夠找到一個合適的Web應用來處理,因此在上面設計的基礎上增加Context來表示一個WEB應用,並且一個Engine可以包含多個Context。
缺點:應用服務器需要將每個域名抽象為一個虛擬主機
在一個web應用中,可以包含多個Servlet實例來處理來自不同的鏈接請求,因此我們還需要一個組件概念來表示Servlet定義,即Wrapper。在前面的多次Container容器中,有Engine、Host、Context、Wrapper等,可以理解為Container的子類.
tomcat源碼生命周期分析:從官網上下載tomcat源碼包,此次分析使用的版本tomcat-8.5.54
一般分為兩類:鏈接器的實現(LifecycleBase),容器的實現(container)。及相關抽象類的實現。
BootStrap(引導程序)是Tomcat的入口。找到java目錄下org.apache.catalina.startup包下的Bootstrap類。
Main方法和static語句塊:
Static語句塊的作用(看代碼):在靜態代碼塊中設置catalinaHome和catalinaBase兩個路徑
static {...
System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());//Globals.CATALINA_HOME_PROP="catalina.home",我們可以通過設置vm參數的形式傳入:editConfig:-Dcatalina.home=catalina-home -Dcatalina.base=catalina-home
// Then base
String base = System.getProperty(Globals.CATALINA_BASE_PROP);...
}
mian方法的作用:實例化BootStrap 初始化BootStrap daemon.load(args) daemon.start();
main{...
try {
bootstrap.init();
}...
try {
String command = "start";
if (command.equals("startd")) {...
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {...
}...
}
我們先分析init方法: 初始化commonLoader、catalinaLoader、sharedLoader,設置catalinaDaemon為Catalina實例
init(){...
initClassLoaders(){
try {
commonLoader = createClassLoader("common", null);//擺脫了類加載的雙親委派機制
if (commonLoader == null) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader = this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
...
};// 初始化commonLoader、catalinaLoader、sharedLoader
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);//此處實際是調用Catalina相關方法
catalinaDaemon = startupInstance;
}
load、start實際上是由Bootstrap反射調用Catalina的load、start,這一部分代碼將在下面的Catalina部分進行分析。load階段主要是通過讀取conf/server.xml或者server-embed.xml,實例化Server、Service、Connector、Engine、Host等組件,並調用Lifecycle#init()完成初始化動作,以及發出INITIALIZING、INITIALIZED事件
load() {...
initDirs();//初始化jmx的環境變量
initNaming();// Before digester - it may be needed 初始化命名空間
Digester digester = createStartDigester();//Create and execute our Digester 定義解析server.xml的配置,告訴Digester哪個xml標簽應該解析成什么類
...
// 如果還是加載不到xml,則直接return,省略部分代碼......
try {
inputSource.setByteStream(inputStream);
// 把Catalina作為一個頂級實例
digester.push(this);
// 解析過程會實例化各個組件,比如Server、Container、Connector等
digester.parse(inputSource);
} catch (SAXParseException spe) {
// 處理異常......
}
} finally {
// 關閉IO流......
}
// 給Server設置catalina信息
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// 調用Lifecycle的init階段
try {
getServer().init();//初始化Server,找到具體實現類方法
} catch (LifecycleException e) {
// ......
}
// ......
}
StandardServer.initInternal(){...//組件都繼承了LifeCycleBase,該類中的init會調用initInternal()由子類具體去實現
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();//初始化service
}...
}
StandardService.initInternal(){...
engine.init();//需要注意的是此處engine初始化后並沒有接着初始化子類host、context、wapper這幾個是在star生命周期初始啟動的
executor.init();
mapperListener.init();
connector.init();...//依次類推查看其他實現類的初始化
}
在connector->ProtocolHandler->AbstractProtocol調用init初始化的時候,還會去執行AbstractEndpoint的init方法,完成請求端口綁定、初始化NIO等操作
AbstractProtocol.init(){...
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
endpoint.init();...
}
AbstractEndpoint.init() throws Exception {
if (bindOnInit) {
bind(){
}
NioEndpoint.bind(){
if (!getUseInheritedChannel()) {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
serverSock.socket().bind(addr,getAcceptCount());
Engine初始化StandardEngine在init階段,需要獲取Realm(域)是用於對單個用戶進行身份驗證的底層安全領域的只讀外觀,並標識與這些用戶相關聯的安全角色。域可以在任何容器級別上附加,但是通常只附加到Context,或者更高級別的容器。