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. Vue管理系统前端系列一vue-cli4.x 初始化项目

    目录 项目介绍 技术基础 开发环境 安装工具 快速原型开发 创建项目 配置相关说明 目录结构 项目介绍 lion-ui 是一个基于 RBAC 的管理系统前端项目,采用 vue 和 element-ui ...

  2. 【论文总结】MapReduce论文

    摘要: MR是啥:编程模型,用户只需编写Map,Reduce两个函数,系统完成分布式计算 MR系统是啥:在大量普通计算机上实现并行化计算,系统只关心如何分割数据.大规模集群的调度.集群容错.集群通信 ...

  3. redis安装及性能测试

    Redis是一个开源的使用ANSI C语言编写.遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库.通常被称为数据结构服务器,因为值(value)可以是 字符串(Stri ...

  4. 用Springboot+Jpa实现学生CRUD操作(含前端页面,含分页,自定义SQL)

    前期准备 使用idea新建个SpringBoot项目 参考博客:https://blog.csdn.net/Mr_Jixian/article/details/89742366?tdsourcetag ...

  5. Windows Server 2012 数据库定时备份

    为了数据的安全,我们在服务器上设置了每周备份一次,下面是具体步骤: 一.在文件客户端服务器创建db_backup.cmd的Windows命令脚本.输入以下内容(直接复制可能出错,请手动输入): @ec ...

  6. 光年数据分析表(seo数据监控表和爬虫数据监控表)

    http://www.wocaoseo.com/thread-307-1-1.html 光年seo培训想必很多人都知道,他们提出的数据化操作影响了很多的seo从业者,下面是他们的2个数据表,搜集于网络 ...

  7. idea创建web项目,不能自动导入tomcat包,导致调用request的方法时,无法正常调用

    问题现象 分析原因 reques不能正常调用它的各种方法是因为没有导入tomcat包,所以不能正常调用request对象中的各种方法. 解决办法 ================== ======== ...

  8. Python 带你一键生成朋友圈超火的九宫格短视频

    1. 场景 如果你经常刷抖音和微信朋友圈,一定发现了最近九宫格短视频很火! ​从朋友圈九宫格图片,到九宫格视频,相比传统的图片视频,前者似乎更有个性和逼格 除了传统的剪辑软件可以实现,是否有其他更加快 ...

  9. 轻轻松松学CSS:float

    float属性,会使元素向左或向右移动,其周围的元素也会重新排列.float不仅自己飘忽不定,还对周围元素有影响,这种影响力不容小觑.他捉摸不定(浮动规律不好把握),他干涉他国内政(对周围元素有影响) ...

  10. PJSIP 机器人

    摘要: 最近再研究PJSIP,有一个需求,再适当的时候,需要给远程客户端放音,比如:播放一段广告.或者一段音乐.需要采用API来实现. 正文: 最近想用PJSIP做一个机器人,想法比较简单就是获取客户 ...