从startup.sh入手

os400=false
case "`uname`" in
OS400*) os400=true;;
esac PRG="$0" while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh if $os400; then
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi exec "$PRGDIR"/"$EXECUTABLE" start "$@"

整个脚本核心就是最后一句代码, EXECUTABLE变量是catalina.sh, 代表执行catalina.sh, 参数是start, 再去对比了shutdown.sh, 两个脚本的核心都是调用catalina.sh传递的变量不同。

浏览catalina.sh脚本

整个脚本很长,我这里之截图了我们关心的脚本内容。 这段代码里, 除了能看到参数传递start, 最后会输出Tomcat started外,能看到调用了org.apache.catalina.startup.Bootstrap, 也就是说找到我们的程序入口,或者说找到了我们的程序的main函数。

    shift
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Djava.security.manager \
-Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&" else
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&" fi if [ ! -z "$CATALINA_PID" ]; then
echo $! > "$CATALINA_PID"
fi echo "Tomcat started."

看到这里我们做个小小的总结:Tomcat本质上也是一个java程序,因此startup.sh会启动一个jvm来运行tomcat的启动类Bootstrap.java。

Bootstrap类核心功能

  • 静态构造器部分, 主要初始化了CATALINA_HOME和CATALINA_BASE两个变量内容
  • main函数方法部分一,创建和初始化daemon, 创建三个类加载器
  • main函数方法部分二,控制tomcat的启动和停止

从Bootstrap.main方法开始

开始main方法之前,首先看两个关键属性

/*************
守护进程对象
**********/
private static volatile Bootstrap daemon = null; /***
守护程序用的catalina对象
***/
private Object catalinaDaemon = null;

Bootstrap#main

 public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
//初始化完成之前,不要对daemon赋值
Bootstrap bootstrap = new Bootstrap();
try {
//调用初始化方法, 完成加载器的配置和初始化器的准备
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
//当作为服务正在运行时,如果调用停止方法,这将在一个新线程上进行,以确保使用正确的类加载器,防止出现未找到类的异常
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
} String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
} if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
//Bootstrap加载
daemon.load(args);
//Bootstrap启动
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else { }
}
public void init() throws Exception {
//初始化类的三个加载器
initClassLoaders();
//设置线程类加载器, 将容器的加载器传入
Thread.currentThread().setContextClassLoader(catalinaLoader);
//加载安全类加载器
SecurityClassLoad.securityClassLoad(catalinaLoader);
//通过反射加载catalina
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
//创建对象
Object startupInstance = startupClass.getConstructor().newInstance(); 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); //对类加载器进行初始化赋值
//调用catalina类内部的setParentClassLoader方法对catalina类内部的类加载赋值
method.invoke(startupInstance, paramValues);
//将创建好的startupInstance对象赋值给catalinaDaemon
catalinaDaemon = startupInstance;
}

Catalina#load

Catalina类的load方法核心就解析config/server.xml并创建Server组件实例, 也就是我们在tomcat整体架构章节里了解的一个tomcat只有一个Server实例。 这部分代码块,我删掉了注释代码,try...catch, 只留下了核心业务代码。

public void load() {
loaded = true;
long t1 = System.nanoTime();
initDirs();
initNaming();
//利用digester类解析server.xml,得到容器的配置
Digester digester = createStartDigester(); InputSource inputSource = null;
InputStream inputStream = null;
File file = null; file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString()); if (inputStream == null) {
inputStream = getClass().getClassLoader().getResourceAsStream(getConfigFile());
inputSource = new InputSource(getClass().getClassLoader().getResource(getConfigFile()).toString());
} if (inputStream == null) {
inputStream = getClass().getClassLoader().getResourceAsStream("server-embed.xml");
inputSource = new InputSource(getClass().getClassLoader().getResource("server-embed.xml").toString());
} if (inputStream == null || inputSource == null) {
return;
} try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
return;
} catch (Exception e) {
return;
} getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); initStreams();
//服务器执行初始化 开始调用的Server的初始化方法注意Server是一个接口
getServer().init();
}

Catalina#start

public void start() {
if (getServer() == null) {
load();
} if (getServer() == null) {
return;
} long t1 = System.nanoTime(); //开始一个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");
} if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook); LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(false);
}
} if (await) {
await();
stop();
}
}

从Bootstrap#createStartDigester方法中可以看到Server接口的实现类是StandardServer

digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");

Server接口继承了Lifecycle接口

StandardServer类继承了抽象类LifecycleMBeanBase,同时实现了Server接口

LifecycleMBeanBase抽象类又继承了抽象类LifecycleBase, 而LifecycleBase抽象类又实现了Lifecycle接口

通过前面的调用链看出来Catalina.start会调用Server接口的start方法,而StandardServer实现类的start方法就追溯到了LifeCycleBase抽象的start方法, 这个类里定义了抽象方法startInternal让子类去实现。 在start方法中也调用了startInternal方法。

StandardServer类图

StandardServer#startInternal

protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING); globalNamingResources.start(); synchronized (servicesLock) {
//这里启动定义的多个service
for (Service service : services) {
service.start();
}
}
}

根据Server的实现类StandardServer类,我顺便查看了其所在包, 看到了整个tomcat用到的核心组件的实现类都在这里了,比如StandardEngine, StandardService,StandardHost, 可以查看其他的实现类结构。

总结





结合上面的两张图片,以及上述的源码分析,我们就能总结出来整个startup.sh过程中完成的任务

  1. Tomcat本质上是一个java程序,因此startup脚本会启动一个jvm来运行tomcat的启动类Bootstrap.
  2. Bootstrap的主要任务就是初始化tomcat的类加载器,并且创建Catalina.
  3. Catalina是一个启动类,通过解析Server.xml创建相应组件,通过调用Server接口实现类去启动Server.
  4. StandardServer通过调用父类LifecycleBase的start方法,并且重写startInternal方法来启动Server和启动Service.
  5. Service组件的职责就是管理连接器和顶层容器,他会调用连接器和顶层容器的start方法.
  6. 容器组件负责启动管理子容器,并且调用Host的start方法, 将各层容器启动起来。

参考资料

https://juejin.cn/post/7155750621864263716

https://2i3i.com/tomcat-code-3.html

https://juejin.cn/post/7082681444182523934

https://time.geekbang.org/column/article/97603

https://zhuanlan.zhihu.com/p/344635709

tomcat源码分析(一)如何启动服务的更多相关文章

  1. Tomcat源码分析之—具体启动流程分析

    从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息 ...

  2. Tomcat源码分析(从启动流程到请求处理)

    Tomcat 8.5下载地址 https://tomcat.apache.org/download-80.cgi Tomcat启动流程 Tomcat源码目录 catalina目录 catalina包含 ...

  3. Tomcat源码分析之—组件启动实现分析

    Tomcat由多个组件组成,那么Tomcat是怎么对他们的生命周期进行管理的么,这里将从Tomcat源码去分析其生命周期的实现: Bootstrape类为Tomcat的入口,所有的组件够通过实现Lif ...

  4. Tomcat 源码分析(转)

    本文转自:http://blog.csdn.net/haitao111313/article/category/1179996 Tomcat源码分析(一)--服务启动 1. Tomcat主要有两个组件 ...

  5. Tomcat源码分析——启动与停止服务

    前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家 ...

  6. [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat

    概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...

  7. Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

    Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...

  8. Tomcat源码分析

    前言: 本文是我阅读了TOMCAT源码后的一些心得. 主要是讲解TOMCAT的系统框架, 以及启动流程.若有错漏之处,敬请批评指教! 建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, ...

  9. Tomcat源码分析--转

    一.架构 下面谈谈我对Tomcat架构的理解 总体架构: 1.面向组件架构 2.基于JMX 3.事件侦听 1)面向组件架构 tomcat代码看似很庞大,但从结构上看却很清晰和简单,它主要由一堆组件组成 ...

  10. tomcat 源码分析

    Tomcat源码分析——Session管理分析(下)    Tomcat源码分析——Session管理分析(上)     Tomcat源码分析——请求原理分析(下)     Tomcat源码分析——请 ...

随机推荐

  1. Ubuntu 22.04 解决和 Windows 共享蓝牙设备的问题

    我有一个 Airpods,连接到 WIndows 可以正常工作,但连接到 ubuntu 后会无法连接,只能删除设备选择重联,但是这又会导致 Windows 不能连接到耳机,只能也删除重新连接,费神费力 ...

  2. Hadoop未授权访问

    Hadoop未授权访问 是什么? Hadoop 是一种用来处理和存储大量数据的软件工具,可以用来日志分析,推荐系统,数据备份   核心组件: 存储大数据:HDFS 文件系统 处理大数据:MapRedu ...

  3. 人工智能模型训练中的数据之美——探索TFRecord

    上一篇:<构建人工智能模型基础:TFDS和Keras的完美搭配> 序言:在人工智能模型的训练过程中,如何高效管理和处理大量数据是一个重要的课题.TensorFlow 的 TFRecord ...

  4. 3.20 什么是环境变量,Linux环境变量有哪些?

    变量是计算机系统用于保存可变值的数据类型,我们可以直接通过变量名称来提取到对应的变量值.在 Linux 系统中,环境变量是用来定义系统运行环境的一些参数,比如每个用户不同的家目录(HOME).邮件存放 ...

  5. 第三篇:低功耗模组Air724UG硬件设计手册

    ​ 今天我们分享最后一篇. 3.20 省电功能 根据系统需求,有两种方式可以使模块进入到低功耗的状态.对于AT版本使用"AT+CFUN"命令可以使模块 进入最少功能状态. 具体的功 ...

  6. 2025年前端面试准备css篇

    1.css 盒子模型 css包含了内容(content) ,内边距(padding),边框(border),外边距(margin) 等因素. css 标准盒子模型宽包括:margin+border+p ...

  7. Spring开闭原则的表现-BeanPostProcessor的扩展点-1

    上接Spring事务处理时自我调用的解决方案及一些实现方式的风险继续分析,在分析上篇的问题之前,我们需要了解下BeanPostProcessor概念和Spring容器创建Bean的流程. 一.Bean ...

  8. 深入JUnit源码之Runner

    初次用文字的方式记录读源码的过程,不知道怎么写,感觉有点贴代码的嫌疑.不过中间还是加入了一些自己的理解和心得,希望以后能够慢慢的改进,感兴趣的童鞋凑合着看吧,感觉JUnit这个框架还是值得看的,里面有 ...

  9. golang之常用第三方包汇总

    汇总golang日常开发中常用的库包 [web] gin:  github.com/gin-gonic/gin [MySQL] gorm: [Redis] go-redis:  github.com/ ...

  10. nodejs版本管理工具之nvm

    有时候 项目中依赖不同版本的node,这就需要我们使用一个版本管理工具 有序的管理这些node,这里介绍使用nvm GitHub地址:https://github.com/nvm-sh/nvm 前提: ...