Tomcat 总体架构设计

在开始这篇文章的时候,忽然发现上一篇内容的题目不是很合适,不应该叫启动流程,更确切的应该是叫启动脚本。

在最开始,先介绍下 Tomcat 的总体设计,先有一个大概的印象,对 Tomcat 不至于那么陌生。

先介绍下 Tomcat 的一些基础组件(以下内容来自刘光瑞老师的「tomcat 架构解析」):

组件名称 介绍
Server 这个其实就是 Servlet 容器,一个 Tomcat 中只能有一个 Server
Service Service 表示一个或多个 Connector 的集合,这些 Connector 共享同一个 Container 来处理其请求。在同一个 Tomcat 实例内可以包含任意多个 Service 实例,它们彼此独立
Connector 这个是 Tomcat 的连接器,用于监听并转化 Servlet 的请求,同时将读取的 Socket 请求转交由 Container 处理,并且支持不同的协议以及不同的 I/O 方式。
Container 表示能够执行客户端请求并返回响应的一类对象。在 Tomcat 中存在不同级别的容器 Engine, Host, Context, Wrapper
Engine 表示整个 Servlet 引擎。在 Tomcat 中, Engine 为最高层级的容器对象。尽管 Engine 不是直接处理请求的容器,却是获取目标容器的入口。
Host Host 作为一类容器,表示 Servlet 引擎(即 Engine 中的虚拟机,与一个服务器的网络名有关,如域名等。客户端可以使用这个网络名连接服务器,这个名称必须要在 DNS 服务器上注册
Context Context 作为一类容器,用于表示 Servletcontext ,在 Servlet 规范中,一个 Servletcontext 即表示一个独立的 Web 应用。
Wapper Wapper 作为一类容器,用于表示 Web 应用中定义的 Servlet。
Executor 表示 Tomcat 组件间可以共享的线程池。

这个表格大致看一下,了解下 Tomcat 的一些组件,无需记忆,先有个印象,后面的内容会慢慢的聊到每一个组件。

作为一款 Web 容器, Tomcat 大体上实现了两个核心功能:

  • 处理 Socket 连接,负责网络字节流与 RequestResponse 对象的转化。
  • 加载并管理 Servlet ,以及处理具体的 Request 请求。

所以 Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)。连接器负责对外交流,容器负责内部处理。

Tomcat 为了实现多种支持多种 I/O 模型和应用层协议,将 连接器(Connector)容器(Container) 进行了分离,

在 Tomcat 中,总会有一个 Service ,一个 Service 包含着多个 Connector 和一个 Container(或者说是它的顶层容器 Engine ) 。

而一个 Container 可以包含多个 Host ,一个 Host 内部又可以有多个 Context ,一个 Context 内部又会有多个 Servlet 。

Tomcat 的设计感觉上和 「俄罗斯套娃」 很像,一层包着一层。

Tomcat 启动初始化流程

先放一张启动流程图,然后我们再从源码的角度来验证这个启动流程。

从上面这张图可以清晰的了解到 Tomcat 的初始化的核心过程如下:

BootStrap -> Catalina -> Server -> Service -> Excutor -> Container (Engine -> Host -> Context -> Container)

1 BootStrap.main()

首先,整个程序是从 BootStrap 开始启动的,执行的是 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) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
} try {
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);
daemon.load(args);
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 {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}

在最开始,执行的是一段 synchronized 的同步代码,这里执行代码 bootstrap.init() 初始化了 bootstrap 对象,具体做了什么跟进去看下:

public void init() throws Exception {
// 初始化类加载器
initClassLoaders();
// 设置线程类加载器,将容器的加载器传入
Thread.currentThread().setContextClassLoader(catalinaLoader);
// 加载安全类
SecurityClassLoad.securityClassLoad(catalinaLoader);
// 初始化日志
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
// 注意这里,通过反射加载了 Catalina
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance(); // Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
// 调用了上面加载的 Catalina 中的 setParentClassLoader 方法
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); catalinaDaemon = startupInstance;
}

注释都加好了,就不多解释了,我们接着回到之前的 main ,接下来还有一句是 daemon = bootstrap ,就是把我们刚才初始化过的 bootstrap 赋值给了 daemon 这个变量。

接下来是一大段的判断,主要是用来判断输入参数,这里面我们关注的就中间那一小段,当输入参数为 start 的时候,总共做了两件大事:

  • daemon.load(args)
  • daemon.start()

先跟进去看下 daemon.load(args) 都干了点啥:

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;
}
// 这里的 catalinaDaemon 刚才在 init() 方法里面进行了初始化,所以这里调用的实际上是 Catalina 的 load 方法
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);
}

这里实际上是开启了整个链式调用,接着往下追,看下 Catalina 里面的 load 方法。

在这里,会有一个方法的重载 load(String args[])load() ,不过关系不大,最终都是会调用到那个无参的方法上的。

public void load() {

    if (loaded) {
return;
}
loaded = true; long t1 = System.nanoTime(); initDirs(); // Before digester - it may be needed
initNaming(); // Create and execute our Digester
Digester digester = createStartDigester(); InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), e);
}
}
} // This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
} if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
} try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
} getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); // 前面都是在初始化加载各种配置文件,使用了 Digester
// Stream redirection
initStreams(); // Start the new server
try {
// 开始调用的 Server 的初始化方法
// Server 是一个接口,并且继承了 Lifecycle ,进行生命周期的管理
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
} long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}

这一大坨大多数都是在加载各种配置文件,在这里,会完成对 server.xml 文件的解析。

我们关心的其实就只有最后那段 try...catch 里面的那一句 getServer().init() ,开始调用 Server 的初始化方法,这个方法就是开启一系列容器组件的加载方法。

不过看到最后一句的 log 打印,忽然有印象了,之前在启动 Tomcat 的时候,在日志的最后部分,都会看到这句打印,而且可以看到的是,这里并不是我之前以为的是使用毫秒数算出来的,而是取的纳秒数通过除 1000000 计算得出的,难道是这样算的更准?

getServer().init() 实际上是调用了 org.apache.catalina.Server 中的 init() 方法,而 org.apache.catalina.Server 则是一个接口,还继承了 org.apache.catalina.Lifecycle 进行容器生命周期的管理。

而抽象类 org.apache.catalina.util.LifecycleBase 则是实现了 org.apache.catalina.Lifecycle 接口,我们在 org.apache.catalina.Lifecycle 中打开 init() 方法:

public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
} try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}

这里我们的关注点是 initInternal() 这个方法,从名字上来看就是一个初始化方法,点过去一看,结果是一个抽象方法,实现类如下:

我们到 org.apache.catalina.core.StandardServer 中去看下其中的 initInternal() 方法,在这个方法的最后,看到了循环 Service 并且调用了 service.init()

for (Service service : services) {
service.init();
}

因为一个 Server 是可以有多个 Service 的,所以这里用了一个循环,接下来就是一个顺序的容器初始化的调用过程:

StandardServer -> StandardService -> StandardEngine -> Connector

每个容器都在初始化自身相关设置的同时,将子容器初始化。

Tomcat 在启动初始化的时候,是通过链条式的调用,每初始化一个完成一个组件,就会在组件内调用下一个组件的初始化方法。

同样的操作在启动的时候也是一样的,不知道各位是否还记得我们在 Bootstrap.main() 中还有另一句代码 daemon.start()

public void start() throws Exception {
if (catalinaDaemon == null) {
init();
} Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}

操作嘛都是一样的操作,从这段代码开始,调用了 Catalina.start() 的方法,然后开启了另一个链式调用。

我就不多说了,留给各位读者自己去翻翻看源码吧。

参考

https://segmentfault.com/a/1190000023475177

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

Tomcat 第三篇:总体架构设计的更多相关文章

  1. MRS IoTDB时序数据库的总体架构设计与实现

    MRS IoTDB时序数据库的总体架构设计与实现 MRS IoTDB是华为FusionInsight MRS大数据套件最新推出的时序数据库产品,其领先的设计理念在时序数据库领域展现出越来越强大的竞争力 ...

  2. .NET Core实战项目之CMS 第九章 设计篇-白话架构设计

    前面两篇文章给大家介绍了我们实战的CMS系统的数据库设计,源码也已经上传到服务器上了.今天我们就好聊聊架构设计,在开始之前先给大家分享一下这几天我一直在听的<从零开始学架构>里面关于架构设 ...

  3. Netty4详解三:Netty架构设计(转)

    http://blog.csdn.net/suifeng3051/article/details/28861883?utm_source=tuicool&utm_medium=referral ...

  4. Netty4具体解释三:Netty架构设计

         读完这一章,我们基本上能够了解到Netty全部重要的组件,对Netty有一个全面的认识.这对下一步深入学习Netty是十分重要的,而学完这一章.我们事实上已经能够用Netty解决一些常规的问 ...

  5. PowerBI入门 第三篇:报表设计技巧

    最近做了几个PowerBI报表,对PowerBI的设计有了更深的理解,对数据的塑形(sharp data),不仅可以在Data Source中实现,例如在TSQL查询脚本中,而且可以在PowerBI中 ...

  6. PowerBI开发 第三篇:报表设计技巧

    最近做了几个PowerBI报表,对PowerBI的设计有了更深的理解,对数据的塑形(sharp data),不仅可以在Data Source中实现,例如在TSQL查询脚本中,而且可以在PowerBI中 ...

  7. 一个简单可参考的API网关架构设计

    网关一词较早出现在网络设备里面,比如两个相互独立的局域网段之间通过路由器或者桥接设备进行通信, 这中间的路由或者桥接设备我们称之为网关. 相应的 API 网关将各系统对外暴露的服务聚合起来,所有要调用 ...

  8. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  9. Day1:了解APICloud平台、理解APICloud应用设计思想、掌握平台使用流程。学习如何对一款APP进行需求分析、功能分解和架构设计等编码之前重要的准备工作

    学习目标 总体上了解一下APICloud平台,重点介绍相关的学习资源,入门资料,常见的FAQ等 明确我们这七天要开发一个什么样的APP,明确功能需求,跟上每天的课程节奏,可以课前预习 梳理出对于一款A ...

随机推荐

  1. 用C++写一个电话通讯录

    目前我也是名初学C++的菜鸟,一直在B站上看的C++的网课,这个的C++电话通讯录是我写的第一个有一些功能的代码,所以在这里想分享给初学C++的小白们,如有任何问题或是建议可以在下方评论或是私信我,下 ...

  2. 动态数组java实现

    数组是一种顺序存储的线性表,所有元素的内存地址是连续的. 动态数组相对于一般数组的优势是可以灵活地添加或删除元素.而一般数组则受限于固定的内存空间.只能有限的添加元素 动态数组(Dynamic Arr ...

  3. Nordic52840SDK学习之定时器

    Nordic 52840 SDK学习之定时器 今天开始学习52840SDK,特在此处记录学习内容,防止以后忘记,或许可以给以后的初学者提供一些帮助.如有错误,请发邮件至843036544@qq.com ...

  4. SPSSAU数据分析思维培养系列2:方法选择篇

    大家好!在上篇文章中,我们一起学习了如何[掌握正确的数据处理思维].在完成数据准备和清理工作后,就要进入到正式分析阶段,而选择什么样的数据分析方法进行分析是关键. 想要进行科学和系统化的数据分析,分析 ...

  5. CentOS 7/8上部署Ceph

    Ceph是一个分布式的存储系统,可以在统一的系统中提供唯一的对象.块和文件存储,Ceph的大致组件如下: 1. Ceph监视器(ceph-mon):用来维护集群状态的映射,包括监视器映射,管理器映射, ...

  6. Istio的流量管理(实操三)

    Istio的流量管理(实操三) 涵盖官方文档Traffic Management章节中的egress部分.其中有一小部分问题(已在下文标注)待官方解决. 目录 Istio的流量管理(实操三) 访问外部 ...

  7. 洛谷P1080 国王游戏 python解法 - 高精 贪心 排序

    洛谷的题目实在是裹脚布 还编的像童话 这题要 "使得获得奖赏最多的大臣,所获奖赏尽可能的少." 看了半天都觉得不像人话 总算理解后 简单说题目的意思就是 根据既定的运算规则 如何排 ...

  8. 23种设计模式(C++)

    每一种都有对应理解的相关代码示例 → Git原码 一. GOF-23 模式分类 从目的来看 • 创建型(Creational)模式:将对象的部分创建工作延迟到子类或者其他对象,从而应对需求变化为对象创 ...

  9. win10找不到wifi

    禁用->启用  就能用了.

  10. [PyTorch 学习笔记] 4.2 损失函数

    本章代码: https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson4/loss_function_1.py https:// ...