我们谈到tomcat时,第一印象是它是一种servlet容器,这个概念是相当抽象和本质的,我们仍然对tomcat的内幕很陌生。我们知道,tomcat由connector和container两大组件构成,connector在前面的文章已经介绍过了,今天我们就来看看container是怎么回事。
一、container基本结构
前文中有讲到,connector和container的初始化工作是由digester解析conf/server.xml来完成的,而在server.xml中已经告诉了我们container的基本结构。我们先来看看server.xml文件:
<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文件,我们可以很清晰的看到,server下包含了service(可有多个),service下包含了connector和engine,其实还可以包含executor(线程池)。engine正是与connector处在同一水平的容器,engine下面有host,下面给出tomcat模块组成图。
前面在模拟tomcat连接器时,正是将servlet容器的实例作为参数传入到连接器的setcontainer()方法中,这样连接器才能调用servlet容器的invoke()方法。
接下来给出一张大众化的container组件结构图。
通过代码可以知道,tomcat提供了一个container接口来抽象容器,并且细分了4种类型的容器,分别是engine、host、context和wrapper,对应不同的概念层次。
· engine:表示整个catalina的servlet引擎
· host:表示一个拥有数个上下文的虚拟主机
· context:表示一个web应用,一个context包含一个或多个wrapper
· wrapper:表示一个独立的servlet
engine是顶层容器,java程序能够运行是因为有引擎即jvm,很自然的,engine正是可以配置jvm(jvmroute="jvm1")。host是engine的子容器,context是host的子容器,wrapper是context的子容器,这4个接口的标准实现分别是standardengine、standardhost、standardcontext、standardwrapper,她们都在org.apache.catalina.core包类,分析tomcat容器正式主要分析这4个类。下图展示了container接口及其子接口和实现类的uml类图。
通过上面的两个图,我们知道了container的结构,那么container的初始化工作是怎么完成的呢?
二、container初始化
回到catalina类中,在load方法中调用了createstartdigester方法。
/**
* create and configure the digester we will be using for startup.
*/
protected digester createstartdigester() {
digester digester = new digester();
// initialize the digester
// configure the actions we will be using
// 如果遇到”server“元素起始符;则创建"org.apache.catalina.core.standardserv // er"的一个实例对象,并压入堆栈;如果"server"元素的"classname"属性存在,那么用 // 这个属性的值所指定的class来创建实例对象,并压入堆栈。
digester.addobjectcreate("server",
"org.apache.catalina.core.standardserver",
"classname");
// 从server.xml读取"server"元素的所有{属性:值}配对,用对应的setter方法将属性值 // 设置到堆栈顶层元素(server)。
digester.addsetproperties("server");
digester.addsetnext("server",
"setserver",
"org.apache.catalina.server");
digester.addobjectcreate("server/service",
"org.apache.catalina.core.standardservice",
digester.addsetproperties("server/service");
digester.addsetnext("server/service",
"addservice",
"org.apache.catalina.service");
digester.addobjectcreate("server/service/listener",
null, // must be specified in the element
digester.addsetproperties("server/service/listener");
digester.addsetnext("server/service/listener",
"addlifecyclelistener",
"org.apache.catalina.lifecyclelistener");
//executor
//connector
// add rulesets for nested elements
digester.addruleset(new namingruleset("server/globalnamingresources/"));
digester.addruleset(new engineruleset("server/service/"));
digester.addruleset(new hostruleset("server/service/engine/"));
digester.addruleset(new contextruleset("server/service/engine/host/"));
addclusterruleset(digester, "server/service/engine/host/cluster/");
digester.addruleset(new namingruleset("server/service/engine/host/context/"));
// when the 'engine' is found, set the parentclassloader.
digester.addrule("server/service/engine",
new setparentclassloaderrule(parentclassloader));
addclusterruleset(digester, "server/service/engine/cluster/");
return (digester);
}
从简化的源码中可以看到,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。
三、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
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) {
loader.backgroundprocess();
} catch (exception e) {
log.warn(sm.getstring("containerbase.backgroundprocess.loader", loader), e);
}
//
firelifecycleevent(lifecycle.periodic_event, null);
这个方法中比较重要的两个
loader.backgroundprocess():调用了载入器的webapploader的backgroundprocess方法,进入这个方法可以看到:
if (reloadable && modified()) {
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这个链路上的容器初始化和app加载已经完成。接下来的文章我们来看请求在容器里所走过的代码逻辑。
原文链接:[http://wely.iteye.com/blog/2295222]