这篇随笔的重点关注启动Tomcat时会用到的两个类,分别是Catalina类 和 Bootstrap类,它们都位于org.apache.catalina.startup包下,Catalina类用于启动或关闭Server对象,并负责解析Tomcat文件:server.xml文件。Bootstrap类是一个入口点,负责创建Catalina实例,并调用其process()方法,理论上这两个类可以合并为一个,但是为了支持Tomcat的多种运行模式,而提供了多种启动类,例如,上面说到的Bootstrap类是作为一个独立的应用程序运行Tomcat的,而另一个类org.apahce.catalina.starup.BootstrapService可以使Tomcat作为一个Windows NT服务来运行。
为了使用户使用方便,Tomcat附带了批处理文件 和 shell 脚本,可以方便的启动或者关闭servlet容器,在这些批处理 文件 和 shell 脚本的帮助下, 用户无须为了执行Bootstrap类 而记下 java.exe程序的选项,相反,用户只需要运行相应的批处理文件或者 shell脚本即可,
先介绍一下Catalina类
Catalina类
org.apache.catalina.startup.Catalina类是启动类,它包含了一个Digester对象,用于解析位于%CATALIN_HOME%conf目录下的server.xml文件。理解了添加到Digester对象中的规则之后,就可以自行配置Tomcat了。
Catalina类还封装了一个Server对象,该对象有一个Service对象,正如之前学习的那样,Service对象包含一个Servlet容器 和 一个 或者多个连接器,可以使用Catalina类来启动或者关闭Server对象,
可以通过实例化Catalina类,并调用其process方法来运行Tomcat,但是在调用该方法时,需要传入适当的参数,第一个参数是start 表示要启动Tomcat,或 stop 表示要向Tomcat发送一条关闭命令,还有其他的参数可选,包括-help、-config、-debug和-nothing
注意:当使用nothing参数时,表示将不对JNDI 命名提供支持,更多关于Tomcat中 对JNDI命名的支持 看org.apahce.naming包中的类吧
一般情况下,及时Catalina类提供了main方法作为程序的入口点,也需要使用Bootstrap类来实例化Catalina类,并调用其process方法,下面给出procee的实现
复制代码
/**
* 实例主程序。
*
* @param args Command line arguments
*/
public void process(String args[]) {
setCatalinaHome();
setCatalinaBase();
try {
if (arguments(args))
execute();
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
复制代码
process 方法设置了两个系统属性,分别是 catalina.home 和 catalina.base,默认值均与user.dir值相同,
注意:user.dir属性的值指明了用户的工作目录,即,会从哪个目录下调用java命令,更多系统属性的列表,可以参见java.lang.System类 getProperties方法的介绍
然后procee 方法会调用arguments方法,并传入参数列表,arguments方法处理命令行参数,如果Catalina对象能够继续处理的话,arguments方法会返回true,
复制代码
/**
* 处理指定的命令行参数,如果应该继续处理,则返回true;否则返回false。
*
* @param args
* Command line arguments to process
*/
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("-debug")) {
debug = true;
} else if (args[i].equals("-nonaming")) {
useNaming = false;
} else if (args[i].equals("-help")) {
usage();
return (false);
} else if (args[i].equals("start")) {
starting = true;
} else if (args[i].equals("stop")) {
stopping = true;
} else {
usage();
return (false);
}
}
return (true);
}
复制代码
process方法会检查arguments方法的返回值,如果返回值为true,则调用execute方法
复制代码
1 /**
2 * 执行 命令行配置后 处理。
3 *
4 *
5 */
6 protected void execute() throws Exception {
7
8 // 如果命令启动则启动
9 if (starting)
10 start();
11 // 如果命令停止则停止
12 else if (stopping)
13 stop();
14
15 }
复制代码
execute方法会调用start方法 启动Tomcat 或者 调用stop方法来关闭Tomcat。
注意:在Tomcat 5 以及之后,没有execute方法,会在procee方法中调用 start方法 或者 stop方法
Start方法
start方法 会创建一个Digester实例来解析server.xml文件(Tomcat 配置文件),在解析 server.xml文件之前,start方法会调用Digester对象的push方法,传入当前Catalin对象作为参数,这样Catalina对象就成为了 Digester对象的内部栈中的第一个对象,解析Server.xml文件之后,会使变量server引用一个Server对象,默认是org.apache.catalina.core.StandardServer类型的对象,然后start方法会调用Server对象的initialize方法 和 start方法,接着,Catalina对象的start方法会调用Server对象的await反方循环等待,直到接收到正确的关闭命令,当await方法返回时,Catalina对象的start方法会调用Server对象的stop方法,从而关闭Server对象和其他的组件,此外 start方法 还会使用关闭钩子,确保用户突然退出应用程序时会执行Server对象的stop方法。
复制代码
1 /**
2 * 启动一个新的Server实例
3 */
4 protected void start() {
5
6 // 创建一个解析XML文件的Digester
7 Digester digester = createStartDigester();
8 // 包含 服务器配置文件的File引用
9 File file = configFile();
10 try {
11 InputSource is = new InputSource("file://" + file.getAbsolutePath());
12 FileInputStream fis = new FileInputStream(file);
13 is.setByteStream(fis);
14 // 将该Catalina对象压入Digester对象的内部栈中,成为内部栈中的第一个元素
15 digester.push(this);
16 digester.parse(is);
17 fis.close();
18 } catch (Exception e) {
19 System.out.println("Catalina.start: " + e);
20 e.printStackTrace(System.out);
21 System.exit(1);
22 }
23
24 // 设置附加变量
25 if (!useNaming) {
26 System.setProperty("catalina.useNaming", "false");
27 } else {
28 System.setProperty("catalina.useNaming", "true");
29 String value = "org.apache.naming";
30 String oldValue = System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
31 if (oldValue != null) {
32 value = value + ":" + oldValue;
33 }
34 System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);
35 value = System.getProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY);
36 if (value == null) {
37 System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
38 "org.apache.naming.java.javaURLContextFactory");
39 }
40 }
41
42 // If a SecurityManager is being used, set properties for
43 // checkPackageAccess() and checkPackageDefinition
44 if (System.getSecurityManager() != null) {
45 String access = Security.getProperty("package.access");
46 if (access != null && access.length() > 0)
47 access += ",";
48 else
49 access = "sun.,";
50 Security.setProperty("package.access", access + "org.apache.catalina.,org.apache.jasper.");
51 String definition = Security.getProperty("package.definition");
52 if (definition != null && definition.length() > 0)
53 definition += ",";
54 else
55 definition = "sun.,";
56 Security.setProperty("package.definition",
57 // FIX ME package "javax." was removed to prevent HotSpot
58 // fatal internal errors
59 definition + "java.,org.apache.catalina.,org.apache.jasper.");
60 }
61
62 // Replace System.out and System.err with a custom PrintStream
63 SystemLogHandler log = new SystemLogHandler(System.out);
64 System.setOut(log);
65 System.setErr(log);
66
67 // 创建一个关闭钩子
68 Thread shutdownHook = new CatalinaShutdownHook();
69
70 // 初始化 Server对象
71 if (server instanceof Lifecycle) {
72 try {
73 server.initialize();
74 ((Lifecycle) server).start();
75 try {
76 // 注册关闭钩子
77 Runtime.getRuntime().addShutdownHook(shutdownHook);
78 } catch (Throwable t) {
79 // This will fail on JDK 1.2. Ignoring, as Tomcat can run
80 // fine without the shutdown hook.
81 }
82 // 等待关闭命令 阻塞 直到 收到正确的关闭命令
83 server.await();
84 } catch (LifecycleException e) {
85 System.out.println("Catalina.start: " + e);
86 e.printStackTrace(System.out);
87 if (e.getThrowable() != null) {
88 System.out.println("----- Root Cause -----");
89 e.getThrowable().printStackTrace(System.out);
90 }
91 }
92 }
93
94 // 关闭服务器组件
95 if (server instanceof Lifecycle) {
96 try {
97 try {
98 // 首先删除关闭钩子,以便server.stop()
99
100 // 不会被调用两次
101 Runtime.getRuntime().removeShutdownHook(shutdownHook);
102 } catch (Throwable t) {
103 // This will fail on JDK 1.2. Ignoring, as Tomcat can run
104 // fine without the shutdown hook.
105 }
106 // 关闭服务器组件
107 ((Lifecycle) server).stop();
108 } catch (LifecycleException e) {
109 System.out.println("Catalina.stop: " + e);
110 e.printStackTrace(System.out);
111 if (e.getThrowable() != null) {
112 System.out.println("----- Root Cause -----");
113 e.getThrowable().printStackTrace(System.out);
114 }
115 }
116 }
117
118 }
复制代码
stop方法
stop方法用来关闭Catalina 和 Server对象
复制代码
1 /**
2 * 停止现有的服务器实例。 其实就是手动 向 服务器组件负责监听关闭命令的端口发送 关闭名命令
3 */
4 protected void stop() {
5
6 // 创建一个解析服务器组件配置XML文件的Digester
7 Digester digester = createStopDigester();
8 File file = configFile();
9 try {
10 InputSource is = new InputSource("file://" + file.getAbsolutePath());
11 FileInputStream fis = new FileInputStream(file);
12 is.setByteStream(fis);
13 digester.push(this);
14 digester.parse(is);
15 fis.close();
16 } catch (Exception e) {
17 System.out.println("Catalina.stop: " + e);
18 e.printStackTrace(System.out);
19 System.exit(1);
20 }
21
22 // 向服务器组件 指定监听关闭命令端口发送关闭命令
23 try {
24 Socket socket = new Socket("127.0.0.1", server.getPort());
25 OutputStream stream = socket.getOutputStream();
26 String shutdown = server.getShutdown();
27 for (int i = 0; i < shutdown.length(); i++)
28 stream.write(shutdown.charAt(i));
29 stream.flush();
30 stream.close();
31 socket.close();
32 } catch (IOException e) {
33 System.out.println("Catalina.stop: " + e);
34 e.printStackTrace(System.out);
35 // 如果发送命令时异常 则直接退出 使用关闭钩子来关闭服务器组件
36 System.exit(1);
37 }
38
39 }
复制代码
启动Digester对象
Catalina类的createStartDigester方法创建了一个Digester实例,然后为其添加规则,以解析server.xml文件,server.xml文件用来配置Tomcat,位于%CATALINA_HOME%/conf目录下,添加到Digester对象中的规则 是理解Tomcat配置的关键,
复制代码
/**
* 创建和配置我们将用于启动的Digester
*/
protected Digester createStartDigester() {
// 初始化Digester
Digester digester = new Digester();
if (debug)
digester.setDebug(999);
// 不校验XML的格式内容
digester.setValidating(false);
// 配置我们将要使用的操作
// 遇到Server模式时,使用Server元素的className的值来创建Server实例
digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className");
// 配置Server 各种属性
digester.addSetProperties("Server");
// 将Server实例 与当前Catalina对象关联上,利用setServer方法,将Server实例传入,Server对象类型为
// "org.apache.catalina.Server"
digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");
// 遇到Server/GlobalNamingResources模式时,创建
// org.apache.catalina.deploy.NamingResources实例
digester.addObjectCreate("Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResources");
// 设置模式 Server/GlobalNamingResources 生成的对象各种属性
digester.addSetProperties("Server/GlobalNamingResources");
// 将其与Server对象关联起来,利用 Server对象的setGlobalNamingResources方法传入
// GlobalNamingResources类型实例
digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResources");
// xml规则到这里 如果存在上面 Server/GlobalNamingResources元素 到这里可定会遇到结束元素标签
// 会将该值从内部栈中移除
// 遇到 Server/Listener 必须制定 Listener实现类
digester.addObjectCreate("Server/Listener", null, // MUST be specified
// in the element
"className");
// 配置Listener对象属性
digester.addSetProperties("Server/Listener");
// 将Listener与Server对象关联起来通过 addLifecycleListener
digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener");
// 同样 在这里 如果存在 Listener标签 也会遇到结束标签 然后将 Listener对象移除从内部栈中
// 遇到Server/Service模式 创建 Service
digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service");
// 真滴累 不想在写了 如果熟悉 Digester的人其实不用再写了 如果不熟悉的话 可以参考之前写的随笔Digester
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");
digester.addObjectCreate("Server/Service/Connector", "org.apache.catalina.connector.http.HttpConnector"