tomcat源码分析(一)如何启动服务
从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过程中完成的任务
- Tomcat本质上是一个java程序,因此startup脚本会启动一个jvm来运行tomcat的启动类Bootstrap.
- Bootstrap的主要任务就是初始化tomcat的类加载器,并且创建Catalina.
- Catalina是一个启动类,通过解析Server.xml创建相应组件,通过调用Server接口实现类去启动Server.
- StandardServer通过调用父类LifecycleBase的start方法,并且重写startInternal方法来启动Server和启动Service.
- Service组件的职责就是管理连接器和顶层容器,他会调用连接器和顶层容器的start方法.
- 容器组件负责启动管理子容器,并且调用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源码分析(一)如何启动服务的更多相关文章
- Tomcat源码分析之—具体启动流程分析
从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息 ...
- Tomcat源码分析(从启动流程到请求处理)
Tomcat 8.5下载地址 https://tomcat.apache.org/download-80.cgi Tomcat启动流程 Tomcat源码目录 catalina目录 catalina包含 ...
- Tomcat源码分析之—组件启动实现分析
Tomcat由多个组件组成,那么Tomcat是怎么对他们的生命周期进行管理的么,这里将从Tomcat源码去分析其生命周期的实现: Bootstrape类为Tomcat的入口,所有的组件够通过实现Lif ...
- Tomcat 源码分析(转)
本文转自:http://blog.csdn.net/haitao111313/article/category/1179996 Tomcat源码分析(一)--服务启动 1. Tomcat主要有两个组件 ...
- Tomcat源码分析——启动与停止服务
前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家 ...
- [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat
概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...
- Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...
- Tomcat源码分析
前言: 本文是我阅读了TOMCAT源码后的一些心得. 主要是讲解TOMCAT的系统框架, 以及启动流程.若有错漏之处,敬请批评指教! 建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, ...
- Tomcat源码分析--转
一.架构 下面谈谈我对Tomcat架构的理解 总体架构: 1.面向组件架构 2.基于JMX 3.事件侦听 1)面向组件架构 tomcat代码看似很庞大,但从结构上看却很清晰和简单,它主要由一堆组件组成 ...
- tomcat 源码分析
Tomcat源码分析——Session管理分析(下) Tomcat源码分析——Session管理分析(上) Tomcat源码分析——请求原理分析(下) Tomcat源码分析——请 ...
随机推荐
- Java 如何确保 JS 不被缓存
大家好,我是 V 哥.今天咱们来聊一聊 Java 后端确保 JavaScript 不被缓存的问题,先来了解一下为什么需要这样做,通常源于以下几种场景或问题: 1. 先来看几个问题 1. 文件更新后无法 ...
- dc-2靶机-超详解
dc-2内网靶 1.信息收集 入站信息 nmap扫描 nmap -A -p- -v 192.168.27.0/24 得到消息: IP:192.168.227.139 cms:wordpress 4.7 ...
- Go语言基础07 _interface _1
Go语言基础07 _interface _1 与其他主要编程语⾔的差异 接⼝为⾮⼊侵性,实现不依赖于借⼝定义 所以接⼝的定义可以包含在接⼝使⽤者包内 package interface_test im ...
- WiFi基础(七):WiFi漫游与WiFi组网
liwen01 2024.10.27 前言 无线 WiFi 的优点是方便.灵活,可以接入各种设备.缺点就是信号容易被干扰.信号覆盖范围有限.下面几个问题应该很多人都有遇到过: 为何很多洗手间的 WiF ...
- 基于 Github 平台的 .NET 开源项目模板 - 项目及半自动脚本
接上一篇模板的介绍文章, 我们接下来看看如何配置和使用这款模板. 项目配置 1. fork 咱这款模板. 2. 并将其设置为模板. 3. 在创建新仓库时使用这个模板. 4. 看使用说明,了解项目结构 ...
- delphi Image32 动画演示2
Image 32 自带的Demo,添加一些注解. unit uFrmAnimation2; interface uses Winapi.Windows, Winapi.Messages, System ...
- StarRocks 物化视图刷新流程及原理
前段时间给 StarRocks 的物化视图新增了一个特性,那也是我第一次接触 StarRocks,因为完全不熟悉这个数据库,所以很多东西都是从头开始了解概念. 为了能顺利的新增这个特性(具体内容可以见 ...
- MySQL原理简介—5.存储模型和数据读写机制
大纲 1.为什么不能直接更新磁盘上的数据 2.为什么要引入数据页的概念 3.一行数据在磁盘上是如何存储的 4.一行数据中的NULL值是如何处理的 5.一行数据的数据头存储的是什么 6.一行数据的真实数 ...
- golang之设计模式
[选项模式] package main import "fmt" type OptionFunc func(*DoSomethingOption) type DoSomething ...
- golang之常用命令
golang常用操作与命令 1.执行golang文件 go run hello_world.go 2.编译成可执行文件(交叉编译) go build hello_world 则会生成hello_wor ...