转自:http://blog.csdn.net/chen77716/article/details/34790
在研究Tomcat之前,一般是借用现有的UML工具分析Tomcat整体结构,但要分析Tomcat的流程就必须从分析Tomcat的StartUp入手。Tomcat的启动是从解析bat文件开始,bat文件最终调用org.apache.catalina.startup.Bootstrap开始类的加载。
一.Tomcat的ClassLoader:
TOMCAT自己的类载入器(ClassLoader)
+---------------------------+
| Bootstrap |
| | |
| System |
| | |
| Common |
| / / |
| Catalina Shared |
+---------------------------+
其中:
- Bootstrap - 载入JVM自带的类和$JAVA_HOME/jre/lib/ext/*.jar
- System
1.载入$CATALINA_HOME/bin/bootstrap.jar 初始化Tomcat,执行Main方法
2. $JAVA_HOME/lib/tools.jar Sun的工具类,包括编译Jsp为Servlet的工具类
- Common 这个目录下的类虽然对TOMCAT和所有的WEB APP都可见.但是Web App的类不应该
放在这个目录下,所有未打包的Class都在$CATALINA_HOME/common/classes下,所
有打包的jar都在
$CATALINA_HOME/commons/endorsed和$CATALINA_HOME/common/lib下,默认情况会
包含以下几个包:
1. jndi.jar JNDI API接口,这个包仅在Java1.2时候装入,1.3以后的版本JDK已
自动装入.
2. naming-common.jar JNDI接口实现类,Tomcat用这些类在内存中使用Context.
3. naming-resources.jar JNDI实现,Tomcat用它们定位Web App的静态资源.
4. servlet.jar Servlet,Jsp API
5. xerces.jar XML解析器,特定的Web App可以在自己的/WEB-INF/lib 中覆盖.
- Catalina 装入Tomcat实现所有接口的类,这些类对Web App是完全不可见的,所有未打包的类在
$CATALINA_HOME/server/classes所有jar包在$CATALINA_HOME/server/lib下.一
般情况该ClassLoader将Load下面几个包:
1. catalina.jar Servlet容器的Tomcat实现包
2. jakarta-regexp-X.Y.jar 正则表达式,请求过滤时使用
3. servlets-xxxxx.jar Servlet支持包
4. tomcat-coyote.jar Tomcat的Coyote连接实现包
5. tomcat-jk.jar Web Server绑定包,允许Tomcat绑定Apache等作为Web Server
6. tomcat-jk2.jar 功能同上
7. tomcat-util.jar Tomcat工具类,可能被一些Connector用到
8. tomcat-warp.jar 用于Apache Server包
- Shared 载入所有WEB APP都可见的类,对TOMCAT不可见. 所有未打包的类在
$CATALINA_HOME/shared/classes所有jar包在$CATALINA_HOME /lib下.
默认情况包含下面几个包:
1. jasper-compiler.jar Jsp编译器,编译Jsp为Servlet
2. jasper-runtime.jar Jsp(已编译成Servlet)运行支持包
3. naming-factory.jar 支持Web App使用JNDI的封装包
-WebAppX Web App ClassLoader,当Web App被部署是该ClassLoader被创建.所有class都在
WEB-INF/classes下,所有jar在WEB-INF/lib下.
特别注意WEB APP自己的ClassLoader的实现与众不同:
它先试图从WEB APP自己的目录里载入,如果失败则请求父ClassLoader的代理
这样可以让不同的WEB APP之间的类载入互不干扰.另,Tomcat Server使用的是Catalina
ClassLoader,一般的Web App使用的是WebApp ClassLoader.
二. org.apache.catalina.startup.Bootstrap
该类是Tomcat的执行入口点,我们着重分析下面两个方法:
1. initClassLoaders,创建ClassLoader层次.
private void initClassLoaders() {
try {
ClassLoaderFactory.setDebug(debug);
//创建common ClassLoader,没有父ClassLoader
commonLoader = createClassLoader("common", null);
//创建catalina ClassLoader,父ClassLoader为common
catalinaLoader = createClassLoader("server", commonLoader);
//创建shared ClassLoader, 父ClassLoader为common
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
log("Class loader creation threw exception", t);
System.exit(1);
}
}
2. createClassLoader,负责具体的创建工作
在$CATALINA_HOME/conf/catalina.properties中定义了common, server, shared
ClassLoader载入类的路径及一些包的安全权限.
//common载入类的路径
common.loader=${catalina.home}/common/classes,
${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
//server载入类的路径
server.loader=${catalina.home}/server/classes,
${catalina.home}/server/lib/*.jar
//shared载入类的路径
shared.loader=${catalina.base}/shared/classes,
${catalina.base}/shared/lib/*.jar
/**
*param name:Load Name
*param parent:父Loader
*classLoader的资源分三种:
*1.未打包的classes,一般是一个目录
*2.打包的jar目录
*3.网络资源,一般是网上的一个jar包 (Applet经常用到这样的loader)
*/
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
//从catalina.properties中取得改Loader的配置信息
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
//classes目录
ArrayList unpackedList = new ArrayList();
//jar目录
ArrayList packedList = new ArrayList();
//网络路径指定的包
ArrayList urlList = new ArrayList();
StringTokenizer tokenizer = new StringTokenizer(value, ",");
//当前Loader该装载的类
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken();
// Check for a JAR URL repository
try {
//如果是网络路径追加url
urlList.add(new URL(repository));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// 本地路径
boolean packed = false;
//${catalina.home}
if (repository.startsWith(CATALINA_HOME_TOKEN)) {
repository = getCatalinaHome()
+ repository.substring(CATALINA_HOME_TOKEN.length());
//${catalina.base}
} else if (repository.startsWith(CATALINA_BASE_TOKEN)) {
repository = getCatalinaBase()
+ repository.substring(CATALINA_BASE_TOKEN.length());
}
/**经过上述操作,把catalina.properties里的路径替换成绝对路径*/
//如果是jar文件路径
if (repository.endsWith("*.jar")) {
packed = true;
repository = repository.substring
(0, repository.length() - "*.jar".length());
}
if (packed) {
packedList.add(new File(repository));
} else {
unpackedList.add(new File(repository));
}
}
File[] unpacked = (File[]) unpackedList.toArray(new File[0]);
File[] packed = (File[]) packedList.toArray(new File[0]);
URL[] urls = (URL[]) urlList.toArray(new URL[0]);
//调用Factory的方法创建ClassLoader
return ClassLoaderFactory.createClassLoader
(unpacked, packed, urls, parent);
}
三. ClassLoaderFactory
ClassLoaderFactory是用于创建ClassLoader的工厂类,这个类比较简单.
//参数含义不再说明,参看上面的分析
public static ClassLoader createClassLoader(File unpacked[],
File packed[],
URL urls[],
ClassLoader parent)
throws Exception {
if (debug >= 1)
log("Creating new class loader");
// Construct the "class path" for this class loader
ArrayList list = new ArrayList();
// 通过class目录构造file协议的url,并追加的list
if (unpacked != null) {
for (int i = 0; i < unpacked.length; i++) {
File file = unpacked[i];
if (!file.exists() || !file.canRead())
continue;
if (debug >= 1)
log(" Including directory or JAR "
+ file.getAbsolutePath());
URL url = new URL("file", null,
file.getCanonicalPath() + File.separator);
list.add(url.toString());
}
}
//取出所有jar目录里的jar文件,逐一构造url,并追加的list
if (packed != null) {
for (int i = 0; i < packed.length; i++) {
File directory = packed[i];
if (!directory.isDirectory() || !directory.exists() ||
!directory.canRead())
continue;
String filenames[] = directory.list();
for (int j = 0; j < filenames.length; j++) {
String filename = filenames[j].toLowerCase();
if (!filename.endsWith(".jar"))
continue;
File file = new File(directory, filenames[j]);
if (debug >= 1)
log(" Including jar file " + file.getAbsolutePath());
URL url = new URL("file", null,
file.getCanonicalPath());
list.add(url.toString());
}
}
}
//追加网络路径的资源
if (urls != null) {
for (int i = 0; i < urls.length; i++) {
list.add(urls[i].toString());
}
}
//调用StandardClassLoader创建实际的Loader
String array[] = (String[]) list.toArray(new String[list.size()]);
StandardClassLoader classLoader = null;
if (parent == null)
classLoader = new StandardClassLoader(array);
else
classLoader = new StandardClassLoader(array, parent);
classLoader.setDelegate(true);
return (classLoader);
}
四. StandardClassLoader
StandardClassLoader继承了URLClassLoader, URLClassLoader类具有从硬盘目录装载类,或从本地或远程装载jar文件的能力.这个类也实现了Reloader接口,提供了自动重新装载类的功能.我们主要看这个类以下及个方法:
1. 构造函数StandardClassLoader
/**
* @param repositories url数组,见上分析
* @param parent 父loader
*/
public StandardClassLoader(String repositories[], ClassLoader parent) {
//调用父类的构造函数
//父类的构造函数将用转换后的repositories生成URLClassPath的实例
//ucp是类成员变量ucp= new URLClassPath(urls);
// URLClassPath是sun的扩展包,无法继续跟踪
super(convert(repositories), parent);
this.parent = parent;
this.system = getSystemClassLoader();
securityManager = System.getSecurityManager();
if (repositories != null) {
for (int i = 0; i < repositories.length; i++)
//处理url
addRepositoryInternal(repositories[i]);
}
}
2. addRepositoryInternal
/**
* @param repository 要处理的url
*/
protected void addRepositoryInternal(String repository) {
URLStreamHandler streamHandler = null;
String protocol = parseProtocol(repository);
if (factory != null)
streamHandler = factory.createURLStreamHandler(protocol);
// 当前url是指向本地或网路的jar文件,验证jar的正确性
//下面的代码看似无用其实是在验证jar文件的正确性,如果jar文件错误抛异常中止执行.
if (!repository.endsWith(File.separator) && !repository.endsWith("/")) {
JarFile jarFile = null;
try {
Manifest manifest = null;
//jar协议
if (repository.startsWith("jar:")) {
URL url = new URL(null, repository, streamHandler);
JarURLConnection conn =
(JarURLConnection) url.openConnection();
conn.setAllowUserInteraction(false);
conn.setDoInput(true);
conn.setDoOutput(false);
conn.connect();
jarFile = conn.getJarFile();
//file协议
} else if (repository.startsWith("file://")) {
jarFile = new JarFile(repository.substring(7));
//file
} else if (repository.startsWith("file:")) {
jarFile = new JarFile(repository.substring(5));
//本地路径的jar文件
} else if (repository.endsWith(".jar")) {
URL url = new URL(null, repository, streamHandler);
URLConnection conn = url.openConnection();
JarInputStream jis =
new JarInputStream(conn.getInputStream());
manifest = jis.getManifest();
//其他情况均为错误
} else {
throw new IllegalArgumentException
("addRepositoryInternal: Invalid URL '" +
repository + "'");
}
} catch (Throwable t) {
t.printStackTrace();
throw new IllegalArgumentException
("addRepositoryInternal: " + t);
} finally {
if (jarFile != null) {
try {
jarFile.close();
} catch (Throwable t) {}
}
}
}
//增加当前的url到系统内部的url列表
synchronized (repositories) {
String results[] = new String[repositories.length + 1];
System.arraycopy(repositories, 0, results, 0, repositories.length);
results[repositories.length] = repository;
repositories = results;
}
}
repositories是一个类变量,存放着所有的url列表.
通过这个方法我们基本可以确定几点:
1. ClassLoader可以把一个以”/”或File.separator结尾的路径作为装载类的资源
2. 也可以把本地或网路上的jar文件作为装入class的资源,其他任何形式的资源(比如zip)都是不合法的.
3. 网络上的资源必须是jar包的形式
到此,ClassLoader的构造结束.
3.loadClass方法
上面我们做了很多的工作,目的是构建一个classLoader,构建ClassLoader的目的是用它load想要的class,因此loadClass方法才是我们的重心,同时通过它可以彻底解开ClassLoader的神秘面纱.
/**
*param name:要load的类名
*param resolve:如果是true将调用resolveClass
*/
public Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (debug >= 2)
log("loadClass(" + name + ", " + resolve + ")");
Class clazz = null;
//检查缓存,看是否类已经被load
clazz = findLoadedClass(name);
if (clazz != null) {
if (debug >= 3)
log(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// 如果是java包用系统ClassLoader取load
if( name.startsWith("java.") ) {
ClassLoader loader = system;
clazz = loader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
throw new ClassNotFoundException(name);
}
// 检查是否有权限对该类对应的包做load,包的load权限在catalina.properties里定义
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = "Security Violation, attempt to use " +
"Restricted Class: " + name;
System.out.println(error);
se.printStackTrace();
log(error);
throw new ClassNotFoundException(error);
}
}
}
//该类是否委派给父ClassLoader去装载
if (delegate) {
if (debug >= 3)
log(" Delegating to parent classloader");
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = loader.loadClass(name);
if (clazz != null) {
if (debug >= 3)
log(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
}
//从本地装载该类
if (debug >= 3)
log(" Searching local repositories");
try {
//调用Tomcat实现ClassLoader的核心方法去寻找类
clazz = findClass(name);
if (clazz != null) {
if (debug >= 3)
log(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
//如果该类没有被委派到父类,则用系统loader去装载
if (debug >= 3)
log(" Delegating to parent classloader");
ClassLoader loader = parent;
if (loader == null)
loader = system;
try {
clazz = loader.loadClass(name);
if (clazz != null) {
if (debug >= 3)
log(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
;
}
}
// This class was not found
throw new ClassNotFoundException(name);
}
4. findClass方法
public Class findClass(String name) throws ClassNotFoundException {
if (debug >= 3)
log(" findClass(" + name + ")");
// 检查包的定义,我们不对此处做深究可略过
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
if (debug >= 4)
log(" securityManager.checkPackageDefinition");
securityManager.checkPackageDefinition(name.substring(0,i));
} catch (Exception se) {
if (debug >= 4)
log(" -->Exception-->ClassNotFoundException", se);
throw new ClassNotFoundException(name);
}
}
}
// 如果在本地不能地位类则请求父类加载
// (throws ClassNotFoundException if it is not found)
Class clazz = null;
try {
if (debug >= 4)
log(" super.findClass(" + name + ")");
try {
synchronized (this) {
clazz = findLoadedClass(name);
if (clazz != null)
return clazz;
//请求父类加载
clazz = super.findClass(name);
}
} catch(AccessControlException ace) {
throw new ClassNotFoundException(name);
} catch (RuntimeException e) {
if (debug >= 4)
log(" -->RuntimeException Rethrown", e);
throw e;
}
if (clazz == null) {
if (debug >= 3)
log(" --> Returning ClassNotFoundException");
throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {
if (debug >= 3)
log(" --> Passing on ClassNotFoundException", e);
throw e;
}
// Return the class we have located
if (debug >= 4)
log(" Returning class " + clazz);
if ((debug >= 4) && (clazz != null))
log(" Loaded by " + clazz.getClassLoader());
return (clazz);
}
5. 父类(URLClassLoader)的findClass方法
前面我们曾经说过,URLClassLoader具有从本地装载class,从本地或网络装载jar的功能.
简单说findClass方法只是简单地调用了PrivilegedExceptionAction的run方法,关于这个类我们不必细致了解,只需看下面打蓝色的部分就可以了.
protected Class findClass(final String name) throws ClassNotFoundException {
try {
return (Class)
AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws ClassNotFoundException {
//将类名替换成硬盘绝对路径
String path = name.replace('.', '/').concat(".class");
//关于ucp类变量的说明见上面
//该调用主要是把本地的class文件转换成Resource(不考虑转换细节)
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
//通过类名与类的Resource生成Class
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
throw new ClassNotFoundException(name);
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}
6. defineClass方法
该方法主要是调用JVM的native方法构建一个class对象,关于这个类的细节我们没必要很清除,我们要知道的是JVM利用Resource构造了Class对象.我们只需关注下面蓝色的部分.
/**
*param name:类名
*param res:这个参数是通过ucp变量得到,ucp是通过我们传入的url构建,url就是
*ClassLoader Load Class 的路径
*换句话说我们辛苦构造的StandClassLoader就是为了得到Resource
*Resource是JVM用来构造Class必须条件
*/
private Class defineClass(String name, Resource res) throws IOException {
int i = name.lastIndexOf('.');
URL url = res.getCodeSourceURL();
if (i != -1) {
String pkgname = name.substring(0, i);
// Check if package already loaded.
Package pkg = getPackage(pkgname);
Manifest man = res.getManifest();
if (pkg != null) {
// Package found, so check package sealing.
if (pkg.isSealed()) {
// Verify that code source URL is the same.
if (!pkg.isSealed(url)) {
throw new SecurityException(
"sealing violation: package " + pkgname + " is sealed");
}
} else {
// Make sure we are not attempting to seal the package
// at this code source URL.
if ((man != null) && isSealed(pkgname, man)) {
throw new SecurityException(
"sealing violation: can't seal package " + pkgname +
": already loaded");
}
}
} else {
if (man != null) {
definePackage(pkgname, man, url);
} else {
definePackage(pkgname, null, null, null, null, null, null, null);
}
}
}
//下面是JVM通过二进制代码构造一个Class
byte[] b = res.getBytes();
java.security.cert.Certificate[] certs = res.getCertificates();
CodeSource cs = new CodeSource(url, certs);
return defineClass(name, b, 0, b.length, cs);
}
至此,我们彻底破译了Tomcat的ClassLoader,下面我们再画一下ClassLoader的构造及loadClass的过程.这里不再画sequence图,只是简单表示一下各方法之间的调用关系.其实,从上面我们可以看出真正起到创建ClassLoader关键作用的是URLClassLoader和JVM的defineClass方法,这些方法已被封装或是本地调用,要进一步研究就需要花费更多的精力.阅读了这个流程后相信每人都能定制出自己需要的ClassLoader,比如加密ClassLoader.
BootsStrap
|
createClassLoader(构造三种url,) |
initClassLoaders(初始化三个ClassLoader)
|
ClassLoaderFactory |
createClassLoader(构造带协议的,真正的url) |
StandardClassLoader
|
loadClass |
findClass 查询类 |
URLClassLoader |
findClass |
defineClass |
参考资料:
http://jakarta.apache.org/tomcat/tomcat-4.1-doc/class-loader-howto.html
备注:
本文所讲解的Tomcat是以5.0为基础的.
本人非常喜欢Tomcat这个开源服务器,想研究其奥妙单甚感一人势单力薄,希望志同道合的朋友与我联系,大家一起破解apache神话.(chen77716@hotmail.com)
相关推荐
### Tomcat中的ClassLoader详解 #### 一、引言 在深入了解Tomcat的工作原理时,一个重要的组成部分就是其ClassLoader机制。本文旨在深入剖析Tomcat中特有的类加载器(ClassLoader)体系结构,帮助读者理解Tomcat...
总的来说,深入研究Tomcat 5.0.18的ClassLoader源码,不仅可以提升我们的技术水平,还能让我们更好地应对Java Web开发中的挑战。对于那些想要深入理解Java类加载机制和Tomcat内部工作的开发者,这是一个不可多得的...
《Tomcat类加载机制——ClassLoader详解》 在Java Web开发中,Tomcat作为最常用的Servlet容器,其类加载机制对于...通过阅读和研究Tomcat源码,我们可以更深入地了解这一机制,从而更好地驾驭这个强大的Web服务器。
- **Classloading**:Tomcat使用定制的ClassLoader加载Web应用的类,遵循“父类加载优先”原则。 6. **连接器与协议处理** - **NIO和Apr**:Tomcat提供了多种连接器实现,如基于Java NIO的 Coyote Connector 和...
5. **ClassLoader机制**:Tomcat使用自定义的ClassLoader来加载Web应用程序的类,确保不同应用之间的类隔离,防止冲突。 Tomcat 6.0.18版的特性包括: 1. **性能优化**:相对于之前的版本,6.0.18进行了性能调优,...
4. Common ClassLoader:在Tomcat中,它是所有Web应用共享的类加载器,用于加载`common.loader`配置项指定的类路径。 5. WebApp ClassLoader:每个Web应用有自己的类加载器,它加载应用的`WEB-INF/classes`和`WEB-...
通过研究这个脚本,我们可以了解如何配置JVM参数,如内存分配、堆大小以及设置系统属性,以优化Tomcat的性能。 2. **Tomcat工作原理** Tomcat基于Coyote和Apr(Apache Portable Runtime)处理HTTP请求。Coyote是...
《深入剖析Tomcat》这本书是Java开发者们探索Tomcat服务器内部机制的重要参考资料,它带领读者逐步揭开Tomcat的...通过学习和研究Tomcat源码,我们可以提升技术水平,解决实际问题,甚至为Tomcat社区贡献自己的力量。
5. **ClassLoader机制**:Tomcat的类加载机制允许每个Web应用拥有自己的类加载器,避免类冲突。理解这部分源码对于理解和解决部署问题至关重要。 6. **Session管理**:Tomcat处理用户会话,包括创建、跟踪和管理...
10. **网络编程**:Tomcat底层使用NIO(非阻塞I/O)和BIO(阻塞I/O)模型,这在`java/org/apache/tomcat/util/net`目录下可以深入研究。 通过学习和分析这些源码,开发者不仅可以提升对Java Web技术的理解,还可以...
4. 最后,如果所有父类加载器都未能加载,Catalina ClassLoader才加载Tomcat自身的类。 VSD文件可能包含一个流程图,详细描绘了Tomcat类加载器的工作流程和关系,这对于理解类加载机制非常有帮助。通过阅读和分析该...
1. **Servlet和JSP标准实现**:Tomcat是Java Web应用的标准之一,它遵循Java Servlet和JSP规范。在源码中,你可以看到如何实现这些规范,包括请求处理、响应生成、会话管理以及生命周期管理。 2. **Catalina组件**...
3. **学习部署和加载机制**:Tomcat如何加载和管理Web应用程序,包括WAR文件的部署和Classloader的工作原理。 4. **研究线程模型**:Tomcat如何使用线程来处理并发请求,以及线程池的配置和管理。 5. **深入JSP和...
5. **ClassLoader**:Tomcat使用自定义的ClassLoader来加载Web应用的类。这使得不同应用之间能隔离地运行,避免类冲突。 6. **Lifecycle**:所有Tomcat组件都遵循生命周期接口,如`org.apache.catalina.Lifecycle`...
3. **启动流程**:Tomcat的启动过程始于`bin/catalina.sh`或`catalina.bat`,这些脚本会初始化Java环境,并加载`catalina.jar`中的`org.apache.catalina.startup.ClassLoader`,接着加载`Server`对象,初始化`...
总的来说,通过研究Tomcat7的源码Eclipse工程,不仅可以提升Java Web开发的专业技能,还能深入了解服务器架构设计,这对于优化应用性能、排查故障以及定制化开发具有极大的价值。在实践中,我们可以结合官方文档和...
源码中,ClassLoader的实现(例如`org.apache.catalina.loader.WebappClassLoaderBase`)值得深入研究。 5. **部署和配置**:Tomcat支持多种方式部署Web应用,如WAR文件、目录结构或者通过管理接口。源码中,你可以...
在Java Web开发领域,Tomcat无疑是最为流行的开源应用服务器之一。它以其轻量级、高效和易于管理的特性赢得了广大开发者喜爱。"Tomcat_src"这个压缩包中包含的就是Apache Tomcat的源代码,这为我们深入理解其工作...