Tomcat分析一——Tomcat的顶层结构及启动过程

2024-05-24 09:32

本文主要是介绍Tomcat分析一——Tomcat的顶层结构及启动过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.1 Tomcat的顶层结构

​ Tomcat最顶层容器叫Server,代表整个服务器,Server中至少有一个Service,用于提供服务。

​ Service主要包含两部分:

Connector:用于处理连接相关的事情,并提供Socket与request、response的转换

Container:用于封装和管理Servlet及具体处理request请求。

一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但可以有多个Connector。(因为一个服务可以有多个连接,如同时提供http和https连接,也可以提供相同协议不同端口的连接)

​ Tomcat中的Server由Catalina管理,Catalina是整个Tomcat的管理类,里面的三个方法load、start、stop分别管理整个服务器的生命周期,load方法用于根据Tomcat的 conf/server.xml 文件创建Server并调用Server的init方法进行初始化,start方法用于启动服务器,stop方法用于停止服务器,start和sto方法内部分别调用Server的start和stop方法,load内部调用了Server的init方法。

​ 这三个方法都会按容器的结构逐层调用相应的方法,如Server的start方法中会调用所有Service中的start方法,Service中的start方法会调用所有包含的Connectors和Container的start方法,这样整个服务器就启动了。init和stop方法也一样,这就是Tomcat的生命周期的管理方式。

​ Catalina的await方法直接调用了Server的await方法,作用是进入一个循环,让主线程不会退出。

​ Tomcat的入口Bootstrap中,Bootstrap的作用类似CatalinaAdaptor,具体处理过程还是使用Catalina完成。好处是把启动的入口和具体的管理类分开,方便创建多种启动方式,每种方式只需要写一个相应的CatalinaAdaptor。

1.2 Bootstrap的启动过程

正常启动Tomcat调用Bootstrap的main方法,主要分为两部分:

1.2.1 首先新建Bootstrap,并执行init方法初始化

init方法初始化ClassLoader,并用ClassLoader创建Catalina实例,赋给catalinaDaemon变量,后面对命令的操作都使用catalinaDaemon具体执行。

1.2.2 处理main方法传入的命令,如果args参数为空,默认执行start

​ start命令的处理调用了三个方法:setAwait(true)、load(args)和start()。都调用了Catalina的相应方法进行具体执行,是用反射来调用的。

public static void main(String[] args) {if (daemon == null) { // 先新建一个BootstrapBootstrap bootstrap = new Bootstrap();try {//初始化ClassLoader,并用ClassLoader创建Catalina实例,赋给catalinaDaemon变量bootstrap.init();} catch (Throwable var3) {handleThrowable(var3);var3.printStackTrace();return;}daemon = bootstrap;} else {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();} 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 var4) {Throwable t = var4;if (var4 instanceof InvocationTargetException && var4.getCause() != null) {t = var4.getCause();}handleThrowable(t);t.printStackTrace();System.exit(1);}
}

​ start方法/setAwait方法/load方法——>根据反射调用catalina的start/setAwait/load方法

​ 首先判断catalinaDaemon是否初始化,否则调用init方法进行初始化。然后使用Method进行反射调用Catalina的start方法。((Catalina)catalinaDaemon).start()。

​ Method是reflect包的类,代表一个具体的方法,可以使用其invoke方法执行代表的方法,第一个参数是Method方法所在的实体,第二个参数是可变参数,用于Method方法执行时需要的参数。

​ catalina.start()——>server.start()——>service.start()——>connector.start()/container.start()

public void start() throws Exception {if (this.catalinaDaemon == null) {this.init();}Method method = this.catalinaDaemon.getClass().getMethod("start", (Class[])null);method.invoke(this.catalinaDaemon, (Object[])null);
}public void setAwait(boolean await) throws Exception {Class<?>[] paramTypes = new Class[]{Boolean.TYPE};Object[] paramValues = new Object[]{await};Method method = this.catalinaDaemon.getClass().getMethod("setAwait",paramTypes);method.invoke(this.catalinaDaemon, paramValues);}
private void load(String[] arguments) throws Exception {String methodName = "load";Object[] param;Class[] paramTypes;if (arguments != null && arguments.length != 0) {paramTypes = new Class[]{arguments.getClass()};param = new Object[]{arguments};} else {paramTypes = null;param = null;}Method method = this.catalinaDaemon.getClass().getMethod(methodName,paramTypes);if (log.isDebugEnabled()) {log.debug("Calling startup class " + method);}method.invoke(this.catalinaDaemon, param);}

1.3 Catalina的启动过程

Catalina的启动主要是调用setAwait、load和start方法完成的。

​ setAwait 方法用于设置Server启动完成后是否进入等待状态的标志,true则进入;

​ load 方法用于加载配置文件,创建并初始化Server;

​ start 方法用于启动服务器。

1.3.1 setAwait(boolean)方法

setAwait方法设置await属性的值,await属性在start方法中 服务器启动后使用它判断是否进入等待状态。

public class Catalina {protected static final StringManager sm = StringManager.getManager("org.apache.catalina.startup");protected boolean await = false;protected String configFile = "conf/server.xml";public void setAwait(boolean b) {this.await = b;}

1.3.2 load 方法

​ load方法根据conf/server.xml创建Server对象,并赋值给server属性,解析操作使用Tomcat开源的Digester完成的,然后调用server的init方法

public void load() {long t1 = System.nanoTime();this.initDirs();this.initNaming();Digester digester = this.createStartDigester();InputSource inputSource = null;InputStream inputStream = null;File file = null;.....file = this.configFile();// conf/server.xmlinputStream = new FileInputStream(file);inputSource = new InputSource(file.toURI().toURL().toString());inputSource.setByteStream((InputStream)inputStream);digester.push(this);digester.parse(inputSource);......this.getServer().setCatalina(this);this.getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());this.getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());this.initStreams();try {this.getServer().init();} catch (LifecycleException var24) {}long t2 = System.nanoTime();if (log.isInfoEnabled()) {log.info("Initialization processed in " + (t2 - t1) / 1000000L + " ms");}}

1.3.3 start方法

Catalina的start方法主要调用了server的start方法启动服务器,并根据await属性判断是否让程序进入等待状态

  1. 先判断Server是否存在,不存在则调用load方法初始化Server

  2. 然后调用Server的start方法启动服务器

  3. 最后注册关闭钩子并根据await属性判断是否进入等待状态,由于之前设置为true,所以需要等待

  4. 进入等待状态会调用await和stop两个方法,await方法直接调用了Server的await方法,会执行一个while循环,这样程序就停在await方法,当await方法的while循环退出时,就会执行stop方法,从而关闭服务器。

    server的await方法里,while循环根据volatile类型的stopAwait是否为true停止,默认false,一直循环
    
public void start() {if (this.getServer() == null) {this.load();}.....    try {this.getServer().start();} catch (LifecycleException var7) {try {this.getServer().destroy();} return;}if (this.useShutdownHook) {if (this.shutdownHook == null) {this.shutdownHook = new Catalina.CatalinaShutdownHook();}Runtime.getRuntime().addShutdownHook(this.shutdownHook);LogManager logManager = LogManager.getLogManager();if (logManager instanceof ClassLoaderLogManager) {((ClassLoaderLogManager)logManager).setUseShutdownHook(false);}}if (this.await) {this.await();this.stop();}}
}

1.4 Server的启动过程

类继承和实现的关系图

Server接口的addService(Service)、removeService(Service)添加和删除Service、Server的init方法和start方法分别循环调用每个Service的init方法和start方法启动所有Service。

Server默认的实现是StandardServerStandardServer又继承自LifecycleMBeanBase

LifecycleMBeanBase又继承自LifecycleBase

LifecycleBase中定义了init和start方法

LifecycleBase里的init方法和start方法又调用initInternal方法和startInternal方法,这两个方法都是模版方法,由子类具体实现,

public final synchronized void init() throws LifecycleException {if (!this.state.equals(LifecycleState.NEW)) {this.invalidTransition("before_init");}try {this.setStateInternal(LifecycleState.INITIALIZING, (Object)null, false);this.initInternal();this.setStateInternal(LifecycleState.INITIALIZED, (Object)null, false);} catch (Throwable var2) {ExceptionUtils.handleThrowable(var2);this.setStateInternal(LifecycleState.FAILED, (Object)null, false);}
}protected abstract void initInternal() throws LifecycleException;public final synchronized void start() throws LifecycleException {try {this.setStateInternal(LifecycleState.STARTING_PREP, (Object)null,false);this.startInternal();if (this.state.equals(LifecycleState.FAILED)) {this.stop();} else if (!this.state.equals(LifecycleState.STARTING)) {this.invalidTransition("after_start");} else {this.setStateInternal(LifecycleState.STARTED, (Object)null, false);}protected abstract void startInternal() throws LifecycleException;

所以调用StandardServer的init方法和start方法会执行StandardServer自己的initInternal方法和startInternal方法,里面又分别执行所有的services的init和start方法。

protected void initInternal() throws LifecycleException {super.initInternal();MBeanFactory factory = new MBeanFactory();factory.setContainer(this);this.onameMBeanFactory = this.register(factory, "type=MBeanFactory");this.globalNamingResources.init();for(int i = 0; i < this.services.length; ++i) {this.services[i].init();}
}
protected void startInternal() throws LifecycleException {this.fireLifecycleEvent("configure_start", (Object)null);this.setState(LifecycleState.STARTING);this.globalNamingResources.start();synchronized(this.servicesLock) {for(int i = 0; i < this.services.length; ++i) {this.services[i].start();}}
}

StandardServer的await方法

await方法处理流程,省略了一些处理异常、关闭Socket及对接收到数据处理的代码。

首先判断端口号port,然后根据port的值分为三种处理方法:

  1. port为-2,会直接退出,不进行循环

  2. port为-1,会进入while(!stopAwait)的循环,且内部没有berak跳出的语句,stopAwait标志只有调用了stop方法才会设置为true,所以port为-1时只有在外部调用stop方法才会退出循环

  3. port为其他值,会进入一个while(!stopAwait)循环,同时会在port所在的端口启动一个ServerSocker监听关闭命令,接收到了则使用break跳出循环。

    ​ 这里的端口port和关闭命令shutdown是在conf/server.xml文件中配置Server时设置的。默认设置如下

    <!-- server.xml -->
    <Server port="8005" shutdown="SHUTDOWN">
    

    ​ 这时在8005端口监听"SHUTDOWN"命令,接收到了就会关闭Tomcat,如果不想使用网络命令来关闭服务器可以将端口设置为-1。

    ​ await方法中从端口接受到数据后还会进行处理,如果接收到的数据中有ASCII码小于32的(ASCII中32以下的为控制符)则从小于32的字符截断并丢弃后面的数据。

1.5 Service的启动过程

Service默认的实现是StandardServiceStandardService也继承自LifecycleMBeanBase

StandardService的init方法和start方法最终会执行StandardServer自己的initInternal方法和startInternal方法

initInternal方法主要调用engine、mapperListener、executor和connector的init方法

​ mapperListener是Mapper的监听器,监听container容器的变化,

​ executors是用在connectors中管理线程的线程池

protected void initInternal() throws LifecycleException {super.initInternal();if (engine != null) {engine.init();}// Initialize any Executorsfor (Executor executor : findExecutors()) {if (executor instanceof JmxEnabled) {((JmxEnabled) executor).setDomain(getDomain());}executor.init();}// Initialize mapper listenermapperListener.init();// Initialize our defined Connectorssynchronized (connectorsLock) {for (Connector connector : connectors) {connector.init();}}
}

startInternal主要调用engine、mapperListener、executor和connector的start方法

protected void startInternal() throws LifecycleException {setState(LifecycleState.STARTING);// Start our defined Container firstif (engine != null) {synchronized (engine) {engine.start();}}synchronized (executors) {for (Executor executor: executors) {executor.start();}}mapperListener.start();// Start our defined Connectors secondsynchronized (connectorsLock) {for (Connector connector: connectors) {// If it has already failed, don't try and start itif (connector.getState() != LifecycleState.FAILED) {connector.start();}}}
}

现在整个Tomcat服务器就启动了,整个启动流程
在这里插入图片描述

这篇关于Tomcat分析一——Tomcat的顶层结构及启动过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/998027

相关文章

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

Nexus安装和启动的实现教程

《Nexus安装和启动的实现教程》:本文主要介绍Nexus安装和启动的实现教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、Nexus下载二、Nexus安装和启动三、关闭Nexus总结一、Nexus下载官方下载链接:DownloadWindows系统根

MySQL存储过程之循环遍历查询的结果集详解

《MySQL存储过程之循环遍历查询的结果集详解》:本文主要介绍MySQL存储过程之循环遍历查询的结果集,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言1. 表结构2. 存储过程3. 关于存储过程的SQL补充总结前言近来碰到这样一个问题:在生产上导入的数据发现

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

github打不开的问题分析及解决

《github打不开的问题分析及解决》:本文主要介绍github打不开的问题分析及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、找到github.com域名解析的ip地址二、找到github.global.ssl.fastly.net网址解析的ip地址三

Oracle修改端口号之后无法启动的解决方案

《Oracle修改端口号之后无法启动的解决方案》Oracle数据库更改端口后出现监听器无法启动的问题确实较为常见,但并非必然发生,这一问题通常源于​​配置错误或环境冲突​​,而非端口修改本身,以下是系... 目录一、问题根源分析​​​二、保姆级解决方案​​​​步骤1:修正监听器配置文件 (listener.

MySQL版本问题导致项目无法启动问题的解决方案

《MySQL版本问题导致项目无法启动问题的解决方案》本文记录了一次因MySQL版本不一致导致项目启动失败的经历,详细解析了连接错误的原因,并提供了两种解决方案:调整连接字符串禁用SSL或统一MySQL... 目录本地项目启动报错报错原因:解决方案第一个:第二种:容器启动mysql的坑两种修改时区的方法:本地