文档章节

Tomcat源码分析----Container初始化与加载

圆梦巨人
 圆梦巨人
发布于 2017/07/12 10:52
字数 2113
阅读 30
收藏 0

摘要: 在本章节中,以下几个问题会被回答:web容器和servlet容器的区别是什么;在springMVC中的web.xml是什么时候加载到tomcat中的;tomcat是怎么加载我们的web服务的;tomcat是怎么实现的热部署;

在本章节中,以下几个问题会被回答:

  • web容器和servlet容器的区别是什么;
  • 在springMVC中的web.xml是什么时候加载到tomcat中的;
  • tomcat是怎么加载我们的web服务的;
  • tomcat是怎么实现的热部署; 1 Container基本结构 从上文中有讲到,Connector和Container的初始化工作是由Digester来解析conf/server.xml来完成的。而在server.xml中已经告诉我们了Container的基本结构。那么我们先来看看server.xml文件:

<?xml version='1.0' encoding='utf-8'?>

<Server port="8005" shutdown="SHUTDOWN"> …… <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> <Engine name="Catalina" defaultHost="localhost"> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm>

  <Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="true">
    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
           prefix="localhost_access_log." suffix=".txt"
           pattern="%h %l %u %t &quot;%r&quot; %s %b" />
  </Host>
</Engine>

</Service> </Server> 如果有接触过xml文件,那么我们可以很清晰的看到,在这个xml文件中,Server下包含了Service,Service下包含了Connector和Engine,Engin下包含了Host。是时候放出一张图了。 screenshot

2 Container初始化 2.1 Container组件构成 继续看看Container容器的结构图,这个图很大众化,是个Container容器介绍的文章都会有: screenshot

通过代码,我们会知道Container是一个接口,Container分成4个级别的容器,而且这四个级别容器的关系为父子关系。真正的顶层容器是Engine。Container作为容器,存在几个概念上的级别:

Engine 表示一个Servlet引擎,它可以包含一个或多个子容器,比如Host或者Context容器; Host 表示一台虚拟的主机,它可以包含一系列Context容器; Context 表示一个唯一的ServletContext,一个 Context 对应一个 Web 工程,它可以包含一个 或多个Wrapper容器; Wrapper 表示一个独立的Servlet定义,即Wrapper本质就是对Servlet进行了一层包装。 通过这个图我们知道了Container的结构,那么Container的初始化工作是怎么完成的呢?

继续回到Catalina类中,在load方法中调用了createStartDigester方法。

protected Digester createStartDigester() { ……

////如果遇到”Server“元素起始符;则创建"org.apache.catalina.core.StandardServer"的一个实例对象,并压入堆栈;如果"Server"元素的"className"属性存在,那么用这个属性的值所指定的class来创建实例对象,并压入堆栈。
digester.addObjectCreate("Server",
                         "org.apache.catalina.core.StandardServer",
                         "className");

//从server.xml读取"Server"元素的所有{属性:值}配对,用对应的Setter方法将属性值设置到堆栈顶层元素(Server)。
digester.addSetProperties("Server");

//遇到"Server"结束符时,调用“次顶层元素(Catalina)”的"setServer"方法,
digester.addSetNext("Server",
                    "setServer",
                    "org.apache.catalina.Server");
……
//遇到标签使用规则
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
……

}

从简化的源码中可以看到,digester对server.xml设置的标签动作有5种调用:

addObjectCreate:遇到起始标签的元素,初始化一个实例对象入栈 addSetProperties:遇到某个属性名,使用setter来赋值 addSetNext:遇到结束标签的元素,调用相应的方法 addRule:调用rule的begin 、body、end、finish方法来解析xml,入栈和出栈给对象赋值 addRuleSet:调用addRuleInstances来解析xml标签 从这些规则和xml中可以看到,Calatina的Server对象是StandardServer。 StandardService包含了多个Connector(xml中有2个connector)和一个StandardEngine Container。 StandardEngine包含了一个Host Container

2.2 Context容器加载web服务与热部署 从confg/server.xml中我们可以看到Server的容器的初始化只有Engine和Host,那么Context是什么时候初始化的呢,是怎么加载我们的web application,怎么实现的热部署呢? 先说结论,tomcat的Engine会启动一个线程,该线程每10s会发送一个发送一个事件,监听到该事件的部署配置类会自动去扫描webapp文件夹下的war包,将其加载成一个Context,即启动一个web服务。

OK,回过头看conf/server.xml和createStartDigester,添加了HostRuleSet,进入在HostRuleSet类中,可以看到这么一行代码:

digester.addRule(prefix + "Host", new LifecycleListenerRule ("org.apache.catalina.startup.HostConfig", "hostConfigClass")); 继续进入LifecycleListenerRule类可以发现,在监听事件中增加了HostConfig类的对象,也就是说StandardHost中新增了一个HostConfig监听器。

再回过头来进入StandardEngine的starInternal方法supper.startInternal(父类ContainerBase)中有这行代码:

threadStart(); 进入后发现开启了一个线程,调用ContainerBackgroundProcessor这个的run方法,而这个run方法可以看到

protected class ContainerBackgroundProcessor implements Runnable {

[@Override](https://my.oschina.net/u/1162528)
public void run() {
    ……
    try {
        while (!threadDone) {
            try {
                Thread.sleep(backgroundProcessorDelay * 1000L);//在StandardEngine中构造方法设置默认backgroundProcessorDelay=10,即10s调用一次
            } catch (InterruptedException e) {
                // Ignore
            }
            if (!threadDone) {
                ……
                processChildren(parent, cl);
            }
        }
    } 
    ……
}

} 也就是说每该线程每10s会调用一次processChildren,继续跟踪该方法,会看到调用其子容器Engine、Host、Context、Wrapper各容器组件及与它们相关的其它组件的backgroundProcess方法。

@Override public void backgroundProcess() { if (loader != null) { try { loader.backgroundProcess(); } catch (Exception e) { log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);
} } …… fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null); }

这个方法中比较重要的两个 loader.backgroundProcess():调用了载入器的WebappLoader的backgroundProcess方法,进入这个方法可以看到:

public void backgroundProcess() { if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (container instanceof StandardContext) { ((StandardContext) container).reload(); } } finally { if (container.getLoader() != null) { Thread.currentThread().setContextClassLoader (container.getLoader().getClassLoader()); } } } else { closeJARs(false); } } 看判断条件reloadable和modified(),reloadable即为是否开启热部署,而modified()则是当前文件是否有修改的判断,当开启了热部署且有修改就会调用Context的reload方法进行重加载,实现web服务的热部署

fireLifecycleEvent:对容器的监听对象发送Lifecycle.PERIODIC_EVENT事件,调用LifecycleListener的lifecycleEvent。

public void fireLifecycleEvent(String type, Object data) {

LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
LifecycleListener interested[] = listeners;
for (int i = 0; i < interested.length; i++)
    interested[i].lifecycleEvent(event);

} 好的,前面说到StandardHost通server.xml配置了HostConfig监听器,那么进入HostConfig查看对该事件的响应方法lifecycleEvent

public void lifecycleEvent(LifecycleEvent event) {

// Identify the host we are associated with
try {
    host = (Host) event.getLifecycle();
    if (host instanceof StandardHost) {
        setCopyXML(((StandardHost) host).isCopyXML());
        setDeployXML(((StandardHost) host).isDeployXML());
        setUnpackWARs(((StandardHost) host).isUnpackWARs());
        setContextClass(((StandardHost) host).getContextClass());
    }
} catch (ClassCastException e) {
    log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
    return;
}

// 看事件与其对应的方法调用
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
    check();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
    start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
    stop();
}

} 可以看到Lifecycle.PERIODIC_EVENT事件会调用其check方法。

protected void check() { if (host.getAutoDeploy()) {//这个条件对应这server.xml的Host配置的autoDeploy="true" DeployedApplication[] apps = deployed.values().toArray(new DeployedApplication[0]); for (int i = 0; i < apps.length; i++) { if (!isServiced(apps[i].name)) //资源查找 checkResources(apps[i], false); } if (host.getUndeployOldVersions()) { checkUndeploy(); } //部署 deployApps(); } } 很显然,如果server.xml的Host配置了能够自动部署,那么会调用deployApps方法。也就是说tomcat每10s会调用一次deployApps,完web application的部署

protected void deployApps() {

File appBase = appBase();
File configBase = configBase();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);

} 可以看到可以通过xml,war包等直接部署!

protected void deployDescriptor(ContextName cn, File contextXml) { …… Context context = null; …… Class<?> clazz = Class.forName(host.getConfigClass());//默认值:ContextConfig LifecycleListener listener = (LifecycleListener) clazz.newInstance(); context.addLifecycleListener(listener); …… host.addChild(context); …… } 而部署的过程,其实就是创建了Context对象,并添加到Host中。 此外从HostConfig部署Contex的方法中可以看到,有3中方式部署war包:

1 在server.xml的Host标签中声明Context标签 2 将war包放入webapps中 3 context.xml配置方式 至此,我们已经知道了Engine、Host、Context的加载了,同时也知道了tomcat是怎么加载我们的web服务,是怎么实现的热部署。那么接下来就剩下最后一个Wrapper的加载了。 很捉急,在server.xml中没有关于Wrapper的初始化加载,那么在哪里呢? 同样回到,上面的deployApps()方法中,在其三种部署方式中都有一节代码

Class<?> clazz = Class.forName(host.getConfigClass());//默认值:ContextConfig LifecycleListener listener = (LifecycleListener) clazz.newInstance(); context.addLifecycleListener(listener); 这段代码的作用是给Context容器添加了ContextConfig监听器。而在Context的startInternal方法中,发送了监听事件:

fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); ContextConfig监听到该事件,调用configureStart方法,在该方法中调用webConfig(),webConfig完成web.xml解析,生成servlet、filter等信息,并配置加载Wrapper。 通过对ContextConfig的分析可以知道,Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。

好了,到了现在,从Engine---Host---Contex----Wrapper这个链路上的容器初始化已经完成。接下来看看Connector的初始化与启动过程。

© 著作权归作者所有

圆梦巨人
粉丝 13
博文 176
码字总数 148989
作品 0
西城
程序员
私信 提问
Tomcat 源码分析(-)启动过程分析

前面几篇文章分别介绍了Tomcat的安装、优化和框架,本文主要用于分析Tomcat源码启动过程,研究一个框架最好的着手方式可能就是研究它的启动过程,因为在这过程中我们可以看到它内部的层次关系...

AaronSheng
2016/11/28
143
0
Container容器-tomcat6.x源码阅读

2013-09-21 容器是tomcat的基础,tomcat通过容器来管理和维护tomcat组件,最外层的容器是Server,最内层的容器是Wrapper,容器提供了一些基础的功能,例如添加父容器,子容器,共享数据,数据...

douglaswei
2013/09/29
220
0
Tomcat在SpringBoot中是如何启动的

前言 我们知道SpringBoot给我们带来了一个全新的开发体验,我们可以直接把web程序达成jar包,直接启动,这就得益于SpringBoot内置了容器,可以直接启动,本文将以Tomcat为例,来看看SpringB...

木木匠
08/12
8.2K
24
Tomcat源码解读系列(二)——Tomcat的核心组成和启动过程

如果要用一张图来形象展现一下Tomcat组成的话,整个Tomcat的组成可以如下图所示: Tomcat在接收到用户请求时,将会通过以上组件的协作来给最终用户产生响应。 首先是最外层的Server和Service...

heroShane
2014/02/07
171
0
Tomcat源码分析

下面谈谈我对Tomcat架构的理解 总体架构: 1、面向组件架构 2、基于JMX 3、事件侦听 1)面向组件架构 tomcat代码看似很庞大,但从结构上看却很清晰和简单,它主要由一堆组件组成,如Server、...

五大三粗
2015/09/28
173
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周六乱弹 —— 早上儿子问我他是怎么来的

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @凉小生 :#今日歌曲推荐# 少点戾气,愿你和这个世界温柔以待。中岛美嘉的单曲《僕が死のうと思ったのは (曾经我也想过一了百了)》 《僕が死の...

小小编辑
今天
1K
13
Excption与Error包结构,OOM 你遇到过哪些情况,SOF 你遇到过哪些情况

Throwable 是 Java 中所有错误与异常的超类,Throwable 包含两个子类,Error 与 Exception 。用于指示发生了异常情况。 Java 抛出的 Throwable 可以分成三种类型。 被检查异常(checked Exc...

Garphy
今天
38
0
计算机实现原理专题--二进制减法器(二)

在计算机实现原理专题--二进制减法器(一)中说明了基本原理,现准备说明如何来实现。 首先第一步255-b运算相当于对b进行按位取反,因此可将8个非门组成如下图的形式: 由于每次做减法时,我...

FAT_mt
昨天
40
0
好程序员大数据学习路线分享函数+map映射+元祖

好程序员大数据学习路线分享函数+map映射+元祖,大数据各个平台上的语言实现 hadoop 由java实现,2003年至今,三大块:数据处理,数据存储,数据计算 存储: hbase --> 数据成表 处理: hive --> 数...

好程序员官方
昨天
61
0
tabel 中含有复选框的列 数据理解

1、el-ui中实现某一列为复选框 实现多选非常简单: 手动添加一个el-table-column,设type属性为selction即可; 2、@selection-change事件:选项发生勾选状态变化时触发该事件 <el-table @sel...

everthing
昨天
20
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部