Tomcat启动过程简述

Tomcat简介

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

启动过程

如何启动

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

分析 startup.bat 文件

代码解析


流程图

Tomcat启动过程简述_第1张图片

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

分析catalina.bat文件

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

脚本

Tomcat启动过程简述_第2张图片

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

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

Tomcat启动过程简述_第3张图片
Tomcat启动过程简述_第4张图片
Tomcat启动过程简述_第5张图片
Tomcat启动过程简述_第6张图片
Tomcat启动过程简述_第7张图片
Tomcat启动过程简述_第8张图片
Tomcat启动过程简述_第9张图片
Tomcat启动过程简述_第10张图片
Tomcat启动过程简述_第11张图片
Tomcat启动过程简述_第12张图片

流程

  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类图.jpgTomcat启动过程简述_第13张图片

main流程图

Bootstrap类main方法流程图.jpgTomcat启动过程简述_第14张图片

start()方法

Tomcat启动过程简述_第15张图片

类加载器

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

  • common
  • catalina
  • shared

通过跟踪代码可以发现这三个类加载器其实就是common一个,是通过读取conf目录下的catalina.properties的属性来创建的。而默认配置文件catalina和shared的值都是空。
对应的catalina.properties的配置如下:
在这里插入图片描述
类加载器的创建代码
Tomcat启动过程简述_第16张图片
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方法。把注释、异常抛出、记录日志、流关闭、非空判断这些非逻辑的代码去掉,基本上就剩余下边的代码:
Tomcat启动过程简述_第17张图片
这段代码的作用:

  • 创建一个 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 类里面并没有发现这两个方法:Tomcat启动过程简述_第18张图片

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

这里所说的组件可以将其理解为我们最开始分析 server.xml 时 xml 文件里的各个节点,父子关系也即 xml 文件里的父子节点。浏览下 LifecycleBase 的子类就会发现节点的实现类都是这个类的子类
Tomcat启动过程简述_第20张图片

那么还是从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

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

这个就是不同组件的关系
Tomcat启动过程简述_第21张图片

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启动过程简述_第22张图片
Tomcat启动过程简述_第23张图片
Tomcat在内存中为这一连串组件产生对象,建立对象调用关系,调用它们各自的初始化和启动方法

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

Lifecycle 实现原理

TODO 请求分析

TODO 线程

你可能感兴趣的:(Tomcat)