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

我们谈到tomcat时,第一印象是它是一种servlet容器,这个概念是相当抽象和本质的,我们仍然对tomcat的内幕很陌生。我们知道,tomcat由Connector和Container两大组件构成,Connector在前面的文章已经介绍过了,今天我们就来看看Container是怎么回事。

 

一、Container基本结构

    前文中有讲到,Connector和Container的初始化工作是由Digester解析conf/server.xml来完成的,而在server.xml中已经告诉了我们Container的基本结构。我们先来看看server.xml文件:

 

Xml代码   收藏代码
  1. <Server port="8005" shutdown="SHUTDOWN">  
  2.   <Service name="Catalina">  
  3.     <Connector port="8080" protocol="HTTP/1.1"  
  4.                connectionTimeout="20000"  
  5.                redirectPort="8443" />  
  6.     <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />  
  7.     <Engine name="Catalina" defaultHost="localhost">  
  8.       <Realm className="org.apache.catalina.realm.LockOutRealm">  
  9.         <Realm className="org.apache.catalina.realm.UserDatabaseRealm"  
  10.                resourceName="UserDatabase"/>  
  11.       </Realm>  
  12.   
  13.       <Host name="localhost"  appBase="webapps"  
  14.             unpackWARs="true" autoDeploy="true">  
  15.         <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"  
  16.                prefix="localhost_access_log." suffix=".txt"  
  17.                pattern="%h %l %u %t &quot;%r&quot; %s %b" />  
  18.       </Host>  
  19.     </Engine>  
  20.   </Service>  
  21. </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方法。

 

Java代码   收藏代码
  1. /** 
  2.  * Create and configure the Digester we will be using for startup. 
  3.  */  
  4. protected Digester createStartDigester() {  
  5.     Digester digester = new Digester();  
  6.     // Initialize the digester  
  7.   
  8.     // Configure the actions we will be using  
  9.     // 如果遇到”Server“元素起始符;则创建"org.apache.catalina.core.StandardServ        // er"的一个实例对象,并压入堆栈;如果"Server"元素的"className"属性存在,那么用        // 这个属性的值所指定的class来创建实例对象,并压入堆栈。  
  10.     digester.addObjectCreate("Server",  
  11.                              "org.apache.catalina.core.StandardServer",  
  12.                              "className");  
  13.     // 从server.xml读取"Server"元素的所有{属性:值}配对,用对应的Setter方法将属性值        // 设置到堆栈顶层元素(Server)。  
  14.     digester.addSetProperties("Server");  
  15.     digester.addSetNext("Server",  
  16.                         "setServer",  
  17.                         "org.apache.catalina.Server");  
  18.   
  19.     digester.addObjectCreate("Server/Service",  
  20.                              "org.apache.catalina.core.StandardService",  
  21.                              "className");  
  22.     digester.addSetProperties("Server/Service");  
  23.     digester.addSetNext("Server/Service",  
  24.                         "addService",  
  25.                         "org.apache.catalina.Service");  
  26.   
  27.     digester.addObjectCreate("Server/Service/Listener",  
  28.                              null// MUST be specified in the element  
  29.                              "className");  
  30.     digester.addSetProperties("Server/Service/Listener");  
  31.     digester.addSetNext("Server/Service/Listener",  
  32.                         "addLifecycleListener",  
  33.                         "org.apache.catalina.LifecycleListener");  
  34.   
  35.     //Executor  
  36.     //Connector  
  37.   
  38.     // Add RuleSets for nested elements  
  39.     digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));  
  40.     digester.addRuleSet(new EngineRuleSet("Server/Service/"));  
  41.     digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));  
  42.     digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));  
  43.     addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");  
  44.     digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));  
  45.   
  46.     // When the 'engine' is found, set the parentClassLoader.  
  47.     digester.addRule("Server/Service/Engine",  
  48.                      new SetParentClassLoaderRule(parentClassLoader));  
  49.     addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");  
  50.   
  51.     return (digester);  
  52.   
  53. }  

    从简化的源码中可以看到,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类中,可以看到这么一行代码:

 

Java代码   收藏代码
  1. digester.addRule(prefix + "Host"new LifecycleListenerRule  
  2.               ("org.apache.catalina.startup.HostConfig""hostConfigClass"));  

    继续进入LifecycleListenerRule类可以发现,在监听事件中增加了HostConfig类的对象,也就是StandardHost中新增了一个HostConfig监听器。再回过头来进入StandardEngine的starInternal方法supper.startInternal(父类ContainerBase)中有这行代码:

    threadStart();

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

 

Java代码   收藏代码
  1. protected class ContainerBackgroundProcessor implements Runnable {  
  2.     @Override  
  3.     public void run() {  
  4.         //  
  5.         try {  
  6.             while (!threadDone) {  
  7.                 try {  
  8.                     Thread.sleep(backgroundProcessorDelay * 1000L);//在StandardEngine中构造方法设置默认backgroundProcessorDelay=10,即10s调用一次  
  9.                 } catch (InterruptedException e) {  
  10.                     // Ignore  
  11.                 }  
  12.                 if (!threadDone) {  
  13.                     //  
  14.                     processChildren(parent, cl);  
  15.                 }  
  16.             }  
  17.         }   
  18.         //  
  19.     }  
  20. }  

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

 

Java代码   收藏代码
  1. @Override  
  2. public void backgroundProcess() {  
  3.     if (loader != null) {  
  4.         try {  
  5.             loader.backgroundProcess();  
  6.         } catch (Exception e) {  
  7.             log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);                  
  8.         }  
  9.     }  
  10.     //  
  11.     fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);  
  12. }  

这个方法中比较重要的两个

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

 

Java代码   收藏代码
  1. public void backgroundProcess() {  
  2.     if (reloadable && modified()) {  
  3.         try {  
  4.             Thread.currentThread().setContextClassLoader  
  5.                 (WebappLoader.class.getClassLoader());  
  6.             if (container instanceof StandardContext) {  
  7.                 ((StandardContext) container).reload();  
  8.             }  
  9.         } finally {  
  10.             if (container.getLoader() != null) {  
  11.                 Thread.currentThread().setContextClassLoader  
  12.                     (container.getLoader().getClassLoader());  
  13.             }  
  14.         }  
  15.     } else {  
  16.         closeJARs(false);  
  17.     }  
  18. }  

看判断条件reloadable和modified(),reloadable即为是否开启热部署,而modified()则是当前文件是否有修改的判断,当开启了热部署且有修改就会调用Context的reload方法进行重加载,实现web服务的**热部署**。

 

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

 

Java代码   收藏代码
  1. public void fireLifecycleEvent(String type, Object data) {  
  2.     LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);  
  3.     LifecycleListener interested[] = listeners;  
  4.     for (int i = 0; i < interested.length; i++)  
  5.         interested[i].lifecycleEvent(event);  
  6.   
  7. }  

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

 

Java代码   收藏代码
  1. public void lifecycleEvent(LifecycleEvent event) {  
  2.   
  3.     // Identify the host we are associated with  
  4.     try {  
  5.         host = (Host) event.getLifecycle();  
  6.         if (host instanceof StandardHost) {  
  7.             setCopyXML(((StandardHost) host).isCopyXML());  
  8.             setDeployXML(((StandardHost) host).isDeployXML());  
  9.             setUnpackWARs(((StandardHost) host).isUnpackWARs());  
  10.             setContextClass(((StandardHost) host).getContextClass());  
  11.         }  
  12.     } catch (ClassCastException e) {  
  13.         log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);  
  14.         return;  
  15.     }  
  16.   
  17.     // 看事件与其对应的方法调用  
  18.     if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {  
  19.         check();  
  20.     } else if (event.getType().equals(Lifecycle.START_EVENT)) {  
  21.         start();  
  22.     } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {  
  23.         stop();  
  24.     }  
  25. }  

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

 

Java代码   收藏代码
  1. protected void check() {  
  2.     if (host.getAutoDeploy()) {//这个条件对应这server.xml的Host配置的autoDeploy="true"  
  3.         DeployedApplication[] apps =  
  4.             deployed.values().toArray(new DeployedApplication[0]);  
  5.         for (int i = 0; i < apps.length; i++) {  
  6.             if (!isServiced(apps[i].name))  
  7.                 //资源查找  
  8.                 checkResources(apps[i], false);  
  9.         }  
  10.         if (host.getUndeployOldVersions()) {  
  11.             checkUndeploy();  
  12.         }  
  13.         //部署  
  14.         deployApps();  
  15.     }  
  16. }  

很显然,如果server.xml的Host配置了能够自动部署,那么会调用deployApps方法。也就是说tomcat每10s会调用一次deployApps,完**web application的部署**。

 

Java代码   收藏代码
  1. protected void deployApps() {  
  2.     File appBase = appBase();  
  3.     File configBase = configBase();  
  4.     String[] filteredAppPaths = filterAppPaths(appBase.list());  
  5.     // Deploy XML descriptors from configBase  
  6.     deployDescriptors(configBase, configBase.list());  
  7.     // Deploy WARs  
  8.     deployWARs(appBase, filteredAppPaths);  
  9.     // Deploy expanded folders  
  10.     deployDirectories(appBase, filteredAppPaths);  
  11. }  

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

 

Java代码   收藏代码
  1. protected void deployDescriptor(ContextName cn, File contextXml) {  
  2.     //  
  3.     Context context = null;  
  4.     //  
  5.     Class<?> clazz = Class.forName(host.getConfigClass());//默认值:ContextConfig  
  6.     LifecycleListener listener =  
  7.         (LifecycleListener) clazz.newInstance();  
  8.     context.addLifecycleListener(listener);  
  9.     //  
  10.     host.addChild(context);  
  11.     //  
  12. }  

 

而部署的过程,其实就是创建了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()方法中,在其三种部署方式中都有一节代码

 

Java代码   收藏代码
  1. Class<?> clazz = Class.forName(host.getConfigClass());//默认值:ContextConfig  
  2. LifecycleListener listener = (LifecycleListener) clazz.newInstance();  
  3. context.addLifecycleListener(listener);  

这段代码的作用是给Context容器添加了ContextConfig监听器。而在Context的startInternal方法中,发送了监听事件:

 

Java代码   收藏代码
  1. 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]

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值