? ? 我们谈到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 "%r" %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", "className"); 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 "className"); 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种调用:
?
? ? 从这些规则和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) { 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这个链路上的容器初始化和app加载已经完成。接下来的文章我们来看请求在容器里所走过的代码逻辑。
?
?
原文:http://wely.iteye.com/blog/2295222