tomcat启动过程

1.init方法
1.Catalina_Home和Catalina_Base
2.初始化类加载器体系
1.Tomcat的类加载器体系
2.initClassLoaders的代码
3.一个疑问
3.Catalina对象
2.load方法
1.Catalina类的命令行参数
2.加载过程
3.start方法
4.await状态
1.setAwait方法
2.await方法
上一篇文章 主要分析了Bootstrap main 方法的总体流程,并讨论了JDK兼容性和启动参数。本篇开始深入细节。

Tomcat 的运行时视图,简单地看,其实就是一些相互关联的组件。这些组件相互协作,完成一定的任务(比如部署Web 应用、处理到HTTP 请求等)。Tomcat 启动过程中所做的主要工作,也就是创建这些组件,并建立组件之间的关联。当然,要创建哪些组件,组件之间怎么关联,这是根据配置文件来定制的。

服务器程序的启动过程一般都有“三段式”,Tomcat 也不例外,它的三段式分别是init 、load 和start 。

init 方法
我们先看看Bootstrap 的init 方法。

Java代码
1.public void init() throws Exception
2. {
3.
4. // Set Catalina path
5. setCatalinaHome();
6. setCatalinaBase();
7.
8. initClassLoaders();
9.
10. Thread.currentThread().setContextClassLoader(catalinaLoader);
11.
12. SecurityClassLoad.securityClassLoad(catalinaLoader);
13.
14. // Load our startup class and call its process() method
15. if (log.isDebugEnabled())
16. log.debug("Loading startup class");
17. Class startupClass =
18. catalinaLoader.loadClass
19. ("org.apache.catalina.startup.Catalina");
20. Object startupInstance = startupClass.newInstance();
21.
22. // Set the shared extensions class loader
23. if (log.isDebugEnabled())
24. log.debug("Setting startup class properties");
25. String methodName = "setParentClassLoader";
26. Class paramTypes[] = new Class[1];
27. paramTypes[0] = Class.forName("https://www.360docs.net/doc/b09505684.html,ng.ClassLoader");
28. Object paramValues[] = new Object[1];
29. paramValues[0] = sharedLoader;
30. Method method =
31. startupInstance.getClass().getMethod(methodName, paramTypes);
32. method.invoke(startupInstance, paramValues);
33.
34. catalinaDaemon = startupInstance;
35. }
public void init() throws Exception
{

// Set Catalina path
setCatalinaHome();
setCatalinaBase();

initClassLoaders();

Thread.currentThread().setContextClassLoader(catalinaLoader);

SecurityClassLoad.securityClassLoad(catalinaLoader);

// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class startupClass =
catalinaLo

ader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();

// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("https://www.360docs.net/doc/b09505684.html,ng.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);

catalinaDaemon = startupInstance;
}
该方法的主要工作依次是:

1.设置Catalina (Tomcat Servlet 容器的代号)的路径:CATALINA_HOME 和CATALINA_BASE
2.初始化Tomcat 的类加载器体系
3.创建org.apache.catalina.startup.Catalina 对象(启动阶段剩余的工作由Catalina类 完成)
Catalina_Home 和Catalina_Base
首先,我们看看这两个路径有何区别。Tomcat的启动脚本已经设置了CATALINA_HOME 和CATALINA_BASE 的值,而且两者的值是相同的,都是Tomcat的根目录。那么为什么还要设置这两个变量呢?

我们可以从Tomcat 5.5 的配置文档(https://www.360docs.net/doc/b09505684.html,/tomcat-5.5-doc/config/host.html )中找到答案:

The description below uses the variable name $CATALINA_HOME to refer to the directory into which you have installed Tomcat 5, and is the base directory against which most relative paths are resolved. However, if you have configured Tomcat 5 for multiple instances by setting a CATALINA_BASE directory, you should use $CATALINA_BASE instead of $CATALINA_HOME for each of these references.

从这段描述可以看出CATALINA_HOME 和CATALINA_BASE 的区别。简单的说,CATALINA_HOME 是Tomcat 的安装目录,CATALINA_BASE 是Tomcat 的工作目录。如果我们想要运行Tomcat 的多个实例,但是不想安装多个Tomcat 软件副本。那么我们可以配置多个工作目录,每个运行实例独占一个工作目录,但是共享同一个安装目录。




Tomcat 每个运行实例需要使用自己的conf 、logs 、temp 、webapps 、work 和shared 目录,因此CATALINA_BASE 就指向这些目录。 而其他目录主要包括了Tomcat 的二进制文件和脚本,CATALINA_HOME 就指向这些目录。

如果我们希望再运行另一个Tomcat 实例,那么我们可以建立一个目录,把conf 、logs 、temp 、webapps 、work 和shared 拷贝到该目录下,然后让CATALINA_BASE 指向该目录即可。

下面,我们看看Bootstrap 是如何设置CATALINA_HOME和CATALINA_BASE。

Java代码
1.private void setCatalinaHome() {
2. if (System.getProperty("catalina.home") != null)
3. return;
4. File bootstrapJar =
5. new File(System.getProperty("user.dir"

), "bootstrap.jar");
6. if (bootstrapJar.exists()) {
7. try {
8. System.setProperty
9. ("catalina.home",
10. (new File(System.getProperty("user.dir"), ".."))
11. .getCanonicalPath());
12. } catch (Exception e) {
13. // Ignore
14. System.setProperty("catalina.home",
15. System.getProperty("user.dir"));
16. }
17. } else {
18. System.setProperty("catalina.home",
19. System.getProperty("user.dir"));
20. }
21. }
private void setCatalinaHome() {
if (System.getProperty("catalina.home") != null)
return;
File bootstrapJar =
new File(System.getProperty("user.dir"), "bootstrap.jar");
if (bootstrapJar.exists()) {
try {
System.setProperty
("catalina.home",
(new File(System.getProperty("user.dir"), ".."))
.getCanonicalPath());
} catch (Exception e) {
// Ignore
System.setProperty("catalina.home",
System.getProperty("user.dir"));
}
} else {
System.setProperty("catalina.home",
System.getProperty("user.dir"));
}
}
CATALINA_HOME 保存在系统变量catalina.home 中。setCatalinaHome 方法首先检查catalina.home 系统变量是否设置。如果已经设置,则直接返回;否则,就检查Tomcat 的启动目录(系统变量user.dir)。如果启动目录是bin,那么启动目录下就存在bootstrap.jar ,故CATALINA_HOME 就是bin 的父目录;如果启动目录下没有bootstrap.jar ,那么就假定启动目录就是CATALINA_HOME 。

Java代码
1.private void setCatalinaBase() {
2. if (System.getProperty("catalina.base") != null)
3. return;
4. if (System.getProperty("catalina.home") != null)
5. System.setProperty("catalina.base",
6. System.getProperty("catalina.home"));
7. else
8. System.setProperty("catalina.base",
9. System.getProperty("user.dir"));
10.}
private void setCatalinaBase() {
if (System.getProperty("catalina.base") != null)
return;
if (System.getProperty("catalina.home") != null)
System.setProperty("catalina.base",
System.getProperty("catalina.home"));
else
System.setProperty("catalina.base",
System.getProperty("user.dir"));
}
CATALINA_BASE 保存在系统变量catalina.base 中。setCatalinaBase 方法首先检查catalina.base 系统变量是否设置,如果已

经设置,就直接返回。否则,就检查catalina.home 系统变量是否设置。如果已经设置,则以CATALINA_HOME 作为CATALINA_BASE 。否则,就以Tomcat 的启动目录(系统变量user.dir )作为CATALINA_BASE 。

catalina.bat 中已经设置了catalin.home 和catalina.base 的值,详见下面代码:

Java代码
1.%_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
%_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
我们可以通过修改catalina.bat 中CATALINA_HOME 和CATALINA_BASE 的值 ,来设置catalina.home 和catalina.base 这两个系统变量。

初始化类加载器体系
顾名思义,initClassLoaders() 负责初始化类加载器。看代码之前,我们先看看Tomcat 的类加载器体系。

Tomcat 的类加载器体系
各种组件容器(Tomcat 、JBoss 、GlassFish 、Geronimo 等),都会有自己的类加载器体系。这主要是为了要把开发者编写的各种组件应用(WAR 、EAR 等)部署到容器中,并实现组件应用之间的隔离。

Tomcat 也实现了自己的类加载器体系。这个在Tomcat 的官方文档中有详细介绍,详见https://www.360docs.net/doc/b09505684.html,/tomcat-5.5-doc/class-loader-howto.html 。这里做点简单介绍。

Tomcat 的类加载器体系如下图所示:

Bootstrap
|
System
|
Common
/ \
Catalina Shared
/ \
Webapp1 Webapp2 ...

BootStrap 就是JVM 的启动类加载器,负责加载Java 核心类库和系统扩展类库(%JAVA_HOME%/jre/lib/ext 下的jar 文件)。有的JVM实现提供了两个类加载器,分别加载核心类库和系统扩展类库。我们这里仍用一个Bootstrap 类加载器表示,不影响理解。

System 是JVM的系统类加载器,负责加载CLASSPATH 下的jar 文件,这些文件包括:

1.%CATALINA_HOME%/bin/bootstrap.jar
2.%JAVA_HOME%/lib/tools.jar
3.%CATALINA_HOME%/bin/commons-logging-api-x.y.z.jar
4.%CATALINA_HOME%/bin/tomcat-juli.jar
5.%CATALINA_HOME%/bin/tomcat-daemon.jar
6.%CATALINA_HOME%/bin/jmx.jar (即之前提到的JDK 1.4 兼容包)

其中,1 和2 是在catalina.bat 中指定的,3-6 是在bootstrap.jar 的META-INF/MANIFEST.MF 文件中指定的。

Common 是公共类加载器,负责加载Tomcat 内部和Web 应用程序都可以看到的类。%CATALINA_HOME%/conf/catalina.properties 文件中指定了这些类:

common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,$

{catalina.home}/common/lib/*.jar

可见,Common 加载的是%CATALINA_HOME%/common 目录下的jar 文件。


Catalina 负责加载Tomcat 内部使用的类,这些类对于Web 应用程序不可见。同样,%CATALINA_HOME%/conf/catalina.properties 文件中指定了这些类:

server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar

可见,Catalina 加载的是%CATALINA_HOME%/server 目录下的jar 文件。

Shared 负责加载仅 在Web应用程序之间共享的类,这些类对于Tomcat 内部是不可见的。同样,%CATALINA_HOME%/conf/catalina.properties 文件中指定了这些类:

shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar

可见,Catalina 加载的是% CATALINA_BASE %/shared 目录下的jar 文件。注意,这里是CATALINA_BASE 。shared 属于Tomcat 的工作目录。

Webapp 负责加载Web 应用程序的类,这些类只对本Web 应用程序可见。很显然,每个Web 应用程序都有一个独立的Webapp 类加载器。它们加载的类包括WAR 包中/WEB-INF/classes 和/WEB-INF/lib 目录下的类。

需要注意的是,Webapp 并没有遵循类加载器委派模型。Webapp 优先从自己的搜索路径中加载类,而不是委派给父亲Shared 。这样做的原因应该是保证Web 应用程序优先使用自己的类。但是,System 负责加载的类不应该被覆盖,因此,Webapp 会首先委派给System ,然后自己加载,接着才会委派给父亲Shared 。这个可以参考org.apache.catalina.loader.WebappClassLoader 的loadClass 方法。



PS:Tomcat 7 的类加载体系有所简化,如下图所示:

Bootstrap
|
System
|
Common
/ \
Webapp1 Webapp2 ...




在这种体系下,Tomcat 内部实现类对Web 应用是可见的。如果担心安全风险,可以以-security 方式启动Tomcat 。

Java代码
1.sh startup.sh -security
sh startup.sh -security这样,绝大部分内部实现类对Web 应用均不可见。



了解Tomcat 类加载器体系之后,我们来看看initClassLoaders 方法的代码。

initClassLoaders 的代码
Java代码
1.private void initClassLoaders() {
2. try {
3. commonLoader = createClassLoader("common", null);
4. if( commonLoader == null ) {
5. // no config file, default to this loader - we might be in a 'single' env.
6. commonLoader=this.getClass().getClassLoader();
7. }
8. catalinaLoader = createClassLoader("server", commonLoader);
9. sharedLoader = createClassLoader("shared", commonLoader);
10. } catch (Throwable t) {
11. log.error("Class loader creation threw exception", t);
12. System.exit(1);
13. }
14. }
private void initClassLoaders() {
try {
commonLoader = createClass

Loader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}

可见,该方法以通过createClassLoader 依次创建了common 、server 和shared 类加载器,并且设置三者之间的父子关系。这三者就是前面提到的Common 、Catalina 和Shared 类加载器。如果%CATALINA_HOME%/conf/catalina.properties 中没有指定Common 的搜索路径,那么就是用当前类的类加载器——系统类加载器作为Common 。

下面,我们看看createClassLoader 的代码:

Java代码
1.private ClassLoader createClassLoader(String name, ClassLoader parent)
2. throws Exception {
3. // CatalinaProperties类读取并封装了catalina.properties中的配置信息
4. String value = CatalinaProperties.getProperty(name + ".loader");
5. if ((value == null) || (value.equals("")))
6. return parent;
7.
8. // 解析catalina.properties中配置的类搜索路径
9. // 将类路径中的${catalina.home}替换成CATALINA_HOME的值,将${catalina.base}替换成CATALINA_BASE的值
10. ArrayList repositoryLocations = new ArrayList();
11. ArrayList repositoryTypes = new ArrayList();
12. int i;
13.
14. StringTokenizer tokenizer = new StringTokenizer(value, ",");
15. while (tokenizer.hasMoreElements()) {
16. String repository = tokenizer.nextToken();
17.
18. // Local repository
19. boolean replace = false;
20. String before = repository;
21. while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
22. replace=true;
23. if (i>0) {
24. repository = repository.substring(0,i) + getCatalinaHome()
25. + repository.substring(i+CATALINA_HOME_TOKEN.length());
26. } else {
27. repository = getCatalinaHome()
28. + repository.substring(CATALINA_HOME_TOKEN.length());
29. }
30. }
31. while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {
32. replace=true;
33. if (i>0) {
34. repository = repository.substring(0,i) + getCatalinaBase()
35. + repository.substring(i+CATALINA_BASE_TOKEN.length());
36. } else {
37. repository = getCatalinaBase()
38.

+ repository.substring(CATALINA_BASE_TOKEN.length());
39. }
40. }
41. if (replace && log.isDebugEnabled())
42. log.debug("Expanded " + before + " to " + replace);
43.
44. // 区分四种类型的路径
45. // Check for a JAR URL repository
46. try {
47. URL url=new URL(repository);
48. repositoryLocations.add(repository);
49. repositoryTypes.add(ClassLoaderFactory.IS_URL);
50. continue;
51. } catch (MalformedURLException e) {
52. // Ignore
53. }
54.
55. if (repository.endsWith("*.jar")) {
56. repository = repository.substring
57. (0, repository.length() - "*.jar".length());
58. repositoryLocations.add(repository);
59. repositoryTypes.add(ClassLoaderFactory.IS_GLOB);
60. } else if (repository.endsWith(".jar")) {
61. repositoryLocations.add(repository);
62. repositoryTypes.add(ClassLoaderFactory.IS_JAR);
63. } else {
64. repositoryLocations.add(repository);
65. repositoryTypes.add(ClassLoaderFactory.IS_DIR);
66. }
67. }
68.
69. String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
70. Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);
71.
72. // 创建类加载器对象
73. ClassLoader classLoader = ClassLoaderFactory.createClassLoader
74. (locations, types, parent);
75.
76. // 类加载器被注册成MBean
77. // Retrieving MBean server
78. MBeanServer mBeanServer = null;
79. if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
80. mBeanServer =
81. (MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0);
82. } else {
83. mBeanServer = MBeanServerFactory.createMBeanServer();
84. }
85.
86. // Register the server classloader
87. ObjectName objectName =
88. new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
89. mBeanServer.registerMBean(classLoader, objectName);
90.
91. return classLoader;
92. }
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
// CatalinaProperties类读取并封装了catalina.properties中的配置信息
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;

// 解析catalina.properties中配置的类搜索路径
// 将类路径中的${catalina.home}替换成CA

TALINA_HOME的值,将${catalina.base}替换成CATALINA_BASE的值
ArrayList repositoryLocations = new ArrayList();
ArrayList repositoryTypes = new ArrayList();
int i;

StringTokenizer tokenizer = new StringTokenizer(value, ",");
while (tokenizer.hasMoreElements()) {
String repository = tokenizer.nextToken();

// Local repository
boolean replace = false;
String before = repository;
while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
replace=true;
if (i>0) {
repository = repository.substring(0,i) + getCatalinaHome()
+ repository.substring(i+CATALINA_HOME_TOKEN.length());
} else {
repository = getCatalinaHome()
+ repository.substring(CATALINA_HOME_TOKEN.length());
}
}
while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {
replace=true;
if (i>0) {
repository = repository.substring(0,i) + getCatalinaBase()
+ repository.substring(i+CATALINA_BASE_TOKEN.length());
} else {
repository = getCatalinaBase()
+ repository.substring(CATALINA_BASE_TOKEN.length());
}
}
if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + replace);

// 区分四种类型的路径
// Check for a JAR URL repository
try {
URL url=new URL(repository);
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_URL);
continue;
} catch (MalformedURLException e) {
// Ignore
}

if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_GLOB);
} else if (repository.endsWith(".jar")) {
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_JAR);
} else {
repositoryLocations.add(repository);
repositoryTypes.add(ClassLoaderFactory.IS_DIR);
}
}

String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);

// 创建类加载器对象
ClassLoader classLoader = ClassLoaderFactory.createClassLoader
(locations, types, parent);

// 类加载器被注册成MBean
// Retrieving MBean server
MBeanServer mBea

nServer = null;
if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
mBeanServer =
(MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0);
} else {
mBeanServer = MBeanServerFactory.createMBeanServer();
}

// Register the server classloader
ObjectName objectName =
new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
mBeanServer.registerMBean(classLoader, objectName);

return classLoader;
}


创建类加载器,必须要知道类搜索路径是什么。前面提到,Tomcat 各类加载器的类搜索路径都定义在%CATALINA_HOME%/conf/catalina.properties 中。该配置文件的主要内容如下:

Java代码
https://www.360docs.net/doc/b09505684.html,mon.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
2.server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
3.shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar

配置文件内容的读取是由CatalinaProperties类 完成的。另外,CatalinaProperties 类还提供了额外的功能:

1.配置文件的位置可以通过系统变量catalina.config 进行自定义
2.如果没有定义配置文件,或配置文件不存在,那么将使用bootstrap.jar 中的org/apache/catalina/startup/catalina.properties
CatalinaProperties 在最大程度上保证了配置文件总是存在的。

上述配置的${catalina.home} 和${catalina.base} 还需要解析,替换成真实的值。例如,将${catalina.home}/common/classes 替换成D:\ProgramFiles\apache-tomcat-5.5.26/common/classes 。

类搜索路径被分为四种:URL 、*.jar 、jar 和目录。ClassLoaderFactory 类在创建类加载器时,会分别处理这四种类型,具体就不细看了。

类加载器创建之后,将被注册成MBean ,ObjectName 是Catalina :type=ServerClassLoader,name= XXX ,其中XXX 表示类加载器的名称(即common 、catalina 、shared )。这样,我们就可以JMX 观察每个类加载器的信息,比如加载了哪些类。

一个疑问
至此,类加载器体系基本建立。但是,init 方法又做了两件事件:

1.Catalina 类加载器被设置成线程的上下文类加载器。
2.SecurityClassLoad 类,使用Catalian 类加载器预先加载了一些类


第1 件事情不足为奇,Tomcat运行过程中,主线程如果需要使用Catalina类加载器,就可以 直接使用线程上下文类加载器。

第2 件事情就想不通了,虽然代码注

释告诉我们,这是为了在启用安全管理器的情况下避免defineClassInPackage 权限错误。但是,我没有理解。

望各位不吝赐教!

Catalina 对象
init 方法接下来的工作就很简单了:

1.通过反射机制,创建一个org.apache.catalina.startup.Catalina 对象
2.再通过反射机制,调用该Catalina 对象的setParentClassLoader 方法,将Shared 类加载器设置成其parentClassLoader 。

也许你会奇怪,parentClassLoader 是什么用呢?其实这还是和前面提到的Webapp 类加载器有关。Catalina 对象的parentClassLoader 其实是Webapp 的父亲,即Shared 类加载器。

我们知道,initClassLoader 方法指定了Common 、Catalina 和Shared 之间的父子关系,那么谁来指定Shared 和Webapp 之间的父子关系呢?显然应该是Webapp 的创建者。Webapp 的创建者是org.apache.catalina.loader.WebappLoader 对象,它从Catalina 对象获取Webapp 的父亲类加载器——parentClassLoader 。

另外,关于Catalina 对象的另一个话题就是:Bootstrap 和Catalina 之间的关系。

仔细阅读Bootstrap 类的代码,发现init 之后的工作(load 、start 、stop 等),都是委派给Catalina 类的同名方法。也就是说,后续的启动和停止,都是Catalina 类完成的。

那么,Tomcat 的启动过程为什么要放在Bootstrap 和Catalina 两个类中呢?而且,Bootstrap 还是通过反射机制调用Catalina ,感觉上是在走弯路。

《How Tomcat Works》 第17 章的开头提到这个问题。

This chapter focuses on Tomcat startup using two classes in the org.apache.catalina.startup package, Catalina and Bootstrap. The Catalina class is used to start and stop a Server object as well as parse the Tomcat configuration file, server.xml. The Bootstrap class is the entry point that creates an instance of Catalina and calls its process method. In theory, these two classes could have been merged. However, to support more than one mode of running Tomcat, a number of bootstrap classes are provided. For example, the aforementioned Bootstrap class is used for running Tomcat as a stand-alone application. Another class, org.apache.catalina.startup.BootstrapService, is used to run Tomcat as a Windows NT service.

理论上,这两个类是可以合并的。但是,为了支持以多种方式启动,Tomcat 将启动的核心逻辑(即Catalina 类)和不同启动方式(比如 Bootstrap类)分开,并且通过提供多种Bootstrap来实现不同的启动方式。

目前,Tomcat 共支持三种启动方式:


1.作为独立的程序,从命令行启动
2.作为嵌入式程序,从其他进程中启动
3.作为Windows 服务,自动启动
在Tomcat 5.5 中:

1.第一种启动方式就是由Bootstrap类和Catalina类实现
2.第二种启动方式是由org.apache.catalina.startup.Embedded 类实现的(其实Catalina 类就是Embedded的子类)
3.第三

种启动方式仍然由Bootstrap类和Catalina类实现

不过,在Tomcat 4.x 中,第三种启动方式由专门的Bootstrap Service类和Catalina Service 类来实现的。

可见,Tomcat 的发展历史中,还是体现了“Bootstrap 和Catalina 分离,Bootstrap 封装不同启动方式”的策略。但是,现在的代码看,这个分离已经不是很清楚。

OK ,init 方法的讨论到此结束。下面我们看看load 和start 方法。Bootstrap 的这两个方法,其实都是直接调用Catalina 类的同名方法,因此,我们主要分析的其实是Catalina 类的相关代码。

load 方法
下面是Boostrap 类的load 方法。

Java代码
1.private void load(String[] arguments)
2. throws Exception {
3. // Call the load() method
4. String methodName = "load";
5. Object param[];
6. Class paramTypes[];
7. if (arguments==null || arguments.length==0) {
8. paramTypes = null;
9. param = null;
10. } else {
11. paramTypes = new Class[1];
12. paramTypes[0] = arguments.getClass();
13. param = new Object[1];
14. param[0] = arguments;
15. }
16. Method method =
17. catalinaDaemon.getClass().getMethod(methodName, paramTypes);
18. if (log.isDebugEnabled())
19. log.debug("Calling startup class " + method);
20. method.invoke(catalinaDaemon, param);
21. }
private void load(String[] arguments)
throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}


可以看出,Catalina 的load 方法有两个版本:有参数和无参数。

有参数版本,首先调用了arguments方法来处理参数。如果处理成功,则再调用无参数版本。因此,load的核心逻辑在无参数版本中。

Java代码
1.public void load(String args[]) {
2.
3. try {
4. if (arguments(args))
5. load();
6. } catch (Exception e) {
7. e.printStackTrace(System.out);
8. }
9.}
public void load(String args[]) {

try {
if (arguments(args))
load();
} catch (Exception e) {
e.printStackTrace(System.out);
}
}

无参

数版本的load 方法做的工作,主要是根据Tomcat 的配置文件,创建各个组件,并建立组件之间的关联。比如,创建核心的Server 组件、Service 组件、Manager 组件、 Loader 组件等。 这个细节我们等会讨论,首先简单看看arguments 方法。arguments 方法对理解启动过程不是很关键,如果不感兴趣可以跳过。

Catalina 类的命令行参数
Catalina.bat 脚本的参数,其实是直接传递到Catalina 类的。arguments 方法正是处理这些参数的。

Catalina 类可以识别的参数包括:

1.-config {pathname} 设置配置文件的路径。如果是相对路径,则是相对于CATALINA_HOME 。默认值是conf/server.xml 。
2.-noaming 不启用命名服务

3.-help 打印帮助信息

4.-start 启动当前的Catalina 实例

5.-stop 停止当前的Catalina 实例

PS:Tomcat 7 新加了-configtest 选项。


arguments 的代码比较简单。

Java代码
1.protected boolean arguments(String args[]) {
2.
3. boolean isConfig = false;
4.
5. if (args.length < 1) {
6. usage();
7. return (false);
8. }
9.
10. for (int i = 0; i < args.length; i++) {
11. if (isConfig) {
12. configFile = args[i];
13. isConfig = false;
14. } else if (args[i].equals("-config")) {
15. isConfig = true;
16. } else if (args[i].equals("-nonaming")) {
17. setUseNaming( false );
18. } else if (args[i].equals("-help")) {
19. usage();
20. return (false);
21. } else if (args[i].equals("start")) {
22. starting = true;
23. stopping = false;
24. } else if (args[i].equals("stop")) {
25. starting = false;
26. stopping = true;
27. } else {
28. usage();
29. return (false);
30. }
31. }
32.
33. return (true);
34. }
protected boolean arguments(String args[]) {

boolean isConfig = false;

if (args.length < 1) {
usage();
return (false);
}

for (int i = 0; i < args.length; i++) {
if (isConfig) {
configFile = args[i];
isConfig = false;
} else if (args[i].equals("-config")) {
isConfig = true;
} else if (args[i].equals("-nonaming")) {
setUseNaming( false );
} else if (args[i].equals("-help")) {
usage();
return (false);
} else if (args[i].equals("start")) {
starting = true;
stopping = false;
} else if (args[i].equals("stop")) {
starting = false;
stopp

ing = true;
} else {
usage();
return (false);
}
}

return (true);
}
Catalina 类的成员变量starting 和stopping 分别表示要启动和停止Tomcat 。

加载过程
下面我们看看Catalina 类的load 方法的代码。


Java代码
1.public void load() {
2.
3. // 初始化CATALINA_HOME、CATALINA_BASE和临时目录,该方法在Embedded类中。
4. initDirs();
5.
6. // 初始化命名服务的基本配置,包括java.naming.factory.url.pkgs和java.naming.factory.initial
7. // java.naming.factory.url.pkgs的默认值为 org.apache.naming
8. // java.naming.factory.initial 的默认值为org.apache.naming.java.javaURLContextFactory
9.
10. // Before digester - it may be needed
11. initNaming();
12.
13. // 创建Digester对象。该对象被用来解析配置文件(默认为conf/server.xml)
14. // Create and execute our Digester
15. Digester digester = createStartDigester();
16. long t1 = System.currentTimeMillis();
17.
18. Exception ex = null;
19. InputSource inputSource = null;
20. InputStream inputStream = null;
21. File file = null;
22. try {
23. // 配置文件,由命令行参数-config指定,否则取默认值conf/server.xml
24. file = configFile();
25. inputStream = new FileInputStream(file);
26. inputSource = new InputSource("file://" + file.getAbsolutePath());
27. } catch (Exception e) {
28. ;
29. }
30.
31. // 如果配置文件不存在,则在类路径中加载
32. if (inputStream == null) {
33. try {
34. inputStream = getClass().getClassLoader()
35. .getResourceAsStream(getConfigFile());
36. inputSource = new InputSource
37. (getClass().getClassLoader()
38. .getResource(getConfigFile()).toString());
39. } catch (Exception e) {
40. ;
41. }
42. }
43.
44. // 如果类路径中也找不到,则加载server-embed.xml
45. // This should be included in catalina.jar
46. // Alternative: don't bother with xml, just create it manually.
47. if( inputStream==null ) {
48. try {
49. inputStream = getClass().getClassLoader()
50. .getResourceAsStream("server-embed.xml");
51. inputSource = new InputSource
52. (getClass().getClassLoader()
53. .getResource("server-embed.xml").toString());
54. } catch (Exception e) {
55. ;
56. }
57. }
58.
59.
60. // 如果没

能加载配置文件,则报错,中断启动
61. if ((inputStream == null) && (file != null)) {
62. log.warn("Can't load server.xml from " + file.getAbsolutePath());
63. return;
64. }
65.
66. // 使用Digester对象解析配置文件,解析的过程中会创建各种组件,包括Server组件。
67. try {
68. inputSource.setByteStream(inputStream);
69. digester.push(this);
70. digester.parse(inputSource);
71. inputStream.close();
72. } catch (Exception e) {
73. log.warn("Catalina.start using "
74. + getConfigFile() + ": " , e);
75. return;
76. }
77.
78. // 将系统标准输出(System.out)和系统错误输出(System.err)重定向到定制的SystemLogHandler。
79. // SystemLogHandler可以将每个线程的输出隔离到不同的输出流中。
80. // Stream redirection
81. initStreams();
82.
83. // 初始化Server组件。成员变量server就代表Server组件
84. // Start the new server
85. if (server instanceof Lifecycle) {
86. try {
87. server.initialize();
88. } catch (LifecycleException e) {
89. log.error("Catalina.start", e);
90. }
91. }
92.
93. long t2 = System.currentTimeMillis();
94. if(log.isInfoEnabled())
95. https://www.360docs.net/doc/b09505684.html,("Initialization processed in " + (t2 - t1) + " ms");
96.
97. }
public void load() {

// 初始化CATALINA_HOME、CATALINA_BASE和临时目录,该方法在Embedded类中。
initDirs();

// 初始化命名服务的基本配置,包括java.naming.factory.url.pkgs和java.naming.factory.initial
// java.naming.factory.url.pkgs的默认值为 org.apache.naming
// java.naming.factory.initial 的默认值为org.apache.naming.java.javaURLContextFactory

// Before digester - it may be needed
initNaming();

// 创建Digester对象。该对象被用来解析配置文件(默认为conf/server.xml)
// Create and execute our Digester
Digester digester = createStartDigester();
long t1 = System.currentTimeMillis();

Exception ex = null;
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
// 配置文件,由命令行参数-config指定,否则取默认值conf/server.xml
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource("file://" + file.getAbsolutePath());
} catch (Exception e) {
;
}

// 如果配置文件不存在,则在类路径中加载
if (inputStream == null) {

try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
;
}
}

// 如果类路径中也找不到,则加载server-embed.xml
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if( inputStream==null ) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
;
}
}


// 如果没能加载配置文件,则报错,中断启动
if ((inputStream == null) && (file != null)) {
log.warn("Can't load server.xml from " + file.getAbsolutePath());
return;
}

// 使用Digester对象解析配置文件,解析的过程中会创建各种组件,包括Server组件。
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
inputStream.close();
} catch (Exception e) {
log.warn("Catalina.start using "
+ getConfigFile() + ": " , e);
return;
}

// 将系统标准输出(System.out)和系统错误输出(System.err)重定向到定制的SystemLogHandler。
// SystemLogHandler可以将每个线程的输出隔离到不同的输出流中。
// Stream redirection
initStreams();

// 初始化Server组件。成员变量server就代表Server组件
// Start the new server
if (server instanceof Lifecycle) {
try {
server.initialize();
} catch (LifecycleException e) {
log.error("Catalina.start", e);
}
}

long t2 = System.currentTimeMillis();
if(log.isInfoEnabled())
https://www.360docs.net/doc/b09505684.html,("Initialization processed in " + (t2 - t1) + " ms");

}

Catalina 加载的主要流程参见上述代码中的中文注释,应该比较清楚,这里就不一一赘述了。

需要注意的是Digester ,它是Apache 基金会的另一个项目,主要负责解析XML 并执行一定的操作。其主要原理是,为XML 的每个元素配置特定的规则,规则描述了Digester 遇到该元素时需要执行的操作。

Tomcat 使用Digester 来解析配置文件(默认是conf/server.xml ),并根据配置创建各种组件,并建立组

件之间的关联。创建和关联,都是通过自定义的规则来实现的。我们以conf/server.xml 的部分内容和部分规则为例,解释一下 Digester 的原理。

配置文件的部分内容:

Xml代码
1.
2.
3.
4.
5.
6. 7. maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
8. enableLookups="false" redirectPort="8443" acceptCount="100"
9. connectionTimeout="20000" disableUploadTimeout="true" />
10.
11. 12. unpackWARs="true" autoDeploy="true"
13. xmlValidation="false" xmlNamespaceAware="false">
14. ...
15.

16.

17.

18.






maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="20000" disableUploadTimeout="true" />

unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
...





其中,每个XML 元素代表一个组件对象,元素中属性对应了组件的成员变量。例如, 就代表Server 组件,该组件对象有两个成员变量port 和shutdown 。不难猜到,它们其实是之前提到 的SHUTDOWN 端口和SHUTDOWN 命令。

Server 组件包含了 Listener 对象和Service 组件。Service 组件又包含了Connetor组件和Engine 组件。Engine 组件又包含了Host 组件。Host 组件也包含了...这就是组件之间的关联。

至于这些组件的作用是什么,关联关系为什么是这样的,我们会在后面的文章中看到。

下面我们在看看Digester 规则。规则以方法调用的定义。Tomcat 启动相关的规则定义在createStartDigester 方法中,部分代码如下:

Java代码
1.protected Digester createStartDigester() {
2.
3. // ...
4.
5. // Configure the actions we will be using
6.
7. // 创建Server组件,即StandardServer对象
8. digester.addObjectCreate("Server",
9. "org.apache.catalina.core.StandardServer",
10. "classN

ame");
11. // 设置StandardServer的成员变量port和shutdown
12. digester.addSetProperties("Server");
13. // 建立Catalina对象和StandardServer对象之间的关联,前者包含后者
14. digester.addSetNext("Server",
15. "setServer",
16. "org.apache.catalina.Server");
17.
18. //...
19. // 创建Listener对象,类由className属性决定
20. digester.addObjectCreate("Server/Listener",
21. null, // MUST be specified in the element
22. "className");
23. // 设置Listener对象的成员变量
24. digester.addSetProperties("Server/Listener");
25. // 建立StandardServer对象和Listener对象之间的关联,前者包含后者
26. digester.addSetNext("Server/Listener",
27. "addLifecycleListener",
28. "org.apache.catalina.LifecycleListener");
29.
30. // 创建Service组件,即StandardService对象
31. digester.addObjectCreate("Server/Service",
32. "org.apache.catalina.core.StandardService",
33. "className");
34. // 设置StandardService的成员变量 name
35. digester.addSetProperties("Server/Service");
36. // 建立StandardServer对象和StandardService对象之间的关联,前者包含后者
37. digester.addSetNext("Server/Service",
38. "addService",
39. "org.apache.catalina.Service");
40.
41. // ...
42. return (digester);
43.
44. }
protected Digester createStartDigester() {

// ...

// Configure the actions we will be using

// 创建Server组件,即StandardServer对象
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
// 设置StandardServer的成员变量port和shutdown
digester.addSetProperties("Server");
// 建立Catalina对象和StandardServer对象之间的关联,前者包含后者
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");

//...
// 创建Listener对象,类由className属性决定
digester.addObjectCreate("Server/Listener",
null, // MUST be specified in the element
"className");
// 设置Listener对象的成员变量
digester.addSetProperties("Server/Listener");
// 建立StandardServer对象和Listener对象之间的关联,前者包含后者
digester.addSetNext("Server/Listener",

"addLifecycleListener",
"org.apache.catalina.LifecycleListener");

// 创建Service组件,即StandardService对象
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
// 设置StandardService的成员变量 name
digester.addSetProperties("Server/Service");
// 建立StandardServer对象和StandardService对象之间的关联,前者包含后者
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");

// ...
return (digester);

}

概括地说,addObjectCreate 表示创建对象,addSetProperties 表示设置对象的属性,addSetNext 表示设置对象的包含对象。这都是 Digester 的常用规则。当然,Tomcat 也定制了一些规则,以执行更加复杂的操作。

OK ,load 方法就算看完了。总之,该方法结束后,Tomcat 的运行时视图已经被建立,各大组件及关联关系均以建立。下一步,就是启动Catalina 了。

start 方法
Java代码
1.public void start()
2. throws Exception {
3. if( catalinaDaemon==null ) init();
4.
5. Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
6. method.invoke(catalinaDaemon, (Object [])null);
7. }
public void start()
throws Exception {
if( catalinaDaemon==null ) init();

Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}

该方法其实仅仅是调用了Catalina 类的start 方法,因此我们重点看Catalina 类的start 方法。

Java代码
1.public void start() {
2.
3. //确保load方法已经被调用。load方法会创建StandardServer 实例,并赋值给成员变量server。
4. if (server == null) {
5. load();
6. }
7.
8. long t1 = System.currentTimeMillis();
9.
10. //调用server的生命周期方法start
11. // Start the new server
12. if (server instanceof Lifecycle) {
13. try {
14. ((Lifecycle) server).start();
15. } catch (LifecycleException e) {
16. log.error("Catalina.start: ", e);
17. }
18. }
19.
20. long t2 = System.currentTimeMillis();
21. if(log.isInfoEnabled())
22. https://www.360docs.net/doc/b09505684.html,("Server startup in " + (t2 - t1) + " ms");
23.
24. //注册JVM的shutdown钩子
25. try {
26. // Register shutdown hook
27. if (useShutdownHook) {
28. if (shutdownHook == null) {
29. shutdownHook = new Cata

linaShutdownHook();
30. }
31. Runtime.getRuntime().addShutdownHook(shutdownHook);
32. }
33. } catch (Throwable t) {
34. // This will fail on JDK 1.2. Ignoring, as Tomcat can run
35. // fine without the shutdown hook.
36. }
37.
38. //如果await设置成true,则进入await状态
39. if (await) {
40. await();
41. //退出await状态后,就停止Tomcat
42. stop();
43. }
44. }
public void start() {

//确保load方法已经被调用。load方法会创建StandardServer 实例,并赋值给成员变量server。
if (server == null) {
load();
}

long t1 = System.currentTimeMillis();

//调用server的生命周期方法start
// Start the new server
if (server instanceof Lifecycle) {
try {
((Lifecycle) server).start();
} catch (LifecycleException e) {
log.error("Catalina.start: ", e);
}
}

long t2 = System.currentTimeMillis();
if(log.isInfoEnabled())
https://www.360docs.net/doc/b09505684.html,("Server startup in " + (t2 - t1) + " ms");

//注册JVM的shutdown钩子
try {
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
} catch (Throwable t) {
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}

//如果await设置成true,则进入await状态
if (await) {
await();
//退出await状态后,就停止Tomcat
stop();
}
}

start 方法执行之前,需要确保load 方法已经执行。如果load 方法已经执行,那么成员变量server 肯定被赋值。因此,start 方法首先判断成员变量 server 是否为null ,如果是,则调用load 方法。

接着,调用server 的start方法,启动 Catalina。启动阶段的工作和加载阶段还是不同的。例如,Connector组件负责网络通信,加载阶段只是创建组件对象,启动阶段才会监听端口。

然后,Catalina 的主线程会进入await状态,如果成员变量await被设置成 true的话。《Tomcat 5.5.26 源代码分析——启动过程(一)》中也提到,如果await被设置成true,那么Tomcat的主线程将监听SHUTDOWN 端口,等待SHUTDOWN 命令,从而我们可以在Tomcat进程外部通过网络停止Tomcat的运行。Tomcat收到SHUTDOWN命令之后,主线程就会退出await状态,await方法也执行结束, 从而stop方法被调用,Tomcat停止运行。

这里我

们需要注意一下server instanceof Lifecycle 的代码。Lifecycle是一个生命周期接口,定义了各种生命周期方法start和 stop。只有实现了该接口的组件才能拥有生命周期方法。生命周期方法的调用是嵌套的,父组件的生命周期方法会调用子组件的同名方法。这样,只需调用顶层组件的start方法,就可以启动所有子孙组件。stop也一样。因此,生命周期方法和组件关联关系,使得Tomcat 很容易管理各个组件的启动和停止。

OK,start方法也介绍完了。各组件的start方法被调用之后,Tomcat已经处于就绪状态,等待请求的到来。

本文的最后,详细讨论一下await状态的实现细节。


await状态
setAwait方法
Bootstrap 类的main方法中,处理start 启动参数时,会调用 setAwait 方法。

Java代码
1.public void setAwait(boolean await)
2. throws Exception {
3. Class paramTypes[] = new Class[1];
4. paramTypes[0] = Boolean.TYPE;
5. Object paramValues[] = new Object[1];
6. paramValues[0] = new Boolean(await);
7. Method method =
8. catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
9. method.invoke(catalinaDaemon, paramValues);
10. }
public void setAwait(boolean await)
throws Exception {
Class paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = new Boolean(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
上述代码其实调用了Catalina 类的setAwait 方法。Catalina 的setAwait 方法定义在于其父类Embedded ,主要就是设置成员变量await的值 。

Java代码
1.public void setAwait(boolean b) {
2. await = b;
3.}
public void setAwait(boolean b) {
await = b;
}

await 变成true 之后,Tomcat 就进入await 状态,这一点在start 方法中已经分析过。

await方法
如果成员变量await 为true ,那么Catalina 类的await 方法就会被调用。

Java代码
1.public void await() {
2.
3. //直接调用StandardServer的await方法
4. server.await();
5.}
public void await() {

//直接调用StandardServer的await方法
server.await();
}

下面我们看看StandardServer 类的await 方法。

Java代码
1.public void await() {
2. // port是SHUTDOWN端口。如果值为-2,则表示不进入await状态,直接返回;如果值为-1,则表示通过简单循环的方式来实现await状态。
3. // 如果port为-2,则表示不进入await状态,直接返回;如果值为-1,则表示通过简单循环的方式来实现 await状态。
4. // 如果port为-1,则表示通过简单循环

相关文档
最新文档