tomcat的入口位于Bootstrap.java,整个启动过程大致可以分为三步,
-
init 初始化catalina
-
load load参数
-
start 启动catalina
init过程
init过程首先会初始化一个Bootstrap对象,在Bootstrap的静态初始化块中会初始化catalinaHomeFile、catalinaBaseFile这两个全局变量,通常情况下这两个变量都是tomcat所在的目录。然后调用Bootstrap的init方法完成具体的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.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。
- 通过类加载器catalinaLoader加载org.apache.catalina.startup.Catalina,通过反射初始化catalina对象
- 调用catalina的setParentClassLoader
- 设置catalinaDaemon为初始化好的catalina对象
load过程
load过程主要调用catalina的load方法完成。 load方法的主要过程分为如下的几步,
1.初始化digester
这个digester用于解析tomcat的server.xml文件
Digester digester = createStartDigester()
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
。。。。。。
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addObjectCreate("Server/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
。。。。。。
return (digester);
}
2.解析server.xml
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;
}
Digester继承org.xml.sax.helpers.DefaultHandler,并且实现了startDocument, endDocument, startElement, endElement等用于解析xml的方法。下面拿startElement作为例子,
@Override
public void startElement(String namespaceURI, String localName, String qName, Attributes list)
throws SAXException {
boolean debug = log.isDebugEnabled();
if (saxLog.isDebugEnabled()) {
saxLog.debug("startElement(" + namespaceURI + "," + localName + "," + qName + ")");
}
。。。。。。
// Fire "begin" events for all relevant rules
List<Rule> rules = getRules().match(namespaceURI, match);
matches.push(rules);
if ((rules != null) && (rules.size() > 0)) {
for (int i = 0; i < rules.size(); i++) {
try {
Rule rule = rules.get(i);
if (debug) {
log.debug(" Fire begin() for " + rule);
}
rule.begin(namespaceURI, name, list);
} catch (Exception e) {
log.error("Begin event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Begin event threw error", e);
throw e;
}
}
} else {
if (debug) {
log.debug(" No rules found matching '" + match + "'.");
}
}
}
这里主要是根据输入的namespaceURI和match获取对应的rules,然后遍历rules中的每一个rule,调用begin方法进行处理。而这里的rule就是在createStartDigester中注册的。比如下面的代码
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
注册了三个rule,分别是ObjectCreateRule、SetPropertiesRule、SetNextRule,第一个rule用于创建StandardServer对象,第二个rule用于设置properties,第三个rule用于调用Catalina的setServer方法设置server为上面创建的StandardServer对象。
digeser通过调用parse方法进入server.xml的解析,并且初始化catalina中的各个属性。
3.设置重定向
protected void initStreams() {
// Replace System.out and System.err with a custom PrintStream
System.setOut(new SystemLogHandler(System.out));
System.setErr(new SystemLogHandler(System.err));
}
tomcat对标准的输入输出做了封装,并且重新设置System.out和System.err
4.初始化server
// 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();
}
上面的代码可以划分为以下几步, 1. 设置Mbean供jmx查询 2. 调用catalina的classloader加载lib中的jar的manifest 3. 获取server中的Service,并且依次初始化
service的初始化依次调用engine、connector的init方法。而connector中由于定义了端口以及协议等信息,所以在connector初始化的时候还涉及到了端口绑定等一系列操作,如果此时端口被占用则会抛出异常。connector相关的东西这里暂时不展开,后续再做详细的介绍。
到这里系统的初始化过程基本结束,下面就要进入start阶段。而此时打印出的日志如下所示,
四月 10, 2017 12:39:34 上午 org.apache.catalina.startup.VersionLoggerListener log 信息: CATALINA_BASE: D:\源码阅读\apache-tomcat-8.5.11-src 四月 10, 2017 12:39:34 上午 org.apache.catalina.startup.VersionLoggerListener log 信息: CATALINA_HOME: D:\源码阅读\apache-tomcat-8.5.11-src 四月 10, 2017 12:39:34 上午 org.apache.catalina.startup.VersionLoggerListener log 信息: Command line argument: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:58750,suspend=y,server=n 四月 10, 2017 12:39:34 上午 org.apache.catalina.startup.VersionLoggerListener log 信息: Command line argument: -Dfile.encoding=UTF-8 四月 10, 2017 12:39:34 上午 org.apache.catalina.core.AprLifecycleListener lifecycleEvent 信息: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\Program Files\Java\jdk1.8.0_101\bin;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Go\bin;C:\Program Files\Java\jdk1.8.0_101\bin;C:\Program Files\Java\jdk1.8.0_101\jre\bin;C:\Program Files (x86)\Git\bin;C:\Users\zblacker\AppData\Local\Microsoft\WindowsApps;C:\Users\zblacker\AppData\Local\atom\bin;C:\Program Files (x86)\Microsoft VS Code\bin;. 四月 10, 2017 12:39:37 上午 org.apache.coyote.AbstractProtocol init 信息: Initializing ProtocolHandler [“http-nio-8080”] 四月 10, 2017 12:39:37 上午 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector 信息: Using a shared selector for servlet write/read 四月 10, 2017 12:39:38 上午 org.apache.coyote.AbstractProtocol init 信息: Initializing ProtocolHandler [“ajp-nio-8009”] 四月 10, 2017 12:39:39 上午 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector 信息: Using a shared selector for servlet write/read 四月 10, 2017 12:39:40 上午 org.apache.catalina.startup.Catalina load 信息: Initialization processed in 6422 ms
start过程
start的过程主要调用StandardServer的start方法,而start方法的主要流程如下所示,
@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的start方法又依次会调用engine的start以及各个connector的start方法。首先来看一下engine的start方法,
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// Standard container startup
super.startInternal();
}
engine的start方法仅仅是调用父类的start方法,父类的这个方法比较长,其中最重要的部分是
// 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])));
}
boolean fail = false;
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
上面的代码会寻找engine里面注册的子容器,并且调用线程池启动这些容器,此时一般情况下会有一个child container,该child container对应的实现是StandardHost。StandardHost的start方法最终也会进入到父类也就是ContainerBase的start方法中,与engine不同的是StandardHost中没有注册child container,因此上面的代码很快就会执行完毕进入到下面的代码,
setState(LifecycleState.STARTING);
将StandardHost的生命状态设置为STARTING,
this.state = state;
String lifecycleEvent = state.getLifecycleEvent();
if (lifecycleEvent != null) {
fireLifecycleEvent(lifecycleEvent, data);
}
并且根据lifecycleEvent的状态机获取下一个状态为start,同时根据状态start调用在StandardHost中注册的生命周期监听器,通常情况下这里的生命周期监听器包括HostConfig,该监听器根据传入的生命周期状态进入到对应的处理函数中,
// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
start()方法经过一系列处理后会进入到deployApps()方法进行应用的加载,
protected void deployApps() {
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
appBase对应的就是配置的应用存放的目录,deployWARs会逐个加载appBase目录下的war包。deployWARs针对每一个应用做一系列校验,然后把启动的任务提交到线程池中,并发进行加载,这里不展开加载的具体过程了。 到此对应的应用加载完成,之后会进行一系列处理直到engine完全启动。之后会进行各个connector的启动,connector的启动主要做3件事情,
- 创建工作线程池
- 设置最大连接限制
- 根据配置的监听线程数启动监听线程
至此tomcat的启动基本完成。