从上一节中我们分析了组件的初始化,除了容器进行了线程池的创建之后,其他的组件的初始化基本上是在注册mbean服务。
Bootstrap
daemon.setAwait(true);
daemon.load(args);
daemon.start();
我们分析了load,那么解析来就开始start
public void start()
throws Exception {
//如果还没有创建Catalina,那么要进行初始化,确保已经初始化
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
//反射调用Catalina的start方法
method.invoke(catalinaDaemon, (Object [])null);
}
Catalina.start()
public void start() {
//确保Catalina已经进行加载
if (getServer() == null) {
load();
}
//如果加载后依然么有创建服务,那么只能停止,打印日志告知没有配置Server
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();
}
}
在分析服务启动前,我们先来分析下服务的阻塞,一个服务启动后不能立马退出,那么就应当进行阻塞
public void org.apache.catalina.startup.Catalina.await() {
getServer().await();
}
public void StandardServer.await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
//如果端口指定为-2,那么不进行阻塞,直接返回
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
//如果端口为-1,那么通过循环加sleep进行阻塞
if( port==-1 ) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
try {
//如果指定了端口,那么打开一个socket
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error("StandardServer.await: create[" + address
+ ":" + port
+ "]: ", e);
return;
}
try {
awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
//BIO阻塞
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error("StandardServer.await: accept: ", e);
break;
}
// Read a set of characters from the socket
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random();
expected += (random.nextInt() % 1024);
}
//接收命令
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
log.warn("StandardServer.await: read: ", e);
ch = -1;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
break;
}
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// Match against our command string
//如果匹配到了关闭命令,那么跳出循环
boolean match = command.toString().equals(shutdown);
if (match) {
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else
log.warn("StandardServer.await: Invalid command '"
+ command.toString() + "' received");
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
//退出
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
tomcat中有一个shutdow的批处理文件,双击这个文件就会打开一个暂时的窗口,它给8005端口发送一个SHUTDOWN命令,让tomcat主线程退出,以达到关闭的目的
getServer().start()
public final synchronized void org.apache.catalina.util.LifecycleBase.start() throws LifecycleException {
//如果已经正在启动了,那么无需重复启动,直接返回,由于多线程的原因,这个state是volatile修饰的,保证内存可见性
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;
}
//如果当前状态还是NEW,也就是连init都么有,那么就需要进行初始化
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();
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.
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
}
}
protected void org.apache.catalina.core.StandardServer.startInternal() throws LifecycleException {
//CONFIGURE_START_EVENT = configure_start
//触发configure_start事件
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
//设置starting状态,并触发事件
setState(LifecycleState.STARTING);
//启动globalNamingResources
globalNamingResources.start();
// Start our defined Services
//启动Service
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
可以看到和init时调用的顺序差不多,server.start->globalNamingResources.start->service.start
这一路比较平静没发生什么大风大浪,我们直接到service.start方法中看看吧
service.start
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
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
//启动线程池
executor.start();
}
}
mapperListener.start();
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
//启动连接器
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
我们先来看看Engine的启动
一贯的先调用LifecycleBase的start方法,设置状态,循环调用生命周期监听器,感兴趣的监听器会做某些处理,最后调用StandardEngine的startInternal, StandardEngine的最后调用StandardEngine的startInternal又会去调用ContainerBase实现的startInternal方法,这个方法是所有容器类通用的方法,所以对于后面的容器启动,不会再次赘述,但会提示调用了此方法。
protected synchronized void ContainerBase.startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
//启动集群组件
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
//启动Realm身份认证
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// Start our child containers, if any
//获取子容器,通过线程池进行一步启动
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
//对于StandardEngine,那么这里启动的就是子容器StandardHost
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
MultiThrowable multiThrowable = new MultiThrowable();
for (Future<Void> result : results) {
try {
//阻塞,等待子容器全部启动完毕
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
multiThrowable.add(e);
}
}
if (multiThrowable.size() > 0) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// 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();
}
从上面的代码可以看到,tomcat对于子容器的启动都是通过线程池的方式去异步启动的,然后主线程通过Future.get进行阻塞,等待子容器启动完毕,so我们来看看StandardHost的启动
public final synchronized void start() throws LifecycleException {
//如果已经正在启动了,那么无需重复启动,直接返回,由于多线程的原因,这个state是volatile修饰的,保证内存可见性
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;
}
//如果当前状态还是NEW,也就是连init都么有,那么就需要进行初始化
//很明显Host这个容器在前面没有进行过初始化,所以这里肯定会进行初始化
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();
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.
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
}
}
上一节中我们没有看到Host与Context的初始化,从上面的代码中我们可以看到在start的时候会对当前容器状态做判断,如果还是NEW这种状态,那么就会先初始化,其实Host与COntext的初始化与Engine相比并没有做特殊的操作,他们跟Host一样都是在ContainerBase中创建了一个线程池,然后再调用LifecycleBase进行MBean的注册。所以这里就不对Host与Context容器的初始化做过多的分析,我们直接分析start
protected synchronized void StandardHost.startInternal() throws LifecycleException {
// Set error report valve
//获取错误管道阀
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
//检查是否已经注册了错误报告管道阀
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
//没有就手动设置错误报告管道阀,错误管道阀用于在发生错误的时候将错误进行整理,最后将错误写到客户端
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
//调用ContainerBase的startInternal方法
super.startInternal();
}
StandardHost的startInternal调用super.startInternal(),意味着将要调用StandardContext的start方法(当然,这里Host的可能是空的,因为我们在server.xml中一般不会配置context标签,一般都是直接放到appbase下面),StandardContext的启动非常复杂,我们单独开一个章节去分析它,这里先跳过。那么接下来,tomcat执行到了HostConfig感兴趣的before_start事件,其代码如下
public void org.apache.catalina.startup.HostConfig.beforeStart() {
//是否创建目录
if (host.getCreateDirs()) {
//根据config.xml配置好的属性或者默认的配置路径进行路径的创建,一般AppBase为$TOMCAT_BASE/webapps,appBase我们可以在配置文件中指定
//configBaseFile为$TOMCAT_BASE/Engine名(一般为Catalina)/conf/host名
File[] dirs = new File[] {host.getAppBaseFile(),host.getConfigBaseFile()};
for (int i=0; i<dirs.length; i++) {
if (!dirs[i].mkdirs() && !dirs[i].isDirectory()) {
log.error(sm.getString("hostConfig.createDirs",dirs[i]));
}
}
}
}
beforestart阶段主要就是判断appBase与当前host的基本配置文件夹是否存在,如果不存在那么就进行创建,本节先到这里,下一节继续查host的start事件
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?