Tomcat启动过程简述

Tomcat简介

Servlet(server applet 服务端小程序)是一种国际组织的协议、约定.
Tomcat只是针对Servlet协议的规范做了封装,其他这样的软件还有jetty等.

启动过程

如何启动

进入Tomcat本目录的bin文件夹下。
执行startup.bat或者
sudo ./startup.sh start
启动startup.bat脚本文件,就可以启动默认端口为8080的tomcat进程。

分析 startup.bat 文件

代码解析

在这里插入图片描述
在这里插入图片描述

流程图

在这里插入图片描述

从上边分析可以看出来,最终执行的是 catalina.bat 下边来看一下这个文件

分析catalina.bat文件

首先省去catalina.bat开头诸多注解,这些注解主要是讲解各个变量是干什么的。需要的话,自己看下英文就可以了。这里就不翻译了。

脚本

在这里插入图片描述

这段判断用户是否是使用catalina.bat run来启动tomcat。
如果使用startup.bat脚本启动tomcat,这段脚本不会执行。
在startup.bat最后一行参数赋值的时候,出现过%1表示命令之后的第一个参数, 在这里指的就是 start。那么%0呢?%0 表示这个可执行程序的名称, %~nx0 的话就是程序的名称+扩展名在这里就是 catalina.bat。
在startup.bat最后一行参数赋值的时候,出现过%1.表示命令之后的第一个参数, 在这里指的就是 start。

%~f0 : 简单说就是表示当前命令的绝对路径.
%* : 我们知道 %1 表示第一个参数, 依次类推, %2 表示第二个… 那么 %* 就很好理解了, 代表所有参数.

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

流程

  1. 首先判断一下用户直接使用 catalina.bat run 来启动 Tocmat
  2. 设置 CATALINA_HOME 和 CATALINA_BASE 环境变量值
  3. 验证 CATALINA_HOME 和 CATALINA_BASE 环境变量值的正确性
  4. 调用 setnv.bat 脚本
  5. 调用 setclasspath.bat 脚本
  6. 添加 bootstrap.jar 和 tomcat-juli.jar 到 CLASSPATH 中
  7. 设置 CATALINA_TMPDIR 临时目录的值为 Tomcat 目录下的 temp
  8. 追加一系列的参数到 JAVA_OPTS 中
  9. 整合相关的启动信息, 参数
  10. 启动 Tomcat

catalina.bat总结

从上面代码可以看出tomcat确实是一个纯java的程序,脚本最后都变成直接使用java命令执行程序,与我们普通写的java程序,没有什么不同。只不过由于 tomcat可以使用各种众多的模式(如debug,Security等),以及各种需要各种参数所以不得不使用脚本来执行。

Bootstrap

Bootstrap类的main方法是整个Tomcat项目的入口,梳理一下就会发现,其实这个类的代码很少,也很清晰,它就是一个大Boss,指挥它的小兵Catalina类的实例完成各种操作。

类图

Bootstrap类图.jpgBootstrap类图

main流程图

Bootstrap类main方法流程图.jpg在这里插入图片描述

start()方法

在这里插入图片描述

类加载器

在start()和main方法中都会调用init(),而在init()方法中又会调用initClassLoader进行初始化类类加载器。Bootstrap类有三个成员变量,分别指向三个类加载器:

  • common
  • catalina
  • shared

通过跟踪代码可以发现这三个类加载器其实就是common一个,是通过读取conf目录下的catalina.properties的属性来创建的。而默认配置文件catalina和shared的值都是空。
对应的catalina.properties的配置如下:
在这里插入图片描述
类加载器的创建代码
在这里插入图片描述
catalina和shared都是以common为父类,且配置属性值都为空,因此这三个引用都是指向commonClassLoader的。

Get到的点

设计模式-委派

Tomcat中共有两个类用于Server的启动,Bootstrap和Catalina。作为Bootstrap的主要工作,是“代理”Catalina内部方法的调用。而这种方式又不像代理模式,更像是MyBatis中常用的一种设计模式——委派,SqlMapClient和另一个Executor后缀的类(具体类名称忘记了),二者定义的方法大致类似,但真正干活的趋势Executor类。在这里Bootstrap类的一个引用对象Catalina实例来执行各个方法的。Bootstrap其本身并不执行任何Server的启动/关闭操作。

看来大师级的人物都偏爱这种设计方法。

Digester

Bootstrap 的 main 方法最后会调用 org.apache.catalina.startup.Catalina 对象的 load 和 start 两个方法,那么就来看看这两个方法里面到底做了些什么。

引入

在什么地方出现了Digester,查看下Catalina类的load方法。把注释、异常抛出、记录日志、流关闭、非空判断这些非逻辑的代码去掉,基本上就剩余下边的代码:
在这里插入图片描述
这段代码的作用:

  • 创建一个 Digester 对象,
  • 根据 inputSource 里设置的文件 xml 路径及Digester 对象所包含的解析规则生成相应对象,
  • 并调用相应方法将对象之间关联起来。
  • 调用 Server 接口对象的 init 方法。

是什么?

一般来说 Java 里解析 xml 文件有两种方式:

  • Dom4J 之类将文件全部读取到内存中,在内存里构造一棵 Dom 树的方式来解析。
  • SAX 的读取文件流,在流中碰到相应的xml节点触发相应的节点事件回调相应方法,基于事件的解析方式,优点是不需要先将文件全部读取到内存中。

Digester 本身是采用 SAX 的解析方式,在其上提供了一层包装,对于使用者更简便友好罢了。最早是在 struts 1 里面用的,后来独立出来成为 apache 的 Commons 下面的一个单独的子项目。Tomcat 里又把它又封装了一层,为了描述方便,直接拿 Tomcat 里的 Digester 建一个单独的

组件

init和start

在Catalina类中的init的方法中,调动了getServer().init()方法,但在Server的实现类StandardServer 类里面并没有发现这两个方法:在这里插入图片描述

两方法必定是在该类的父类中已实现了,在 StandardServer 类的父类 LifecycleMBeanBase 类的父类 LifecycleBase 类里面终于找到了这两个方法的实现,下面先来看下 init 方法:
在这里插入图片描述
这里面就做了一件事情,调用了一下接下来定义的抽象方法 initInternal()。
实际上看下 LifecycleBase 的实现类就会发现,所有的组件类几乎都继承了 LifecycleBase 类,所以这些组件类一般只会有 initInternal 方法的定义。(这里所说的组件类就是前面我们分析 Digester 解析时发现的 StandardServer、StandardService、StandardEngine、StandardHost、StandardContext 等类)

这里所说的组件可以将其理解为我们最开始分析 server.xml 时 xml 文件里的各个节点,父子关系也即 xml 文件里的父子节点。浏览下 LifecycleBase 的子类就会发现节点的实现类都是这个类的子类
在这里插入图片描述

那么还是从start方法开始

 public final synchronized void start() throws LifecycleException {

	//前置校验,这里如果发现 start 方法已经调用过了,将会记录日志并直接返回
	if (LifecycleState.STARTING_PREP.equals(state) ||
			LifecycleState.STARTING.equals(state) ||
			LifecycleState.STARTED.equals(state)) {

		if (log.isDebugEnabled()) {
			Exception e = new LifecycleException();
			log.debug(sm.getString("lifecycleBase.alreadyStarted",
					toString()), e);
		} else if (log.isInfoEnabled()) {
			log.info(sm.getString("lifecycleBase.alreadyStarted",
					toString()));
		}
		return;
	}
    
	//如果发现 start 放的需要做的前置方法没有调用完,或者调用出错,将会先调用这些前置方法
	if (state.equals(LifecycleState.NEW)) {
		init();
	} else if (state.equals(LifecycleState.FAILED)){
		stop();
	} else if (!state.equals(LifecycleState.INITIALIZED) &&
			!state.equals(LifecycleState.STOPPED)) {
		invalidTransition(Lifecycle.BEFORE_START_EVENT);
	}
	setStateInternal(LifecycleState.STARTING_PREP, null, false);

	//将会调用本类中定义的抽象方法 startInternal()
	try {
		startInternal();
	} catch (Throwable t) {
		ExceptionUtils.handleThrowable(t);
		setStateInternal(LifecycleState.FAILED, null, false);
		throw new LifecycleException(
				sm.getString("lifecycleBase.startFail",toString()), t);
	}

	if (state.equals(LifecycleState.FAILED) ||
			state.equals(LifecycleState.MUST_STOP)) {
		stop();
	} else {
		// Shouldn't be necessary but acts as a check that sub-classes are
		// doing what they are supposed to.
		if (!state.equals(LifecycleState.STARTING)) {
			invalidTransition(Lifecycle.AFTER_START_EVENT);
		}

		setStateInternal(LifecycleState.STARTED, null, false);
	}
}

从以上 init 和 start 方法的定义可以看到这两个方法最终将会调用子类中定义的 initInternal 和 startInternal 。
那这两个方法里面干了些什么?

@Override
protected void initInternal() throws LifecycleException {

	super.initInternal();

	// 定义全局的字符串缓存
	// 注意,虽然缓存是全局的,但是如果有多个服务器的话
	// 在JVM中(可能在嵌入时发生),然后是相同的缓存会以多个名称注册
	onameStringCache = register(new StringCache(), "type=StringCache");

	// 定义 MBeanFactory
	MBeanFactory factory = new MBeanFactory();
	factory.setContainer(this);
	onameMBeanFactory = register(factory, "type=MBeanFactory");

	// Register the naming resources
	globalNamingResources.init();

	// 使用来自common和shared的jar填充扩展验证器
	// class loaders
	if (getCatalina() != null) {
		ClassLoader cl = getCatalina().getParentClassLoader();
		// 遍历类装入器层次结构。在系统类装入器处停止。这将添加shared(如果存在)和common加载器
		while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
			if (cl instanceof URLClassLoader) {
				URL[] urls = ((URLClassLoader) cl).getURLs();
				for (URL url : urls) {
					if (url.getProtocol().equals("file")) {
						try {
							File f = new File (url.toURI());
							if (f.isFile() &&
									f.getName().endsWith(".jar")) {
								ExtensionValidator.addSystemResource(f);
							}
						} catch (URISyntaxException e) {
							// Ignore
						} catch (IOException e) {
							// Ignore
						}
					}
				}
			}
			cl = cl.getParent();
		}
	}
	// 初始化定义的Service
	//循环调用 Server 类里内置的 Service 数组的 init 方法。
	for (int i = 0; i < services.length; i++) {
		services[i].init();
	}
}

同样在startInternal方法中也同样有一个循环初始化Service,如下代码:

protected void startInternal() throws LifecycleException {

	fireLifecycleEvent(CONFIGURE_START_EVENT, null);
	setState(LifecycleState.STARTING);

	globalNamingResources.start();

	// Start our defined Services
	synchronized (services) {
		for (int i = 0; i < services.length; i++) {
			services[i].start();
		}
	}
}

Service

上边提到的Digester, 它会经过对 xml 文件的解析将会产生

  • org.apache.catalina.core.StandardServer
  • org.apache.catalina.core.StandardService
  • org.apache.catalina.connector.Connector
  • org.apache.catalina.core.StandardEngine
  • org.apache.catalina.core.StandardHost
  • org.apache.catalina.core.StandardContext

等等一系列对象,这些对象从前到后前一个包含后一个对象的引用(一对一或一对多的关系)

这个就是不同组件的关系
在这里插入图片描述

Server –> Service –>
Connector & Container( Engine –> Host –> Context( Wrapper( Servlet ) ) )

Tomcat 的心脏是两个组件:Connector 和 Container,Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个 Container 可以选择对应多个 Connector。

多个 Connector 和一个 Container 就形成了一个 Service,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非 Server 莫属了。所以整个 Tomcat 的生命周期由 Server 控制。

Server 要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到这个 Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。
在这里插入图片描述
在这里插入图片描述
Tomcat在内存中为这一连串组件产生对象,建立对象调用关系,调用它们各自的初始化和启动方法

1.启动Server
2.启动Service(启动Engine(多线程的启动host容器)和启动Context(Wrapper))
3.启动Connector

Lifecycle 实现原理

TODO 请求分析

TODO 线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值