tomcat啟動過程
tomcat中最頂層的容器叫server,代表伺服器,server中起碼有一個service,提供服務。service中包含兩部分:connector和Container。connector處理連接配接相關的功能,包括socket與request、response的轉化。Container負責封裝管理servlet以及處理request請求。一個tomcat隻有一個server,一個server包含多個service,一個service隻有一個Container,但是可以有多個connector(使用相同協定或者不同協定提供不同端口的連接配接),tomcat結構如圖所示:

tomcat的Server由org.apache.catalina.startup.Catalina來管理,Catalina是整個tomcat的管理類,有load、start、stop三個方法來管理整個伺服器的生命周期,load方法用于根據conf/server.xml檔案建立server并根據init方法進行初始化;start方法啟動伺服器,stop方法停止伺服器;start和stop方法分别調用server的start和stop方法,load調用server的init方法,然後這三個方法在按容器結構逐層調用對應方法。例如server的start方法會調用service方法,service的start方法又會調用connectors和container的start方法,然後整個tomcat服務就啟動了。init方法和start方法也類似,這就是tomcat的生命周期。Catalina還有個await方法,Catalina的await方法調用了server的await方法,使的主線程不會退出。
tomcat的main方法不在Catalina中,而是在org.apache.catalina.startup.Bootstrap中,Bootstrap作用類似于CatalinaAdaptor,具體處理過程使用Catalina完成,這樣啟動入口和管理類可以分開,可以友善的創造出多種啟動方式,每種方式隻要設計對應的CatalinaAdaptor即可。
Bootstrap的啟動過程
bootstrap是tomcat的入口,啟動tomcat即是調用bootstrap的main方法:
public static void main(String[] args)
{
if (daemon == null)
{
Bootstrap bootstrap = new Bootstrap();
try
{
bootstrap.init();
}
catch (Throwable t)
{
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
}
else
{
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try
{
String command = "start";
if (args.length > ) {
command = args[(args.length - )];
}
if (command.equals("startd"))
{
args[(args.length - )] = "start";
daemon.load(args);
daemon.start();
}
else if (command.equals("stopd"))
{
args[(args.length - )] = "stop";
daemon.stop();
}
else if (command.equals("start"))
{
daemon.setAwait(true);
daemon.load(args);
daemon.start();
}
else if (command.equals("stop"))
{
daemon.stopServer(args);
}
else if (command.equals("configtest"))
{
daemon.load(args);
if (null == daemon.getServer()) {
System.exit();
}
System.exit();
}
else
{
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
}
catch (Throwable t)
{
if (((t instanceof InvocationTargetException)) && (t.getCause() != null)) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit();
}
}
main方法有兩部分内容:首先建立Bootstrap,并執行init方法;然後處理main的參數,如果參數為空,預設執行start方法。
在init中初始化了ClassLoader,并使用Classloader建立了Catalina執行個體,指派給catalinaDaemon,後面操作都使用catalinaDaemon來執行。
對于start指令處理調用了三個方法:setAwait(true)、load(args)和start(),這三個方法内部都調用了Catalina的相應方法,不過是通過反射來調用的。start方法(另外兩個也類似):
public void start() throws Exception {
if (this.catalinaDaemon == null) {
init();
}
Method method = this.catalinaDaemon.getClass().getMethod("start", (Class[])null);
method.invoke(this.catalinaDaemon, (Object[])null);
}
首先判斷catalinaDaemon有沒有初始化,沒有的話進行init,然後通過Method進行反射調用Catalina的start方法。
Catalina的啟動過程
Catalina主要調用setAwait、load和start來完成啟動。setAwait用于設定Server啟動完成後時候進入等待狀态;load用于加載配置檔案,并初始化server;start用于啟動伺服器。
setAwait方法如下:
public void setAwait(boolean b)
{
this.await = b;
}
setAwait方法就是設定await的值,start方法啟動伺服器後會使用它的值來判斷是否進入等待狀态。
Catalina的load方法根據conf/server.xml來建立server方法,并進行指派,然後調用server的init方法:
public void load()
{
long t1 = System.nanoTime();
initDirs();
initNaming();
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", new Object[] { 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", new Object[] { getConfigFile() }), e);
}
}
}
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", new Object[] { "server-embed.xml" }), e);
}
}
}
if ((inputStream == null) || (inputSource == null))
{
if (file == null)
{
log.warn(sm.getString("catalina.configFail", new Object[] { getConfigFile() + "] or [server-embed.xml]" }));
}
else
{
log.warn(sm.getString("catalina.configFail", new Object[] { 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;
}
if (inputStream != null) {
try
{
inputStream.close();
}
catch (IOException localIOException3) {}
}
getServer().setCatalina(this);
}
finally
{
if (inputStream != null) {
try
{
inputStream.close();
}
catch (IOException localIOException4) {}
}
}
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
initStreams();
try
{
getServer().init();
}
catch (LifecycleException e)
{
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new Error(e);
}
log.error("Catalina.start", e);
}
long t2 = System.nanoTime();
if (log.isInfoEnabled()) {
log.info("Initialization processed in " + (t2 - t1) / L + " ms");
}
}
Catalina的start方法主要調用server的start方法,并根據await來判斷是否讓程式進入等待狀态:
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();
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) / L + " ms");
}
if (this.useShutdownHook)
{
if (this.shutdownHook == null) {
this.shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
LogManager logManager = LogManager.getLogManager();
if ((logManager instanceof ClassLoaderLogManager)) {
((ClassLoaderLogManager)logManager).setUseShutdownHook(false);
}
}
if (this.await)
{
await();
stop();
}
}
先判斷Server是否已經存在,如果不存在調用load來初始化Server,然後調用Server的start方法來啟動伺服器。并且注冊關閉鈎子并根據await判斷程式是否進入等待狀态。進入等待狀态會調用await和stop兩個方法,await調用server的await,server内部的await方法會執行while循環,此時程式就停在了await方法,當await退出時,就會執行stop方法關閉伺服器。
Server啟動過程
Server接口中提供addService(Service service)和removeService(Service service)來添加和删除Service,Server的start和init方法分别循環調用所有Service的init和start方法來啟動所有的Service。
Server的預設實作是org.apache.catalina.core.StandardServer,StandardServer繼承于LifecycleMBeanBase,lifecycleMbeanBase繼承LifecycleBase,init和start方法定義在LifecycleBase中,LifecycleBase裡的init和start調用initInternal和startInternal方法,這兩個都是模闆方法,由具體子類實作,是以StandardServer的init和start方法執行時會調用自己的initInternal和startInternal方法。StandardServer的initInternal和startInternal方法循環調用了每個service的start和init方法:
protected void startInternal() throws LifecycleException {
fireLifecycleEvent("configure_start", null);
setState(LifecycleState.STARTING);
this.globalNamingResources.start();
synchronized (this.servicesLock)
{
for (int i = ; i < this.services.length; i++) {
this.services[i].start();
}
}
}
protected void initInternal() throws LifecycleException {
super.initInternal();
this.onameStringCache = register(new StringCache(), "type=StringCache");
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
this.onameMBeanFactory = register(factory, "type=MBeanFactory");
this.globalNamingResources.init();
if (getCatalina() != null)
{
ClassLoader cl = getCatalina().getParentClassLoader();
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 localURISyntaxException) {}catch (IOException localIOException) {}
}
}
}
cl = cl.getParent();
}
}
for (int i = ; i < this.services.length; i++) {
this.services[i].init();
}
}
StandardServer還實作了await方法,Catalina就是調用它使伺服器進入等待狀态:
public void await()
{
if (this.port == -) {
return;
}
if (this.port == -)
{
try
{
this.awaitThread = Thread.currentThread();
while (!this.stopAwait) {
try
{
Thread.sleep(L);
}
catch (InterruptedException localInterruptedException) {}
}
}
finally
{
this.awaitThread = null;
}
return;
}
try
{
this.awaitSocket = new ServerSocket(this.port, , InetAddress.getByName(this.address));
}
catch (IOException e)
{
log.error("StandardServer.await: create[" + this.address + ":" + this.port + "]: ", e);
return;
}
try
{
this.awaitThread = Thread.currentThread();
while (!this.stopAwait)
{
ServerSocket serverSocket = this.awaitSocket;
if (serverSocket == null) {
break;
}
Socket socket = null;
StringBuilder command = new StringBuilder();
try
{
long acceptStartTime = System.currentTimeMillis();
try
{
socket = serverSocket.accept();
socket.setSoTimeout();
stream = socket.getInputStream();
}
catch (SocketTimeoutException ste)
{
InputStream stream;
log.warn(sm.getString("standardServer.accept.timeout", new Object[] { Long.valueOf(System.currentTimeMillis() - acceptStartTime) }), ste);
try
{
if (socket != null) {
socket.close();
}
}
catch (IOException localIOException1) {}
continue;
}
catch (AccessControlException ace)
{
log.warn("StandardServer.accept security exception: " + ace.getMessage(), ace);
try
{
if (socket != null) {
socket.close();
}
}
catch (IOException localIOException2) {}
continue;
}
catch (IOException e)
{
if (this.stopAwait) {
try
{
if (socket != null) {
socket.close();
}
}
catch (IOException localIOException3) {}
}
log.error("StandardServer.await: accept: ", e);
}
InputStream stream;
int expected = ;
while (expected < this.shutdown.length())
{
if (this.random == null) {
this.random = new Random();
}
expected += this.random.nextInt() % ;
}
while (expected > )
{
int ch = -;
try
{
ch = stream.read();
}
catch (IOException e)
{
log.warn("StandardServer.await: read: ", e);
ch = -;
}
if ((ch < ) || (ch == )) {
break;
}
command.append((char)ch);
expected--;
}
try
{
if (socket != null) {
socket.close();
}
}
catch (IOException localIOException5) {}
match = command.toString().equals(this.shutdown);
}
finally
{
try
{
if (socket != null) {
socket.close();
}
}
catch (IOException localIOException6) {}
}
boolean match;
if (match)
{
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
}
log.warn("StandardServer.await: Invalid command '" + command.toString() + "' received");
}
}
finally
{
ServerSocket serverSocket;
ServerSocket serverSocket = this.awaitSocket;
this.awaitThread = null;
this.awaitSocket = null;
if (serverSocket != null) {
try
{
serverSocket.close();
}
catch (IOException localIOException8) {}
}
}
}
await的大緻邏輯為先判斷端口号port:
- port為-2,直接退出
- port為-1,會進入while(!stopAwait)的循環,并且内部沒有break語句,stopAwait隻有調用了stop才會變成true,是以此情況下隻有外部調用stop才會退出循環
- port為其他值,會進入while(!stopAwait)循環,同時在port所在端口啟動serverSocket監聽關閉指令,如果接受到了則使用break調出循環。
這裡的port為在配置檔案中設定的shutdown端口,如果不通過網絡指令來關閉tomcat,将port設定為-1即可。await對于接受的參數進行了一定處理,ASCII碼小于32及其後面的部分直接丢棄。
Service的啟動
Service的預設實作為org.apache.catalina.core.StandardService,StandardService也繼承LifecycleMbeanBase,是以init和start也會調用initInternal和startInternal:
protected void initInternal()
throws LifecycleException
{
super.initInternal();
if (this.container != null) {
this.container.init();
}
for (Executor executor : findExecutors())
{
if ((executor instanceof JmxEnabled)) {
((JmxEnabled)executor).setDomain(getDomain());
}
executor.init();
}
this.mapperListener.init();
synchronized (this.connectorsLock)
{
for (Connector connector : this.connectors) {
try
{
connector.init();
}
catch (Exception e)
{
String message = sm.getString("standardService.connector.initFailed", new Object[] { connector });
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new LifecycleException(message);
}
}
}
}
}
protected void startInternal()
throws LifecycleException
{
if (log.isInfoEnabled()) {
log.info(sm.getString("standardService.start.name", new Object[] { this.name }));
}
setState(LifecycleState.STARTING);
if (this.container != null) {
synchronized (this.container)
{
this.container.start();
}
}
synchronized (this.executors)
{
for (Executor executor : this.executors) {
executor.start();
}
}
this.mapperListener.start();
synchronized (this.connectorsLock)
{
for (Connector connector : this.connectors) {
try
{
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
catch (Exception e)
{
log.error(sm.getString("standardService.connector.startFailed", new Object[] { connector }), e);
}
}
}
}
StandardService中的initInternal和startInternal方法主要調用container、executors、mapperListener、connectors的init和start方法。MapperListener是Mapper的監聽器,用來監控container的變化,executor是用來在connector管理線程的線程池,在server.xml中有參考的用法,隻不過是被注釋的:
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
這樣整個tomcat就完成了啟動,tomcat的整個啟動流程如下圖: