TOMCAT 源码分析 -- 启动
TOMCAT 源码分析 – 启动
前语
Tomcat源码版本为官网下载的9.0.35版本。
构建环境参考https://blog.csdn.net/a17816876003/article/details/106586805
配置文件
Tomcat启动的配置文件为server.xml
,启动过程也全都围绕它进行,Tomcat的模块结构也可以在其中一览无余
<?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" />
<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>
TOMCAT模块结构
Tomcat最重要的模块是Container容器,它层层包裹,像“套娃”一样一层套一层,便于管理内层的生命周期。其结构于配置文件server.xml
中的XML标签可以看得出来。首先记下理解这张图的模块结构,对下面源码启动的顺序理解帮助十分大。
生命周期
Tomcat中每个容器的生命周期都实现了一个接口Lifecycle
,其重要的方法有init
、start
、stop
、destroy
、getState
等。从它的继承树(仅展示部分)可以观察到,容器都实现了它:
启动类
其启动类为Bootstrap
,启动方法为main
方法。
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();
} catch (Throwable t) {
}
daemon = bootstrap;
} else {
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
String command = "start";
// ...
} else if (command.equals("start")) {
daemon.setAwait(true);
// 最重要的两步 -- daemon为Bootstrap类本身
// 第一步加载 -- 加载完会绑定监听socket端口(Tomcat 8开始使用NIO)
daemon.load(args);
// 第二步启动 -- 启动完才会去accept()处理请求
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
}
} catch (Throwable t) {
}
}
加载-load
加载过程,通过反射调用了Catalina
类的load
方法
/**
* Load daemon.
*/
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;
}
// 通过反射调用了`Catalina`类的`load`方法
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);
}
其加载步骤按顺序有以下几步:
1. 读取配置文件
读取%home%/conf/server.xml配置文件,并用Digester
进行解析,将配置文件的容器等配置按照层次递归的放入Server
中,实际为StandardServer
这个类中。
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
File file = configFile();
// Create and execute our Digester
Digester digester = createStartDigester();
// ...
digester.push(this);
digester.parse(inputSource);
2. 对server
对象进行初始化
getServer().init();
- 2.1 发现其初始化都是用超类
LifecycleBase
的final方法进行初始化的(Engine
、Host
、Context
、Wrapper
等都间接继承于它)。
public abstract class LifecycleBase implements Lifecycle {
@Override
public final synchronized void init() throws LifecycleException {
// ...
try {
// 设置状态
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 真正执行初始化的方法
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
}
查看initInternal
方法的定义,发现他将这个方法交由子类进行具体实现,这里就提现了Java多态的模板方法的好处,继续看下去会发现,所有Containner
组件的初始化都经过init()
方法,最终由自己实现initInternal
方法,管理自己内部容器的初始化操作。
// LifecycleBase.java
protected abstract void initInternal() throws LifecycleException;
继续进入initInternal
方法执行,进入了Server
的实现类StandardServer
中
// StandardServer.java
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize utility executor
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
// Initialize our defined Services
// Server管理自己的套娃Service,最终也借用超类的init()方法对Service进行初始化
for (Service service : services) {
// 对service进行init
service.init();
}
}
3. 对service
进行初始化
在第二步的末尾的循环中又进入了2.1
中的init
方法,再调用service
实现类StandardService
的initInternal
方法
// StandardService.java
@Override
protected void initInternal() throws LifecycleException {
// 初始化Engine
if (engine != null) {
engine.init();
}
// 初始化执行器
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// 初始化监听器
mapperListener.init();
// 初始化 Connectors (可以有多个连接器)
synchronized (connectorsLock) {
for (Connector connector : connectors) {
connector.init();
}
}
}
4. 初始化引擎、执行器、监听器、连接器
-
初始化引擎
重要的仍是其实现父类的
initInternal
方法,对Realm
(领域)的配置,实际获取了LockOutRealm
。// StandardEngine.java @Override protected void initInternal() throws LifecycleException { getRealm(); // 这一步中还会有`ContainerBase`中创建`startStopExecuter`线程池供`start`启动阶段使用。 super.initInternal(); } // ContainerBase.java private void reconfigureStartStopExecutor(int threads) { if (threads == 1) { // Use a fake executor -- 虚假的线程池 if (!(startStopExecutor instanceof InlineExecutorService)) { startStopExecutor = new InlineExecutorService(); } } else { // Delegate utility execution to the Service Server server = Container.getService(this).getServer(); server.setUtilityThreads(threads); startStopExecutor = server.getUtilityExecutor(); } }
-
初始化执行器
源码中带的
server.xml
中没有定义他,debug直接跳过了。 -
初始化监听器
// LifecycleMBeanBase.java // MapperListener.java 中并无实现initInternal方法,则一直调用到超类的额该方法 @Override protected void initInternal() throws LifecycleException { if (oname == null) { mserver = Registry.getRegistry(null, null).getMBeanServer(); // 将oname 赋值为Catalina:type=Mapper oname = register(this, getObjectNameKeyProperties()); } }
-
初始化连接器
@Override protected void initInternal() throws LifecycleException { // Initialize adapter adapter = new CoyoteAdapter(this); protocolHandler.setAdapter(adapter); if (service != null) { protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor()); } try { // 对协议处理器进行初始化 protocolHandler.init(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e); } }
- 初始化协议处理器
// AbstractProtocol.java (Http11NioProtocol.java)
@Override
public void init() throws Exception {
// 初始化终点(端点)
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
endpoint.init();
}
// 终(端)点初始化 --这一步,连接器就要让终点去绑定端口了
// AbstractEndpoint.java
public final void init() throws Exception {
if (bindOnInit) {
bindWithCleanup();
bindState = BindState.BOUND_ON_INIT;
}
}
// NioEndpoint.java
protected void initServerSocket() throws Exception {
if (!getUseInheritedChannel()) {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
// 最终这一步去绑定了8080端口
serverSock.socket().bind(addr,getAcceptCount());
} else {
}
serverSock.configureBlocking(true); //mimic APR behavior
}
5. 回归Bootstrap
接着单步执行会发现它一层一层往外走,最终回到了Bootstrap
中,进入下一步启动-start()
。
启动-start
启动过程通过反射调用了Catalina
的start
方法。
// Bootstrap.java
public void start() throws Exception {
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
// Catalina.java
public void start() {
// 获取不到Server则调用之前的加载,到这里他已经加载过了,不会进这个分支
if (getServer() == null) {
load();
}
// 正式调用Server的启动生命周期
try {
getServer().start();
} catch (LifecycleException e) {
}
}
启动步骤按顺序分以下几个:
1. Server
的启动
可以发现它也是交由超类的final
start
进行实现,可想而知,所有容器的启动,也都会进这个start
方法。
并且与load()
方法相似startInternal
都交由子类具体实现其内部逻辑
// LifecycleBase.java
@Override
public final synchronized void start() throws LifecycleException {
// ...
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
// startInternal交由子类具体实现内部逻辑
startInternal();
} catch (Throwable t) {
}
}
具体进入startInternal
看实现,可以看到实现在StandardServer
中
// org.apache.catalina.core.StandardServer#startInternal
@Override
protected void startInternal() throws LifecycleException {
// 触发生命周期的事件 -- START
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// 对定义的Service进行start
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
}
2. Service
启动
同理,service.start();
进入了超类的start()
方法,且最终调用自身StandardService
的startInternal
方法进行实现(下同之处自动省略)。
// org.apache.catalina.core.StandardService#startInternal
@Override
protected void startInternal() throws LifecycleException {
// 第一步启动引擎
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
// 第二步启动执行器
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
// 第三步启动监听器
mapperListener.start();
// 第四步启动已经成功加载的连接器
// 那么为什么会有失败的呢,最直接的就是端口被占用,无法绑定bind()
synchronized (connectorsLock) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
3. 启动引擎、执行器、监听器、连接器
-
启动引擎
// org.apache.catalina.core.StandardEngine#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // Standard container startup // 调用了超类[ContainerBase]中的startInternal super.startInternal(); } // org.apache.catalina.core.ContainerBase#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // debug 发现进来为null Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { logger.info(String.format("cluster[%s] 开始执行生命周期之start()", cluster.getClusterName())); ((Lifecycle) cluster).start(); } // 会获取到Load方法加载进来的`LockOutRealm` Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { logger.info(String.format("realm-属于container[%s] 开始执行生命周期之start()", realm.getContainer().getName())); // 最终执行的为org.apache.catalina.realm.CombinedRealm#startInternal ((Lifecycle) realm).start(); } // Start our child containers, if any // 最终找到了Engine下定义的Host [StandardHost] Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { // 使用了load()时初始化的线程池 logger.info("开始使用线程池提交多线程去调用子Container[%s]的call方法初始化"); results.add(startStopExecutor.submit(new StartChild(child))); } MultiThrowable multiThrowable = null; // 使用Future#get进行阻塞,获取Host的初始化结果 for (Future<Void> result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } } if (multiThrowable != null) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable()); } // Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) { logger.debug(String.format("开始管道pipeline[%s]的生命周期之start()", pipeline.getContainer().getName())); ((Lifecycle) pipeline).start(); } // 声明周期改成启动中 setState(LifecycleState.STARTING); // Start our thread if (backgroundProcessorDelay > 0) { monitorFuture = Container.getService(ContainerBase.this).getServer() .getUtilityExecutor().scheduleWithFixedDelay( new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS); } }
-
启动
Cluster
获得对象为null,无需启动
-
启动
Realm
有对象,跳过
-
启动子容器
Host
根据上述代码可知,它会被提交到线程池,进行多线程启动,将会调用到线程的
call
方法// org.apache.catalina.core.ContainerBase.StartChild // 超类中的静态内部类 private static class StartChild implements Callable<Void> { @Override public Void call() throws LifecycleException { // 调用子容器的start方法,在这步中,子容器为`StandardHost` child.start(); //DEBUG: child: "StandardEngine[Catalina].StandardHost[localhost]" return null; } }
接着通过
StandardHost
的start
生命周期又进入startInternal
方法// org.apache.catalina.core.StandardHost#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // 检查管道中有没有报错 // 真正的启动交由超类实现 super.startInternal(); } // 接着又交由到ContainerBase这个熟悉的超类进行实现,在启动-start章节的3.1中已经贴出过,这里不再详细展示,主要就是多线程去启动器子容器 -- 套娃模式的好处 // org.apache.catalina.core.ContainerBase#startInternal @Override protected synchronized void startInternal() throws LifecycleException { // Start our child containers, if any Container children[] = findChildren(); // 由于在server.xml中没有在<Host>节点下再定义<Context>容器,所以这次children数组是空数组 List<Future<Void>> results = new ArrayList<>(); for (Container child : children) { logger.info("开始使用线程池提交多线程去调用子Container[%s]的call方法初始化"); results.add(startStopExecutor.submit(new StartChild(child))); } }
-
启动管道
pipeline
跳过
-
-
启动执行器
在执行完多线程对
Host
的启动后,一路点击Step Out
跳回到StandardService
中的startInternal
方法。若已经分不清层次可以在
StandardService
类中搜索下面这段代码并打上断点。// org.apache.catalina.core.StandardService#startInternal synchronized (executors) { for (Executor executor: executors) { executor.start(); } }
不过
executors
数组长度为0,在这儿就跳过了。 -
启动监听器
// org.apache.catalina.mapper.MapperListener#startInternal @Override public void startInternal() throws LifecycleException { // 获取引擎 Engine engine = service.getContainer(); // 添加监听器进引擎 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); } } } // org.apache.catalina.mapper.MapperListener#addListeners private void addListeners(Container container) { // 把监听器[回调接口]注册进容器、生命周期、并递归对子容器进行注册 container.addContainerListener(this); container.addLifecycleListener(this); for (Container child : container.findChildren()) { // 通过一共5层递归,观察到层次如下 // StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default] addListeners(child); } }
可以观察到套娃一样对子容器进行递归添加监听器层次为:
StandardEngine[Catalina].StandardHost[localhost].StandardContext[].StandardWrapper[default]
。 -
启动连接器
可以在
org.apache.catalina.core.StandardService#startInternal
中搜索for (Connector connector: connectors)
打上断点,快速从上一步的递归中出来,再进入connector.start();
。 ps: 这时候在windows平台上遇到了端口被占用,如何杀死占用了8080端口的进程呢?
<!-- 先找出占用8080端口的进程 --> netstat -ano| findstr "8080" <!-- 杀死占用8080端口的进程[此处刚好为7400] --> taskkill /f /pid 7400
// org.apache.catalina.connector.Connector#startInternal @Override protected void startInternal() throws LifecycleException { try { // 启动协议处理器 protocolHandler.start(); } catch (Exception e) { } } // org.apache.coyote.AbstractProtocol#start @Override public void start() throws Exception { // 启动端点 endpoint.start(); } // org.apache.tomcat.util.net.AbstractEndpoint#start public final void start() throws Exception { startInternal(); } // org.apache.tomcat.util.net.NioEndpoint#startInternal @Override public void startInternal() throws Exception { if (!running) { // 读取配置 // Create worker collection if (getExecutor() == null) { // 初始化线程池,任务队列 createExecutor(); } // 创建限制锁 initializeConnectionLatch(); // 创建NIO的poller线程 poller = new Poller(); Thread pollerThread = new Thread(poller, getName() + "-ClientPoller"); pollerThread.setPriority(threadPriority); pollerThread.setDaemon(true); pollerThread.start(); // 真正开始接收NIO端口的请求 startAcceptorThread(); } } // org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThread protected void startAcceptorThread() { acceptor = new Acceptor<>(this); String threadName = getName() + "-Acceptor"; // http-nio-8080-Acceptor acceptor.setThreadName(threadName); Thread t = new Thread(acceptor, threadName); t.setPriority(getAcceptorThreadPriority()); t.setDaemon(getDaemon()); t.start(); // Thread[http-nio-8080-Acceptor,5,main] }
-
回归
BootStrap
一直
Step Out
回到BootStrap
中,完成启动。
总结
- 加载(load)过程主要完成 配置读取-实例化容器等组件、创建线程池、占用监控端口。
- 启动(start)过程主要完成 顺序/多线程启动各层容器、开始接收端口的请求数据。
收获
- 组件模块化,在超类中定义基本操作,在子类中定义具体的一部分的实现。架构脉络清晰,层次分明,节省大量代码量。
- 加载与启动整体解耦分离,就不必在启动过程中在意所需组件是否加载。有点像Spring的懒加载,读取了Bean的定义,但不必去初始化,用到的时候才去启动(描述的不贴切)。
上一篇: tomcat项目部署去掉项目名
下一篇: 记录Android开发学习