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. pytest封神之路第一步 tep介绍

    『 tep is a testing tool to help you write pytest more easily. Try Easy Pytest! 』 tep前身 tep的前身是接口自动化测 ...

  2. 精讲响应式WebClient第2篇-GET请求阻塞与非阻塞调用方法详解

    本文是精讲响应式WebClient第2篇,前篇的blog访问地址如下: 精讲响应式webclient第1篇-响应式非阻塞IO与基础用法 在上一篇文章为大家介绍了响应式IO模型和WebClient的基本 ...

  3. umount 时目标忙解决办法

    [root@node2 ~]# umount /var/lib/ceph/osd/ceph- umount: /var/lib/ceph/osd/ceph-:目标忙. (有些情况下通过 lsof() ...

  4. Java多线程_CAS算法和ABA问题

    CAS算法概述CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换.CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B. CAS指令执行时,当且仅当内存地址V ...

  5. ZEQP仓储管理系统( WMS)开源

    ZEQP仓储管理系统 代码框架是用的前后台分离的方式 后台使用的是Asp.Net Core平台,开发所有业务,向前台提供Rest API接口. 使用的认证方式是JWT 前端有两个项目,一个是Web端, ...

  6. WPF Devexpress ChartControl CrosshairLabel显示内容居右

    源码可加Q群:580749909. 一.解决的问题 ChartControl中希望CrosshairLabel的内容据右 or 自定义 二.实现. 多个显示实例(实例:条形,线形,点等等)下的内容设置 ...

  7. 力扣Leetcode 199. 二叉树的右视图

    199. 二叉树的右视图 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值. 示例: 输入: [1,2,3,null,5,null,4] 输出: [1, 3, ...

  8. python 四位玫瑰数 + 100以内素数求和

    四位玫瑰数 描述‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬ ...

  9. python编写汉诺塔 Hanoi

    #hanoi.py count = 0 def hanoi(n, src, dst, mid): #src为原1号柱子 dst 目标3号柱子 mid中间2号过渡柱子 global count #对全局 ...

  10. Unity Prefab关联

    Unity3D研究院之Prefab里面的Prefab关联问题http://www.xuanyusong.com/archives/3042