整体概览
我们先有个宏观的概念,观察一下核心启动流程。
这里要先介绍Tomcat的两个核心接口:Lifecycle
和LifecycleListener
,因为这两个接口所对应的事件监听机制
贯穿启动流程的始终。
- Lifecycle
该接口提供了tomcat组件生命周期中的方法及其对应的监听事件(EVENT),其主要结构如下:
在Tomcat启动过程中,不同的组件在不同的阶段触发不同的event,然后由组件对应的listener捕获和处理。
下面看看我们关注的哪些组件实现了该接口:
- LifecycleListener
该接口提供了处理监听到的事件的处理方法。
/**
* Interface defining a listener for significant events (including "component
* start" and "component stop" generated by a component that implements the
* Lifecycle interface. The listener will be fired after the associated state
* change has taken place.
*
* @author Craig R. McClanahan
*/
public interface LifecycleListener {
/**
* Acknowledge the occurrence of the specified event.
*
* @param event LifecycleEvent that has occurred
*/
public void lifecycleEvent(LifecycleEvent event);
}
下面看看我们比较关注的实现了该接口的对象:
后面在具体介绍启动流程的时候,会对这些对象一一解析。
启动流程解析
Tomcat的启动引导类Boostrap,先加载自定义的类加载器,然后通过反射加载Catalina,最终启动服务。
init阶段:
- Catalina的loadServer过程
该过程通过调用Catalina的load方法,读取配置文件(conf/server.xml
),利用SAX技术解析文件,利用反射生成对应的Server、Listener、GlobalNamingResources、Service、Engine、Host等对象,其中Server、Service、Engine、Host对象的默认实现对象分别为:StandardServer、StandardService、StandardEngine、StandardHost。
默认的配置文件conf/server.xml
文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
- StandardServer的initService过程
- 该过程通过调用StandService的initInternal方法,分别init其中的Engine、Connector等组件。
- Connector中init的过程会调用其中PreHandler中的init方法,最终会落实到PreHandler中EndPoint的init方法。initEndPoint过程主要是调用bind方法来绑定服务器监听端口。
Connector中init过程:
PreHandler中的init过程:
EndPoint中的init过程:
start阶段:
这里我们着重看下StandardHost中的默认的start方法。
- 寻找对应的Context子容器,调用对应的Context的start方法,默认Host中无Context子容器。
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
- Host发布START_EVENT,提供给HostConfig接收,然后进行解析。
setState(LifecycleState.STARTING);
- HostConfig中会创建对应的Context子容器,加载与Context相对应的ContextConfig,最后调用Context的start方法。其中,ContextConfig用来处理Context发出的event。
HostConfig中deployXXX方法的部分代码:
// 创建对应的context
Class<?> clazz = Class.forName(host.getConfigClass());
// 添加对应LifeCycleListener
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
context.addLifecycleListener(listener);
-------------分割线-------------
// 会调用context的start方法
host.addChild(context);
host.addChild(context)
最终会调用父类的addChildInternal
方法,调用context的start方法:
private void addChildInternal(Container child) {
if( log.isDebugEnabled() )
log.debug("Add child " + child + " " + this);
synchronized(children) {
if (children.get(child.getName()) != null)
throw new IllegalArgumentException("addChild: Child name '" +
child.getName() +
"' is not unique");
child.setParent(this); // May throw IAE
children.put(child.getName(), child);
}
// Start child
// Don't do this inside sync block - start can be a slow process and
// locking the children object can cause problems elsewhere
try {
if ((getState().isAvailable() ||
LifecycleState.STARTING_PREP.equals(getState())) &&
startChildren) {
child.start();
}
} catch (LifecycleException e) {
log.error("ContainerBase.addChild: start: ", e);
throw new IllegalStateException("ContainerBase.addChild: start: " + e);
} finally {
fireContainerEvent(ADD_CHILD_EVENT, child);
}
}
Connector的start过程会先去调用PreHandler的start方法,其中PreHandler的start方法会调用EndPoint中的start方法。startEndPoint后,服务器开启acceptor和poller线程,acceptor用于接受请求,poller用于处理和传递请求。
至此,Tomcat的启动大致流程基本已经结束。其实,关于Context的start方法和EndPoint中的start方法还有很大的一部分,这里省略掉先不讲了,等后续有时间可以单独拿出来分析一波。