Tomcat 第三篇:总体架构设计
共 11952字,需浏览 24分钟
·
2020-09-22 20:43
![](https://filescdn.proginn.com/2e5df80cc4468782f828db075577def8/cb18e28b292ac8afb8fcb68df800901e.webp)
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
连接,负责网络字节流与Request
和Response
对象的转化。加载并管理 Servlet
,以及处理具体的Request
请求。
「所以 Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)。连接器负责对外交流,容器负责内部处理。」
Tomcat 为了实现多种支持多种 I/O 模型和应用层协议,将 连接器(Connector)
和 容器(Container)
进行了分离,
![](https://filescdn.proginn.com/1b083ffc542f7a3d603569693590d621/5bf6f8bfd6bec5add66154e0094d292a.webp)
在 Tomcat 中,总会有一个 Service ,一个 Service 包含着多个 Connector 和一个 Container(或者说是它的顶层容器 Engine ) 。
而一个 Container 可以包含多个 Host ,一个 Host 内部又可以有多个 Context ,一个 Context 内部又会有多个 Servlet 。
Tomcat 的设计感觉上和 「俄罗斯套娃」 很像,一层包着一层。
Tomcat 启动初始化流程
先放一张启动流程图,然后我们再从源码的角度来验证这个启动流程。
![](https://filescdn.proginn.com/953ed7f7dd7330cfcbaae098c089806b/a9bba5f8420f21f0fdb791ab431280ed.webp)
从上面这张图可以清晰的了解到 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()
这个方法,从名字上来看就是一个初始化方法,点过去一看,结果是一个抽象方法,实现类如下:
![](https://filescdn.proginn.com/3d7e3bbaa217c9ba60bae18d08d48643/c8e5c4223d597e43cff0d3aef6f16a4b.webp)
我们到 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
![](https://filescdn.proginn.com/5de59e0ef4d03667ee3612cf170ac94a/08097f0f72106c041e0ed8e93c8fdb93.webp)