从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. 线性规划(LP)问题

    约束最优化--线性规划(LP)问题 1 线性规划 1.1 图解法(计算机不适用,便于理解) 1.2 单纯形法 1.3 计算几何的方法(待更新) 1 线性规划 约束优化问题:给定约束条件和目标函数,计算 ...

  2. 结构体(C语言)

    目录 1. 结构体类型的声明 1.1 结构体回顾 1.1.1 结构的声明 1.1.2 结构体变量的创建和初始化 1.2 结构的特殊声明 1.3 结构的自引用 2. 结构体内存对齐 2.1 对齐规则 2 ...

  3. 学习JavaScript第四天

    文章目录 1 回顾 内置对象 2 内置对象 2.1 Function 2.2 Global 3 DOM 部分知识点介绍 4 BOM 4.1 window ① 弹框 ② 打开关闭窗口 ③ 页面滚动 ④ ...

  4. 物理实验霍尔效应判断P/N型半导体笔记

    最近这不要物理实验期末考试了嘛,我火速抄起实验书准备复习. 复习到霍尔效应判断P/N型半导体这一部分时很惊奇地发现网上一会儿还搜不出个明了的总结,也有很多误导性的判断方法.Σ( ° △ °|||)︴ ...

  5. PostgresSQL创建一个只读用户

    create user readonlyuser with password 'R3333333341'; grant select on all tables in schema public to ...

  6. 给 Ollama 穿上 GPT 的外衣

    上一篇我们介绍了如何在本地部署 ollama 运行 llama3 大模型.过程是相当简单的.但是现在给大模型交流只能在命令行窗口进行.这样的话就只能你自己玩了.独乐乐不如众乐乐嘛.我们接下来说一下如何 ...

  7. 一个.NET开源、轻量级的运行耗时统计库 - MethodTimer

    前言 在.NET开发中,为了准确统计对应方法的执行时间,我们最常用的方式是手动使用 Stopwatch 来显式编写计时逻辑,但是假如你需要大量的使用 Stopwatch 来进行耗时统计的话不利于保持代 ...

  8. Java反编译器剖析

    本文由 ImportNew - 邬柏 翻译自 javacodegeeks.如需转载本文,请先参见文章末尾处的转载要求. Importnew注:如果你也对Java技术翻译分享感兴趣,欢迎加入我们的Jav ...

  9. 三、FreeRTOS学习笔记-系统配置

    FreeRTOSConfig.h配置文件作用:对FreeRTOS进行功能配置和裁剪,以及API函数的使能. 系统配置文件中主要是一些宏定义 相关宏定义可分为三类: 1."INCLUDE&qu ...

  10. k8s之常用命令

    Minikube # 启动集群服务 minikube start # 查看集群服务状态 minikube status# 登录集群minikube ssh # 关闭,暂停,恢复,删除集群服务 mini ...