1. 程式人生 > 實用技巧 >Tomcat啟動流程簡析

Tomcat啟動流程簡析

Tomcat是一款我們平時開發過程中最常用到的Servlet容器。本系列部落格會記錄Tomcat的整體架構、主要元件、IO執行緒模型、請求在Tomcat內部的流轉過程以及一些Tomcat調優的相關知識。

力求達到以下幾個目的:

  • 更加熟悉Tomcat的工作機制,工作中遇到Tomcat相關問題能夠快速定位,從源頭來解決;
  • 對於一些高併發場景能夠對Tomcat進行調優;
  • 通過對Tomcat原始碼的分析,吸收一些Tomcat的設計的理念,應用到自己的軟體開發過程中。

1. Bootstrap啟動入口

在前面分析Tomcat啟動指令碼的過程中,我們最後發現startup.bat最後是通過呼叫Bootstrap這個類的main方法來啟動Tomcat的,所以先去看下Bootstrap這個類。

public static void main(String args[]) {

        synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
//建立Bootstrap物件,代用init方法
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
} try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
} if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
//一般情況下會進入這步,呼叫Bootstrap物件的load和start方法。
//將Catalina啟動設定成block模式
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}

上面的程式碼邏輯比較簡單,如果我們正常啟動tomcat,會順序執行Bootstrap物件的init()方法, daemon.setAwait(true)、daemon.load(args)和daemon.start()方法。我們先看下Bootstrap物件的init方法:

public void init() throws Exception {

        initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
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); catalinaDaemon = startupInstance; }

這個方法主要做了以下幾件事:

  • 建立commonLoader、catalinaLoader、sharedLoader類載入器(預設情況下這三個類載入器指向同一個物件。建議看看createClassLoader方法,裡面做的事情還挺多,比如裝載catalina.properties裡配置的目錄下的檔案和jar包,後兩個載入器的父載入器都是第一個,最後註冊了MBean,可以用於JVM監控該物件);
  • 例項化一個org.apache.catalina.startup.Catalina物件,並賦值給靜態成員catalinaDaemon,以sharedLoader作為入參通過反射呼叫該物件的setParentClassLoader方法。

執行完init()方法,就開始執行bootstrap物件的load和start方法;

 private void load(String[] arguments)
throws Exception { // Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param); }

呼叫catalinaDaemon物件的load方法,catalinaDaemon這個物件的型別是org.apache.catalina.startup.Catalina。strat方法也是類似的,最後都是呼叫Catalina的start方法。

總結下Bootstrap的啟動方法最主要乾的事情就是建立了Catalina物件,並呼叫它的load和start方法。

2. Catalina的load和start方法

第一節分析到Bootstrap會觸發呼叫Catalina的load和start方法。

     /**
* 從註釋可以看出這個方法的作用是建立一個Server例項
* Start a new server instance.
*/
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
//檢查臨時目錄
initDirs();
// Before digester - it may be needed
initNaming(); // Create and execute our Digester
Digester digester = createStartDigester(); InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), e);
}
}
} // This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
} if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
} try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
} getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); // Stream redirection
initStreams(); // Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
} long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}

將上面的程式碼精簡下:

Digester digester = createStartDigester();
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
getServer().setCatalina(this);
getServer().init();

做的事情就兩個:

  • 建立一個Digester物件(Digester物件的作用就是解析server.xml配置檔案,這邊會先載入conf/server.xml檔案,找不到的話會嘗試載入server-embed.xml這個配置檔案),解析完成後生成org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等一系列物件,這些物件從前到後前一個包含後一個物件的引用(一對一或一對多的關係)。最後將StandardServer賦值給Catalina物件的server屬性;如果你配置了聯結器元件共享的執行緒池,還會生成StandardThreadExecutor物件。
  • 第二件事就是呼叫StandardServer的init方法。

臨時總結下:Catalina的load方法的作用主要是解析conf/server.xml,生成StandardServer物件,再觸發StandardServer的init方法。

第一節中還分析到Bootstrap會觸發呼叫Catalina的start方法。那麼我們看看start方法中幹了什麼。

/**
* Start a new server instance.
*/
public void start() { if (getServer() == null) {
load();
} if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
} long t1 = System.nanoTime(); // Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
} long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
} // Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
} if (await) {
await();
stop();
}
}

這段程式碼最主要的作用就是呼叫StandardServer物件的start方法。

總結下:Catalina物件的laod和start方法的作用是解析conf/server.xml,生成StandardServer物件,再觸發StandardServer的init方法和start方法。

到這邊為止我們可以看到Tomcat的啟動流程還是很清晰的,下面繼續看StandardServer的init方法和start到底幹了些什麼。

3.StandardServer的init和start方法

通過尋找StandardServer的init方法,我們發現StandardServer本身沒有實現這個方法,這個方法是它從父類LifecycleBase中繼承過來的:

@Override
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
} try {
//釋出初始化容器時間,對應的listener做相應處理
setStateInternal(LifecycleState.INITIALIZING, null, false);
//呼叫子類的initInternal()
initInternal();
//釋出容器已經初始化事件,對應的listener做相應處理
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}

所以呼叫StandardServer的init方法,其實是促發了容器初始化事件釋出,然後又調到了StandardServer的initInternal方法。那麼我們看看StandardServer的start方法的邏輯是什麼。

程式碼點進去,發現StandardServer的start方法也是調的父類LifecycleBase中的方法。

@Override
public final synchronized void start() throws LifecycleException { if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) { if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
} return;
} if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
} try {
//釋出事件
setStateInternal(LifecycleState.STARTING_PREP, null, false);
//呼叫子類的startInternal
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
//釋出容器啟動事件
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}

從以上init和start方法的定義可以看到這兩個方法最終將會呼叫StandardServer中定義的initInternal和startInternal。

先來看initInternal方法

protected void initInternal() throws LifecycleException {

        super.initInternal();

        // Register global String cache
// Note although the cache is global, if there are multiple Servers
// present in the JVM (may happen when embedding) then the same cache
// will be registered under multiple names
onameStringCache = register(new StringCache(), "type=StringCache"); // Register the MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory"); // Register the naming resources
globalNamingResources.init(); // Populate the extension validator with JARs from common and shared
// class loaders
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}

重點程式碼在最後,迴圈呼叫了Service元件的init方法。

再來看StandardServer的startInternal方法

@Override
protected void startInternal() throws LifecycleException { fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING); globalNamingResources.start(); // Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}

也是迴圈呼叫了Service元件的start方法。這邊的Service元件就是在從conf/server.xml中解析出來的StandardService物件,檢視下這個類的繼承體系:

LifecycleBase (org.apache.catalina.util)
LifecycleMBeanBase (org.apache.catalina.util)
StandardService (org.apache.catalina.core)

我們發現這個類繼承體系和StandardServer是一樣的。其實我們再觀察的仔細一點會發現從conf/server.xml解析胡來的類的繼承體系都是一樣的。所以我們呼叫這些類的init和start方法最後還是會呼叫到他們的initInternal和startInternal方法。

4. StandardService的initInternal和startInternal方法

先看下StandardService的initInternal方法

@Override
protected void initInternal() throws LifecycleException { super.initInternal();
//呼叫engine的initInternal方法,這個方法中也沒做特別重要的操作,只是做了一個getReal操作
if (engine != null) {
engine.init();
} //StandardThreadExecutor的initInternal方法中沒沒幹什麼事情
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
} // Initialize mapper listener
//這步也沒做什麼重要操作
mapperListener.init(); // Initialize our defined Connectors
// 聯結器主鍵的初始化,主要是檢查聯結器的protocolHandler的主鍵,並將其初始化.
synchronized (connectorsLock) {
for (Connector connector : connectors) {
connector.init();
}
}
}

看了上面的程式碼,覺得Tomcat原始碼邏輯還是很清晰的。之前在分析Tomcat元件的文章中講到Service元件是有Connector元件、engine元件和一個可選的執行緒池組成。上面的程式碼中正好對應了這三個元件的初始化話。

Connector元件和engine元件的初始化又會觸發他們各自子元件的初始化,所以StandardService的initInternal方法會觸發Tomcat下各類元件的初始化。這邊大致記錄下各個元件初始化話的順序:

  • engine元件初始化:engine元件初始化沒做什麼特別的操作,也沒觸發它的子元件(Host、Context和Wrapper元件的初始化),所以這步比較簡單;
  • Executor元件的初始化:沒有觸發其他元件初始化;
  • Mapper元件初始化:mapper元件初始化也沒幹什麼重要的操作,也沒觸發其他子元件初始化;
  • Connector元件初始化:檢查聯結器的protocolHandler的子元件,並觸發其初始化
  • ProtocolHandler元件初始化:觸發Endpoint元件初始化,Endpoint類才是接收轉化請求的真正的類;

然後再看StandardService的startInternal方法

protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING); // Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start();
}
} synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
} mapperListener.start(); // Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}

邏輯依然很清楚,StandardService會依次觸發各個子元件的start方法。

  • Engine元件的start:Engine元件的start方法組要作用還是觸發了Host元件的start方法,具體程式碼見

    protected synchronized void startInternal() throws LifecycleException {  
    
        // Start our subordinate components, if any
    if ((loader != null) && (loader instanceof Lifecycle))
    ((Lifecycle) loader).start();
    logger = null;
    getLogger();
    if ((manager != null) && (manager instanceof Lifecycle))
    ((Lifecycle) manager).start();
    if ((cluster != null) && (cluster instanceof Lifecycle))
    ((Lifecycle) cluster).start();
    Realm realm = getRealmInternal();
    if ((realm != null) && (realm instanceof Lifecycle))
    ((Lifecycle) realm).start();
    if ((resources != null) && (resources instanceof Lifecycle))
    ((Lifecycle) resources).start(); // 找出Engine的子容器,也就是Host容器
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<Future<Void>>();
    //利用執行緒池呼叫Host的start方法
    for (int i = 0; i < children.length; i++) {
    results.add(startStopExecutor.submit(new StartChild(children[i])));
    } boolean fail = false;
    for (Future<Void> result : results) {
    try {
    result.get();
    } catch (Exception e) {
    log.error(sm.getString("containerBase.threadedStartFailed"), e);
    fail = true;
    } }
    if (fail) {
    throw new LifecycleException(
    sm.getString("containerBase.threadedStartFailed"));
    } // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle)
    ((Lifecycle) pipeline).start();
    setState(LifecycleState.STARTING);
    // Start our thread
    threadStart(); }
  • Host元件的start:經過前面介紹,我們知道Host元件的start方法最後還是會呼叫自己startInternal方法;

  • Context元件的start:觸發Wrapper的start,載入filter、Servlet等;

  • Wrapper元件的start:

這邊我們重點看下StandardContext的startInternal,這個方法乾的事情比較多:

 protected synchronized void startInternal() throws LifecycleException {

        if(log.isDebugEnabled())
log.debug("Starting " + getBaseName()); // Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
} setConfigured(false);
boolean ok = true; // Currently this is effectively a NO-OP but needs to be called to
// ensure the NamingResources follows the correct lifecycle
if (namingResources != null) {
namingResources.start();
} // Post work directory
postWorkDirectory(); // Add missing components as necessary
if (getResources() == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources"); try {
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
resourcesStart();
} if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
} // An explicit cookie processor hasn't been specified; use the default
if (cookieProcessor == null) {
cookieProcessor = new Rfc6265CookieProcessor();
} // Initialize character set mapper
getCharsetMapper(); // Validate required extensions
boolean dependencyCheck = true;
try {
dependencyCheck = ExtensionValidator.validateApplication
(getResources(), this);
} catch (IOException ioe) {
log.error(sm.getString("standardContext.extensionValidationError"), ioe);
dependencyCheck = false;
} if (!dependencyCheck) {
// do not make application available if dependency check fails
ok = false;
} // Reading the "catalina.useNaming" environment variable
String useNamingProperty = System.getProperty("catalina.useNaming");
if ((useNamingProperty != null)
&& (useNamingProperty.equals("false"))) {
useNaming = false;
} if (ok && isUseNaming()) {
if (getNamingContextListener() == null) {
NamingContextListener ncl = new NamingContextListener();
ncl.setName(getNamingContextName());
ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
addLifecycleListener(ncl);
setNamingContextListener(ncl);
}
} // Standard container startup
if (log.isDebugEnabled())
log.debug("Processing standard container startup"); // Binding thread
ClassLoader oldCCL = bindThread(); try {
if (ok) {
// Start our subordinate components, if any
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
} // since the loader just started, the webapp classloader is now
// created.
setClassLoaderProperty("clearReferencesRmiTargets",
getClearReferencesRmiTargets());
setClassLoaderProperty("clearReferencesStopThreads",
getClearReferencesStopThreads());
setClassLoaderProperty("clearReferencesStopTimerThreads",
getClearReferencesStopTimerThreads());
setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
getClearReferencesHttpClientKeepAliveThread());
setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
getClearReferencesObjectStreamClassCaches());
setClassLoaderProperty("skipMemoryLeakChecksOnJvmShutdown",
getSkipMemoryLeakChecksOnJvmShutdown()); // By calling unbindThread and bindThread in a row, we setup the
// current Thread CCL to be the webapp classloader
unbindThread(oldCCL);
oldCCL = bindThread(); // Initialize logger again. Other components might have used it
// too early, so it should be reset.
logger = null;
getLogger(); Realm realm = getRealmInternal();
if(null != realm) {
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
} // Place the CredentialHandler into the ServletContext so
// applications can have access to it. Wrap it in a "safe"
// handler so application's can't modify it.
CredentialHandler safeHandler = new CredentialHandler() {
@Override
public boolean matches(String inputCredentials, String storedCredentials) {
return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
} @Override
public String mutate(String inputCredentials) {
return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
}
};
context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
} // Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); // Start our child containers, if not already started
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
} // Start the Valves in our pipeline (including the basic),
// if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
} // Acquire clustered manager
Manager contextManager = null;
Manager manager = getManager();
if (manager == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.cluster.noManager",
Boolean.valueOf((getCluster() != null)),
Boolean.valueOf(distributable)));
}
if ( (getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error("standardContext.clusterFail", ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
} // Configure default manager if none was specified
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager",
contextManager.getClass().getName()));
}
setManager(contextManager);
} if (manager!=null && (getCluster() != null) && distributable) {
//let the cluster know that there is a context that is distributable
//and that it has its own manager
getCluster().registerManager(manager);
}
} if (!getConfigured()) {
log.error(sm.getString("standardContext.configurationFail"));
ok = false;
} // We put the resources into the servlet context
if (ok)
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources()); if (ok ) {
if (getInstanceManager() == null) {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map<String, Map<String, String>> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
setInstanceManager(new DefaultInstanceManager(context,
injectionMap, this, this.getClass().getClassLoader()));
}
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
} // Create context attributes that will be required
if (ok) {
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
} // Set up the context init params
mergeParameters(); // Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
} // Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
} // Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
} try {
// Start manager
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
} // Configure and call application filters
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
} // Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
} // Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
} // Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
} startTime=System.currentTimeMillis(); // Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
} // The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
getResources().gc(); // Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
} else {
setState(LifecycleState.STARTING);
}
}

上面的程式碼有4處重點:呼叫ServletContainerInitializers、啟用Listener、啟用Filter和啟用startup的Servlet。這個和我們平時對Tomcat啟動流程的認知是一致的。

到這裡整個Container元件(包括Engine、Host、Context和Wrapper元件)的start方法呼叫就結束了。接下來是Connector和Mapper元件的start。

//MapperListenner的startInternal
public void startInternal() throws LifecycleException { setState(LifecycleState.STARTING); Engine engine = service.getContainer();
if (engine == null) {
return;
} findDefaultHost(); addListeners(engine); Container[] conHosts = engine.findChildren();
for (Container conHost : conHosts) {
Host host = (Host) conHost;
if (!LifecycleState.NEW.equals(host.getState())) {
// Registering the host will register the context and wrappers
registerHost(host);
}
}
}

以上方法的主要作用是將Host元件和域名對映起來。

最後看下Connector元件的start:

protected void startInternal() throws LifecycleException {

        // Validate settings before starting
if (getPortWithOffset() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
} setState(LifecycleState.STARTING); try {
//促發protocolHandler元件的start,最後促發endpoint元件的start
//觸發endpoint時會建立exceutor執行緒池,預設的話核心執行緒數10,最大執行緒數200
//建立poller執行緒,最大是2個執行緒,如果你機器cpu的核數小於2的話就建立1個
//建立accetpor執行緒,預設是1個(可以看看Acceptor這個類的原始碼,瞭解下怎麼接收請求的)
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}

通過以上一些列複雜的呼叫過程,最終執行完所有在server.xml裡配置的節點的實現類中initInternal和startInternal方法。上面提到的org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等元件的這兩個方法都會呼叫到。

至此,Tomcat已經能開始響應瀏覽器發過來的請求了。至於具體的Tomcat響應請求流程會在後續部落格中介紹。

5. 總結

看了整個啟動流程,雖然邏輯是比較清楚的,但是流程比較上,所以有必要做下總結:

  • step1:Bootstrap作為整個Tomcat主啟動類,最主要的功能是建立Catalina物件,並呼叫它的load和start方法;

  • step2:Catalina的load方法的作用主要是解析conf/server.xml,生成StandardServer物件(此時生成StandardServer物件中已經包含了各種子元件,比如StandardService、StandardEngine等),再觸發StandardServer的init方法;Catalina的start方法又觸發了StandardServer的start方法;

  • step3:StandardServer的init方法和start方法會依次觸發各個子元件的initInternal和startInternal方法。大致的觸發順序是:

    Engine元件的initInternal(這邊要注意的是Engine元件並沒有觸發它的子元件Host、Context和Wrapper的initInternal)-->Executor元件initInternal(處理請求的工作執行緒池)-->Mapper元件初始化(mapper元件初始化也沒幹什麼重要的操作,也沒觸發其他子元件初始化)-->Connector元件初始化(檢查聯結器的protocolHandler的子元件,並觸發其初始化)-->ProtocolHandler元件初始化(觸發Endpoint元件初始化)​

​ Engine元件的startInternal(主要作用是觸發Host元件的start)-->Host元件的startInternal(主要作用是觸發Context元件的startInternal)-->Contextz元件的startInternal(載入呼叫ServletContainerInitializers、載入Listener、載入filtr和startup的Servlet,並且觸發Wrapper元件的startInternal)-->Wrapper元件的startInternal(載入對映Servlet)-->Mapper元件的startInternal(將域名和Host元件對映起來)-->Connector元件的startInternal(protocolHandler元件的start,最後促發endpoint元件的start)