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

相关文章

oracle 11g导入\导出(expdp impdp)之导入过程

《oracle11g导入导出(expdpimpdp)之导入过程》导出需使用SEC.DMP格式,无分号;建立expdir目录(E:/exp)并确保存在;导入在cmd下执行,需sys用户权限;若需修... 目录准备文件导入(impdp)1、建立directory2、导入语句 3、更改密码总结上一个环节,我们讲了

Vite 打包目录结构自定义配置小结

《Vite打包目录结构自定义配置小结》在Vite工程开发中,默认打包后的dist目录资源常集中在asset目录下,不利于资源管理,本文基于Rollup配置原理,本文就来介绍一下通过Vite配置自定义... 目录一、实现原理二、具体配置步骤1. 基础配置文件2. 配置说明(1)js 资源分离(2)非 JS 资

ShardingProxy读写分离之原理、配置与实践过程

《ShardingProxy读写分离之原理、配置与实践过程》ShardingProxy是ApacheShardingSphere的数据库中间件,通过三层架构实现读写分离,解决高并发场景下数据库性能瓶... 目录一、ShardingProxy技术定位与读写分离核心价值1.1 技术定位1.2 读写分离核心价值二

MyBatis-plus处理存储json数据过程

《MyBatis-plus处理存储json数据过程》文章介绍MyBatis-Plus3.4.21处理对象与集合的差异:对象可用内置Handler配合autoResultMap,集合需自定义处理器继承F... 目录1、如果是对象2、如果需要转换的是List集合总结对象和集合分两种情况处理,目前我用的MP的版本

Java Kafka消费者实现过程

《JavaKafka消费者实现过程》Kafka消费者通过KafkaConsumer类实现,核心机制包括偏移量管理、消费者组协调、批量拉取消息及多线程处理,手动提交offset确保数据可靠性,自动提交... 目录基础KafkaConsumer类分析关键代码与核心算法2.1 订阅与分区分配2.2 拉取消息2.3

SpringBoot通过main方法启动web项目实践

《SpringBoot通过main方法启动web项目实践》SpringBoot通过SpringApplication.run()启动Web项目,自动推断应用类型,加载初始化器与监听器,配置Spring... 目录1. 启动入口:SpringApplication.run()2. SpringApplicat

解决Nginx启动报错Job for nginx.service failed because the control process exited with error code问题

《解决Nginx启动报错Jobfornginx.servicefailedbecausethecontrolprocessexitedwitherrorcode问题》Nginx启... 目录一、报错如下二、解决原因三、解决方式总结一、报错如下Job for nginx.service failed bec

AOP编程的基本概念与idea编辑器的配合体验过程

《AOP编程的基本概念与idea编辑器的配合体验过程》文章简要介绍了AOP基础概念,包括Before/Around通知、PointCut切入点、Advice通知体、JoinPoint连接点等,说明它们... 目录BeforeAroundAdvise — 通知PointCut — 切入点Acpect — 切面

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

MySQ中出现幻读问题的解决过程

《MySQ中出现幻读问题的解决过程》文章解析MySQLInnoDB通过MVCC与间隙锁机制在可重复读隔离级别下解决幻读,确保事务一致性,同时指出性能影响及乐观锁等替代方案,帮助开发者优化数据库应用... 目录一、幻读的准确定义与核心特征幻读 vs 不可重复读二、mysql隔离级别深度解析各隔离级别的实现差异