Servlet(server applet 服务端小程序)是一种国际组织的协议、约定.
Tomcat只是针对Servlet协议的规范做了封装,其他这样的软件还有jetty等.
进入Tomcat本目录的bin文件夹下。
执行startup.bat或者
sudo ./startup.sh start
启动startup.bat脚本文件,就可以启动默认端口为8080的tomcat进程。
从上边分析可以看出来,最终执行的是 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 表示第二个… 那么 %* 就很好理解了, 代表所有参数.
从上面代码可以看出tomcat确实是一个纯java的程序,脚本最后都变成直接使用java命令执行程序,与我们普通写的java程序,没有什么不同。只不过由于 tomcat可以使用各种众多的模式(如debug,Security等),以及各种需要各种参数所以不得不使用脚本来执行。
Bootstrap类的main方法是整个Tomcat项目的入口,梳理一下就会发现,其实这个类的代码很少,也很清晰,它就是一个大Boss,指挥它的小兵Catalina类的实例完成各种操作。
在start()和main方法中都会调用init(),而在init()方法中又会调用initClassLoader进行初始化类类加载器。Bootstrap类有三个成员变量,分别指向三个类加载器:
通过跟踪代码可以发现这三个类加载器其实就是common一个,是通过读取conf目录下的catalina.properties的属性来创建的。而默认配置文件catalina和shared的值都是空。
对应的catalina.properties的配置如下:
类加载器的创建代码
catalina和shared都是以common为父类,且配置属性值都为空,因此这三个引用都是指向commonClassLoader的。
Tomcat中共有两个类用于Server的启动,Bootstrap和Catalina。作为Bootstrap的主要工作,是“代理”Catalina内部方法的调用。而这种方式又不像代理模式,更像是MyBatis中常用的一种设计模式——委派,SqlMapClient和另一个Executor后缀的类(具体类名称忘记了),二者定义的方法大致类似,但真正干活的趋势Executor类。在这里Bootstrap类的一个引用对象Catalina实例来执行各个方法的。Bootstrap其本身并不执行任何Server的启动/关闭操作。
看来大师级的人物都偏爱这种设计方法。
Bootstrap 的 main 方法最后会调用 org.apache.catalina.startup.Catalina 对象的 load 和 start 两个方法,那么就来看看这两个方法里面到底做了些什么。
在什么地方出现了Digester,查看下Catalina类的load方法。把注释、异常抛出、记录日志、流关闭、非空判断这些非逻辑的代码去掉,基本上就剩余下边的代码:
这段代码的作用:
一般来说 Java 里解析 xml 文件有两种方式:
Digester 本身是采用 SAX 的解析方式,在其上提供了一层包装,对于使用者更简便友好罢了。最早是在 struts 1 里面用的,后来独立出来成为 apache 的 Commons 下面的一个单独的子项目。Tomcat 里又把它又封装了一层,为了描述方便,直接拿 Tomcat 里的 Digester 建一个单独的
在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();
}
}
}
上边提到的Digester, 它会经过对 xml 文件的解析将会产生
等等一系列对象,这些对象从前到后前一个包含后一个对象的引用(一对一或一对多的关系)
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