- 浏览: 239477 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
mhsjlove:
正则表达式的捕获组比这好用多了
高效率分离字符串到一个Map中 -
yk0025:
正在研究中。。。
Antx学习总结 -
asialee:
博主这块写的挺好的,我完全看了,不过我也写了一些关于编解码器的 ...
Netty源代码框架剖析( 二 ) -
he_wen:
不能够停止tomcat的话 kill -9 进程号 得到进程 ...
检查tomcat是否宕机 -
zhangyou1010:
catalina.sh stop 如果这个不能停止tomc ...
检查tomcat是否宕机
一、The Loader接口
在web应用程序中加载servlet和其他类时有一些规则。比如:在应用程序中加载一个servlet,被限制在the WEB-INF/classes目录以及子目录下。然而,servlet不会访问其他类,即使这些类包含运行在Tomcat中的JVM的CLASSPATH下。同时一个servlet限制了访问类库的目录(WEB-INF/lib目录)。
一个Tomcat Loader代表了一个web应用的loader而不是一个class loader. 一个loader一定要实现the org.apache.catalina.Loader接口。一个Loader实现一个自己定制的类加载器是由org.apache.catalina.loader.WebappClassLoader类代表的。只要你使用Loader接口中的getClassLoader方法你就能获得一个web Loader里面的类加载器。
该接口还有其他方法就是定义了repositories集合。一个web应用中的WEB-INF/classes和WEB-INF/lib目录将会被添加到respositories。The Loader接口中的addRepository方法是添加一个repository, 而findRepositories方法将会返回所有的repositories(以数组形式返回)。
一个Tomcat loader实现通常与一个context相关联,the Loader接口中的getContainer和setContainer方法就是为了与context 相关联。一个loader也支持重新部署(当一个或者多个类在context容器里修改了)。这种方式,一个servlet程序设计者重新编译了servlet类或者说一个受支持的类和新的类将会被重新加载(这种环境是不需要Tomcat重新启动的)。为了达到重新加载的目的,The Loader接口也有一个modified方法。实现一个loader里面,the modified方法一定要返回true因为一个或者多个类在制定的目录下已经发生修改了,这样重新加载时必须的。然而一个实现Loader本身不会加载,相反它会调用Context接口的reloade方法。Loader接口还有两个方法:setReloadable, getReloaderable,目的是决定Loader是否支持自动加载(热部署)。Context的一个标准的实现(the org.apache.catalina.core.StandardContext类将会在第十二章讨论),默认情况下,重新加载时不允许的。因此为了使一个context容器支持重新加载,你需要在Tomcat目录下的server.xml文件中添加一个Context元素,例如下面:
<Context path="/myApp" docBase="myApp" debug="0" reloadable="true"/>
同时,一个Loader实现需要告知是否需要委派给父亲类加载器。对于这样的目的,The Loader接口有提供了两个方法:getDelegate和setDelegate。
下面是Loader接口的代码:
package org.apache.catalina; import java.beans.PropertyChangeListener; public interface Loader { public ClassLoader getClassLoader(); public Container getContainer(); public void setContainer(Container container); public DefaultContext getDefaultContext(); public void setDefaultContext(DefaultContext defaultContext); public boolean getDelegate(); public void setDelegate(boolean delegate); public String getInfo(); public boolean getReloadable(); public void setReloadable(boolean reloadable); public void addPropertyChangeListener(PropertyChangeListener listener); public void addRepository(String repository); public String[] findRepositories(); public boolean modified(); public void removePropertyChangeListener(PropertyChangeListener listener); }
Catalina提供了the org.apache.catalina.loader.WebappLoader作为实现the Loader接口。对于这个类加载器,The WebappLoader对象包含了org.apache.catalina.loader.WebappClassLoader实例(该类继承了java.net.URLClassLoader类)。
注意:当容器invoke方法被调用,无论什么时候与loader相关联的容器都需要一个servlet类。容器首先会调用the Loader接口中的getClasssLoader方法老获得类加载器。容器然后就会调用类加载器中的loadClass方法目的是加载the servlet类。更加详细的讨论请大家快看第十一章“StandardWrapper”.
下面是关于Loader接口和实现的类图:
二、The Reloader接口
为了支持自动部署,一个类加载器的实现一定要实现org.apache.catalina.Reloader接口。
package org.apache.catalina.loader; public interface Reloader { public void addRepository(String repository); public String[] findRepositories(); public boolean modified(); }
The Reloader接口十分重要的方法就是modified方法,因为假设在web应用程序中一个servlet或者受支持的类已经修改,那么就要返回true. 在实现Reloader接口中的类加载器类中,The addRepository方法是用来添加一个repository(相当一添加一个规则),the findRepositories方法就是返回全部repositories的字符串数组。
二、The WebappLoader 类
The org.apache.catalina.loader.WebappLoader类实现了Loader接口并且代表了一个web应用的loader(主要负责加载web应用程序所需要的类)。WebappLoader类定义了一个成员变量( the org.apache.catalina.loader.WebappClassLoader类实例 )作为它的类加载器。像Catalina其他组件那样,WebappLoader类实现了 org.apache.catalina.Lifecycle,通过与之关联的容器来启动/关闭该组件。The WebappLoader类也实现了 java.lang.Runnable接口,它的目的就是创建一个独立的线程反复的调用class Loader中的modified方法。如果the modified方法返回了true,那么the WebappLoader实例通知与之关联的容器(在本应用程序是一个context)。类的重新加载是由the Context来实现的不是通过WebappLoader类实现。The Context组件到底做了什么请看第十二章的讨论,(StandardContext)。
当The WebappLoader类中的开始方法就会有许多任务要执行如:
1、创建一个class loader
2、设置规则(repositories)
3、设置类路径
4、设置权限
5、开启一个自动部署的线程
每个任务都会在下面的子部分详细讨论。
三、创建一个Class Loader
为了加载一个类,a WebappLoader实例绑定了一个内部的class loader. 你可能会想起前面讨论的Loader接口(仅仅提供了getClassLoader方法,确没有提供setClassLoader)。因此,你无法实例化一个 class loader传给The WebappLoader.The WebappLoader,它并没有和一个默认的class Loader绑定,那么就意味着它就无法绑定class loader了吗?
答案肯定说不,The WebappLoader提供了the getLoaderClasss和setLoaderClass方法目的就是获得和改变私有变量字符串loaderClass。这个变量是一个字符串(代表了the class loader的类名字)。默认情况下,the loaderclass值是org.apache.catalina.loader.WebappClassLoader. 如果你能够创建你自己的class Loader(扩展了WebappClassLoader),就调用setLoaderClass方法迫使你的WebappLoader使用你自己定制的类加载器。否则,当WebappLoader开始启动时,他就会调用自己的私有方法createClassLoader(目的是创建一个WebappClassLoader实例)。下面的方法如下:
private WebappClassLoader createClassLoader() throws Exception { Class clazz = Class.forName(loaderClass); WebappClassLoader classLoader = null; if (parentClassLoader == null) { // Will cause a ClassCast is the class does not extend WCL, but // this is on purpose (the exception will be caught and rethrown) classLoader = (WebappClassLoader) clazz.newInstance(); } else { Class[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); } return classLoader; }
可能使用了不同的class loader,除了使用了WebappClassLoader实例。然而,注意的是createClassLoader方法返回一个WebappClassLoader实例。因此,如果你想定制你自己的class loader且没有继承WebappClassLoader,这个方法会抛出一个异常。
classLoader = (WebappClassLoader) clazz.newInstance();
四、设置Repositories
The WebappLoader类中的start方法调用了setRepositories方法(目的的想添加repositories到class loader)。 The WEB-INF/classs 目录当做参数传给了class loader类中的addRepository方法,the WEB-INF/lib目录当做参数传给了class loader类中的setJarPath方法。这种方式,the class loader将能够在WEB-INF/classes目录和一个部署在WEB-INF/lib目录下的任何类库加载classes.
五、 设置类路径
这个任务执行是由the start方法调用了setClassPath方法在servlet context下赋值给了一个属性(该属性字符串包含了Jasper JSP compiler的类路径信息),这里就不在讨论。
六、设置权限
当运行的Tomcat运用了安全管理,那么setPermission方法给class loader添加权限(限制访问目录比如:WEB-INF/classes和WEB-INF/lib). 如果没有安全管理在使用,那么该方法就会立马返回。
七、开始一个自动部署的线程
WebLoader支持自动部署。如果一个类在web-inf.classes或者WEB-INF/lib目录下重新编译了,那么这个类就会在Tomcat不重新启动的前提下,自动的加载这个类。要达到这样的目的,WebappLoader要有一个线程(对每个资源,每个x秒钟)核实时间标记。x 在这儿是被定义的变量checkInterval的值,默认值是15,也就是意味着每个15秒就核实一下。The getCheckInterval和setCheckInterval方法被用来访问这个成员变量。
在Tomcat 4 中WebappLoader实现了java.lang.Runnable接口,目的是支持自动部署。下面是实现Runnable接口的方法:
public void run() { if (debug >= 1) log("BACKGROUND THREAD Starting"); // Loop until the termination semaphore is set while (!threadDone) { // Wait for our check interval threadSleep(); if (!started) break; try { // Perform our modification check if (!classLoader.modified()) continue; } catch (Exception e) { log(sm.getString("webappLoader.failModifiedCheck"), e); continue; } // Handle a need for reloading notifyContext(); break; } if (debug >= 1) log("BACKGROUND THREAD Stopping"); }
注意:在Tomcat 5中核实被修改的类任务是由org.apache.catalina.core.StandardContext类中的backgroundProcess方法执行。这个方法通过一个专门的线程(在 org.apache.catalina.core.ContainerBase类中)周期的调用,ContainerBase的子类是StanardContext. 在开启的专门线程,由内部类ContainerBackgroundProcessor实现了Runnable接口.
到了一个十分重要的部分,下面的run方法包含了一个while循环,跳出循环的条件就是布尔值started(WebappLoader已经启动了)变成false。The while循环做了下面几件事:
1、通过checkInternal变量控制休眠的周期
2、通过调用The WebappLoader实例变量class loader中的modified方法,来核实是否已经加载的类被修改了。如 果没有就继续。
3、如果一个类已经修改了,就会调用the notifyContext私有方法,请求the context(与The WebappLoader相关联)重新加载。
The notifyContext方法代码如下面所示:
private void notifyContext() { WebappContextNotifier notifier = new WebappContextNotifier(); (new Thread(notifier)).start(); }
The notifyContext方法没有直接调用The Context接口的reload方法。相反,它是实例化了一个内部类WebappContextNotifier并传递给线程对象然后再调用启动线程的start方法。这种方式,重新加载的功能将会以不同的线程来实现。下面是WebappContextNotifier类代码:
protected class WebappContextNotifier implements Runnable { /** * Perform the requested notification. */ public void run() { ((Context) container).reload(); } }
当WebappContextNotifer实例传给了一个线程,那么线程对象中的start方法将会被调用,The WebappContextNotifier实例中的run方法就会被执行。接着,the run方法就会调用Context接口的reload方法。你将会在第十二章看到org.apache.catalina.core.StandardContext类是如何实现the reload方法的。
八、The WebappClassLoader类
The org.apache.catalina.loader.WebappClassLoader类代表了the class loader,负责处理在web应用中的类的加载。WebappClassLoader继承了java.net.URLClassLoader类,这个类我们用来加载java Classes(在前面章节的应用程序中)。
WebappClassLoader优化设计以及进行安全的考虑。比如,它就会缓存前面已经加载好的类,来提高系统性能,它也缓存没有找到的类,如此这样下一次在加载同样的类时,the class loader就会抛出ClassNotFounException异常. WebappClassLoader会搜索存放在repositories下面的类以及具体的JAR文件。关于安全的考虑,WebappClassLoader类就不允许某些类被加载。这些类被存放在一个字符串数组triggers,数组里面有一个值:
private static final String[] triggers={
"javax.servlet.Servlet"
};
同时也不允许加载属于下面的这些包名和子包下,无法一开始就委派到the system class loader中去:
private static final String[] packageTriggers = { "javax", // Java extensions "org.xml.sax", // SAX 1 & 2 "org.w3c.dom", // DOM 1 & 2 "org.apache.xerces", // Xerces 1 & 2 "org.apache.xalan" // Xalan };
现在让我们看看这个类是怎样实现缓存功能和类的加载。
九、Caching
Tomcat为了到达更好的性能,已经加载的类存储到cache中,目的是为了下次请求时相同的类不再需要加载,直接从缓存中取。Caching是在本地上做的,也就是缓存是有WebappClassLoader实例进行管理。除此之外,the java.lang.ClassLoader维护了一个Vector (把已经加载的类存放在这里),目的是防止这些类被垃圾回收站销毁。在本应用程序中,caching是有父类管理。
可能被WebappClassLoader加载的每个类(不管类文件部署在WEB-INF/classes目录,还是该类来自一个JAR文件)要指向一个资源(resource).一个resource有org.apache.catalina.ResourceEntry类代表。一个ResourceEntry实例拥有许多内容如:类的字节数组,最后的修改时间,The Manifest(当资源是来自一个JAR文件)。
下面是ResourceEntry类的代码:
package org.apache.catalina.loader; import java.net.URL; import java.security.cert.Certificate; import java.util.jar.Manifest; public class ResourceEntry { public long lastModified = -1; public byte[] binaryContent = null; public Class loadedClass = null; public URL source = null; public URL codeBase = null; public Manifest manifest = null; public Certificate[] certificates = null; }
全部的cached资源存储在一个HashMap中名字叫做resourceEntries.The key是资源的名字。无法找到的资源就会存储到另外一个HashMap(叫做noyFoundResources).
十、加载Classes
但要加载一个类时,The WebappClassLoader类要运用以下规则:
1、首先要和售本地的cache,整个已经加载的classes一定要存放在cached中。
2、如果在本地cache中没有找到此类,那么就需要调用java.lang.ClassLoader类中的findLoadedClass方法,目的是核实cache。
3、如果在两个cache中都没有找到,那么就用system 类加载器目的是阻止web应用中的类覆盖J2EE中的类。
4、如果安全管理被使用,就要核实the class是否允许加载。如果不允许那么就会抛出一个异常ClassNotFoundException.
5、如果布尔值(委派标记)为true或者被加载的the class属于trigger包中的名字,那么就使用父类加载器加载the calss.如果父类加载器为null, 那么就是用system 类加载器。
6、加载来自当前的repositories中的类。
7、如果类在当前的repositories没有找到以及布尔值(委派标记)为false时,就是用父类加载器。如果父类加载器也为null,那么就只能使用system加载。
8、如果类仍然还是没有找到,那么就会抛出一个异常ClassNotFoundException.
十一、应用程序
本章的应用程序目的是想说明与Context相关的WebappLoader实例。the org.apche.catalina.core.StandardContext类是Context的一个标准的实现,因此在本应用程序就实例化了StandardContext类。然而,StandardContext会推迟到第十二章进行讨论。在这个阶段你不需要理解这个类的详细细节,而需要你理解仅仅是关于协调工作的触发事件,如:The START_EVENT、STOP_EVENT. The Listener一定要实现org.apache.catalina.lifecycle.LifecycleListener接口,然后调用The StandardContext类中的setConfigured方法。对于本应用程序,The listener是有SimpleContextConfig类代表的,下面显示详细的代码:
package ex08.pyrmont.core; import org.apache.catalina.Context; import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleListener; public class SimpleContextConfig implements LifecycleListener { public void lifecycleEvent(LifecycleEvent event) { if (Lifecycle.START_EVENT.equals(event.getType())) { Context context = (Context) event.getLifecycle(); context.setConfigured(true); } } }
呵呵。。这样就只需要你实例化StandardContext和SimpleContextConfig这两个类,然后就调用org,apache.catalina.Lifecycle接口中的addLifecycleListener方法目的是把后面的实例注册给StandardContext.Lifecycle接口我们在第六章是详细的讨论了。
除此之外,本应用程序也保留了前面章节的以下几个类:SimplePipeline,SimplWrapper , SimpleWrapperValve.
同时本应用程序使用了PrimitiveServlet和ModernServlet来进行测试,但是这次StandardContext强制这些servltes存放在WEB-INF/classes应用程序目录下。该应用目录的名称是myApp,当你第一次部署可下载的ZIP文件时该目录就应该被创建。为了告知StandardContext实例在哪个应用目录下查找,那么你必须把use.dir 属性值设置到叫做catalina.base的一个系统属性中,下面是其代码:
System.setProperty("catalina.base",System.getProperty("use.dir"));
事实上,在Bootstrap类中的main方法代码第一行实例化的是一个默认连接器:
Connector connector=new HttpConnector();
然后创建了两个wrapper(负责处理servlet),接着初始化他们,正如前面的章节的应用程序那样:
Wrapper wrapper1 = new SimpleWrapper(); wrapper1.setName("Primitive"); wrapper1.setServletClass("PrimitiveServlet"); Wrapper wrapper2 = new SimpleWrapper(); wrapper2.setName("Modern"); wrapper2.setServletClass("ModernServlet");
接着,有创建了StandardContext实例,在设置路径以及基于context的文档。
Context context = new StandardContext(); // StandardContext's start method adds a default mapper context.setPath("/myApp"); context.setDocBase("myApp");
上面的代码就如同Tomcat配置文件server.xml.
<Context path="/myApp" docBase="myApp"/>
然后,两个wrappers别添加到context,然后又给他们添加mapping,目的是为了Context查找the wrapper.
context.addChild(wrapper1); context.addChild(wrapper2); // context.addServletMapping(pattern, name); context.addServletMapping("/Primitive", "Primitive"); context.addServletMapping("/Modern", "Modern");
下一步,实例化一个listener,在把它注册到the Context中。
LifecycleListener listener=new SimpleContextConfig();
((Lifecycle)context).addLifecycleListener(listener);
在下一步,实例化一个WebappLoader,并与the Context相关联。
Loader loader=new WebappLoader();
context.setLoader(loader);
connector.setContainer(context); try { connector.initialize(); ((Lifecycle) connector).start(); ((Lifecycle) context).start();
下面的几行代码仅仅就是显示了资源 docBase的值以及在类加载器中的repositories.
之后,the context有与默认连接器相关联,连接器初始化,start方法被调用,接下来就是启动context容器。这就是把servlet容器放到一个service中。。。
// now we want to know some details about WebappLoader WebappClassLoader classLoader = (WebappClassLoader) loader.getClassLoader(); System.out.println("Resources' docBase: " + ((ProxyDirContext)classLoader.getResources()).getDocBase()); String[] repositories = classLoader.findRepositories(); for (int i=0; i<repositories.length; i++) { System.out.println(" repository: " + repositories[i]); }
当你运行该应用程序时,上面几行代码就是显示了docBase和repositories列表。
Resources's docBase: C:/HowTomcatWorks/myApp
repository: /WEB-INF/classes/
docBase的值会根据你的电脑显示的不一样,这是取决 于你的应用程序是放在哪里.
最后,应用程序一直等待,知道在控制台上按了Enter键,才会终止应用程序。
// make the application wait until we press a key. System.in.read(); ((Lifecycle) context).stop();
十二、运行应用程序
这里就不必多说
十三、 总结
The Web 应用的Loader或者一个简单的loader在catalina中的组件式尤为重要。一个Loader负责加载类因此要拥有一个内在的class loader.这个内在的class loader是自己定制的类(Tomcat通常在context应用中使用了某些规则),同时定制的class loader支持缓存以及热部署。。。
发表评论
-
tomcat的请求流程
2011-12-27 09:59 3684该文章是摘自张华的博 ... -
《How Tomcat Works》翻译(9) 之 会话管理续集
2010-12-19 22:02 1416八、The ManagerBase类 The Mana ... -
《How Tomcat Works》翻译(9) 之 会话管理
2010-12-08 13:37 1516第九章、会话管理 一、前言部分 ... -
《How Tomcat Works》翻译(6) 之 类加载器
2010-12-07 15:22 1682第八章 、Loader加载器 一、前言部分 ... -
《How Tomcat Works》翻译(5)之 日志续集
2010-12-07 10:22 1275七、The FileLogger类 The F ... -
《How Tomcat Works》翻译(5)之 日志
2010-12-06 21:09 1395第七章、日志 一、前言部分 A Logger ... -
《How Tomcat Works》翻译(4)之 生命周期
2010-12-05 22:19 1360第六章 生命周期 一 ... -
《How Tomcat Works》翻译(3)之Context容器
2010-12-05 19:09 1995一、The Context应用 在这章的第一个应用程 ... -
How Tomcat works 翻译(2) 之 Wrapper容器
2010-12-03 22:42 2313第五章、容器 ... -
How Tomcat works 翻译(1) 之 说明部分
2010-12-03 20:12 2139首先,由于本人读《How Tomcat works》 ...
相关推荐
在容器模块中,还涉及许多子模块,例如管理用户会话的管理模块和负责加载servlet类的加载器模块等。 对于不同版本的Tomcat,作者指出Tomcat 5相较于Tomcat 4有诸多改进。比如,Tomcat 5支持更高版本的Servlet和JSP...
《How Tomcat Works》是一份深入探讨Apache Tomcat工作原理的重要资源,包含了英文PDF文档、中文HTML翻译以及源代码,旨在帮助读者理解Tomcat服务器的内部运作机制。这份资料是IT从业者,特别是Java Web开发者、系统...
《How Tomcat Works》是一本深入探讨Apache Tomcat工作原理的书籍,中文版的提供使得国内开发者能够更方便地理解这一流行的开源Java Servlet容器。这本书不仅涵盖了Tomcat的基础知识,还详细解析了其内部机制,对于...
此外,书中还会讨论Tomcat的部署流程,如WAR文件的解压和加载,以及类加载器的工作方式,这对于理解类的加载和隔离至关重要。 在安全性方面,《How Tomcat Works》可能会涵盖如何设置SSL/TLS以实现HTTPS连接,保护...
- **加载器**:负责加载Servlet类。 #### 七、Tomcat 4与5的区别 Tomcat 4与5之间存在一些关键差异,主要包括: - **规范支持**:Tomcat 5支持Servlet 2.4和JSP 2.0规范,而Tomcat 4仅支持Servlet 2.3和JSP 1.2。...
《How Tomcat Works中文版》这本书是一本深入探讨Apache Tomcat服务器工作原理的专著。Apache Tomcat服务器,或简称为Tomcat,是世界上广泛使用的Java Servlet容器和JavaServer Pages(JSP)引擎,负责处理基于Java...
首先,从标题和描述我们可以得知,这个压缩包包含两部分内容:《HowTomcatWorks》的中文翻译和源码。这使得我们能够从理论和实践两个层面去了解Tomcat。中文版的书籍可以帮助中国开发者消除语言障碍,更深入地理解...
《How Tomcat Works》这本书深入浅出地介绍了Apache Tomcat这款广泛应用的Java Servlet容器的工作原理。Tomcat作为开源软件,是许多Web应用的基础,尤其在轻量级开发和测试环境中非常常见。以下是对Tomcat核心知识点...
4. **类加载机制**:Tomcat采用Web应用独立的类加载器结构,每个Web应用都有自己的ClassLoader,确保应用间类的隔离,防止冲突。 5. **线程模型**:Tomcat使用线程池来处理请求,通过调整线程池大小可以优化服务器...
在Servlet和JSP方面,《How Tomcat Works》会讲解它们的生命周期,包括加载、初始化、服务、销毁等阶段。Servlet容器如何根据web.xml配置文件来管理Servlet实例,以及JSP如何被编译成Servlet并执行,这些都会在书中...
《How Tomcat Works》是一本深入解析Apache Tomcat服务器内部工作原理的重要参考资料,它提供了对Tomcat架构的全面理解,包括其设计、配置和优化。这本书的中文版和英文版都为读者提供了便利,无论你是母语为中文...
HowTomcatWorks书籍课程实例工程与代码 书籍剖析了Tomcat 4.1.12和Tomcat 5.0.18--一个免费的、开源的、深受大众欢迎的、代号为Catalina的servlet容器,并讲解其容器的内部运行机制。通过迭代实现一个简化版软件来...
tomcat工作原理深入详解——HowTomcatWorks中文版.pdf
《How Tomcat Works》是一本深入解析Apache Tomcat工作原理的经典书籍,对于Java Web开发者来说,它是理解Tomcat内部机制的重要资源。这本书分为英文版和中文版,方便不同语言背景的读者阅读。同时,源码的提供使得...
《How Tomcat Works》是一本深入探讨Apache Tomcat工作原理的专业书籍,对于任何希望深入了解Java Servlet和JavaServer Pages (JSP)容器的人来说,都是一份宝贵的资源。Tomcat作为最流行的开源Servlet容器,其内部...