Tomcat启动分析(一)的末尾提到Bootstrap类利用反射实例化Catalina类后接着调用Catalina类的load方法,本文先从load方法开始分析Catalina类,然后再分析start方法。
load方法
Catalina类的load方法代码如下:
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Create and execute our Digester
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
// 省略一些代码
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
// 省略一些代码
}
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
简单地说,load方法主要做了以下几件事:
- initDirs方法初始化目录,检查java.io.tmdir系统属性是否存在;
- initNaming方法初始化命名,这个不清楚做什么的,好像是与JNDI有关的;
- createStartDigester方法创建Digester解析conf/server.xml文件,digester.parse(inputSource)开始真正的解析过程;
- 为Server实例设置Tomcat安装目录和工作目录;
- initStreams方法设置新的标准输出和标准错误输出;
- Server实例初始化。
下面详细分析上述每项工作。
initDirs方法
initDirs方法很简单,就是检查一下java.io.tmpdir属性表示的是否是一个存在的目录:
protected void initDirs() {
String temp = System.getProperty("java.io.tmpdir");
if (temp == null || (!(new File(temp)).isDirectory())) {
log.error(sm.getString("embedded.notmp", temp));
}
}
initNaming方法
TODO,暂时略过,不太懂JNDI。
解析server.xml文件
因为笔者以前做过和XML解析有关的工作,所以在load方法中看到InputSource后就意识到该部分代码与XML有关。解析工作由Digester类完成,该类的实例由createStartDigester方法创建,解析入口在digester.parse(inputSource)这一行。Digester类一边解析XML,一边利用反射实例化各个组件并建立组件之间的关系。实际上,Apache有一个专门解析XML的Digester项目,感兴趣的读者可以深入了解。
Catalina类的createStartDigester方法部分代码如下:
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<>();
ArrayList<String> attrs = new ArrayList<>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addObjectCreate("Server/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
// 省略一些代码
long t2=System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Digester for server.xml created " + ( t2-t1 ));
}
return (digester);
}
Server、Server/Listener和Server/Service等看着很眼熟,很像server.xml中的元素。从Digester类看看server.xml是如何被解析的。
1. Digester类
Digester类继承了SAX包的DefaultHandler2,重写了如startElement,endElement等解析事件处理方法。其在内部维护了一个对象栈,用来保存解析过程中创建的对象。在Catalina的load方法中,digester.push(this);使Catalina实例自身在开始解析前先入栈。
createStartDigester方法调用了很多次addObjectCreate、addSetNext、addSetProperties和addRule方法,这些方法代码如下,它们都与Rule的概念有关。
public void addObjectCreate(String pattern, String className, String attributeName) {
addRule(pattern, new ObjectCreateRule(className, attributeName));
}
public void addSetNext(String pattern, String methodName, String paramType) {
addRule(pattern, new SetNextRule(methodName, paramType));
}
public void addSetProperties(String pattern) {
addRule(pattern, new SetPropertiesRule());
}
2. Rule
简单地说,Rule就是解析XML时遇到符合某种模式的元素时执行的动作。抽象类Rule的代码如下所示,最重要的方法分别是begin、body和end,分别在遇到匹配元素的起始处、元素体(body)和结尾处触发。
public abstract class Rule {
// 省略一些代码
public void begin(String namespace, String name, Attributes attributes) throws Exception {
// NO-OP by default.
}
public void body(String namespace, String name, String text) throws Exception {
// NO-OP by default.
}
public void end(String namespace, String name) throws Exception {
// NO-OP by default.
}
}
从Digester的方法也可以看到Rule的各个方法的执行时机,startElement和endElement分别重写了DefaultHandler2的方法,表示在元素起始和结束时触发。从下述代码中可以看到startElement方法会执行匹配Rule的begin方法,而endElement方法会执行匹配Rule的body和end方法。
@Override
public void startElement(String namespaceURI, String localName, String qName, Attributes list)
throws SAXException {
// 省略一些代码
// Fire "begin" events for all relevant rules
List<Rule> rules = getRules().match(namespaceURI, match);
matches.push(rules);
if ((rules != null) && (rules.size() > 0)) {
for (int i = 0; i < rules.size(); i++) {
try {
Rule rule = rules.get(i);
if (debug) {
log.debug(" Fire begin() for " + rule);
}
rule.begin(namespaceURI, name, list);
} catch (Exception e) {
// 省略一些代码
}
}
}
}
@Override
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException {
// 省略一些代码
// Fire "body" events for all relevant rules
List<Rule> rules = matches.pop();
if ((rules != null) && (rules.size() > 0)) {
String bodyText = this.bodyText.toString();
for (int i = 0; i < rules.size(); i++) {
try {
Rule rule = rules.get(i);
if (debug) {
log.debug(" Fire body() for " + rule);
}
rule.body(namespaceURI, name, bodyText);
} catch (Exception e) {
// 省略一些代码
}
}
}
// Recover the body text from the surrounding element
bodyText = bodyTexts.pop();
// Fire "end" events for all relevant rules in reverse order
if (rules != null) {
for (int i = 0; i < rules.size(); i++) {
int j = (rules.size() - i) - 1;
try {
Rule rule = rules.get(j);
if (debug) {
log.debug(" Fire end() for " + rule);
}
rule.end(namespaceURI, name);
} catch (Exception e) {
// 省略一些代码
}
}
}
// 省略一些代码
}
-
2.1 ObjectCreateRule类
ObjectCreateRule类的代码如下,正如其类名暗示的那样,其在begin方法中会根据类名利用反射实例化对象,并将该对象压进Digester的对象栈,而end方法会使Digester对象栈做出栈操作。public class ObjectCreateRule extends Rule { // 省略一些代码 @Override public void begin(String namespace, String name, Attributes attributes) throws Exception { // Identify the name of the class to instantiate String realClassName = className; if (attributeName != null) { String value = attributes.getValue(attributeName); if (value != null) { realClassName = value; } } if (digester.log.isDebugEnabled()) { digester.log.debug("[ObjectCreateRule]{" + digester.match + "}New " + realClassName); } if (realClassName == null) { throw new NullPointerException("No class name specified for " + namespace + " " + name); } // Instantiate the new object and push it on the context stack Class<?> clazz = digester.getClassLoader().loadClass(realClassName); Object instance = clazz.getConstructor().newInstance(); digester.push(instance); } } @Override public void end(String namespace, String name) throws Exception { Object top = digester.pop(); if (digester.log.isDebugEnabled()) { digester.log.debug("[ObjectCreateRule]{" + digester.match + "} Pop " + top.getClass().getName()); } }
-
2.2 SetPropertiesRule类
SetPropertiesRule类的代码如下,正如其类名暗示的那样,其在begin方法中使用IntrospectionUtils类的setProperty方法调用ObjectCreateRule所创建实例的setter方法设置属性:public class SetPropertiesRule extends Rule { // 省略一些代码 @Override public void begin(String namespace, String theName, Attributes attributes) throws Exception { // Populate the corresponding properties of the top object Object top = digester.peek(); if (digester.log.isDebugEnabled()) { if (top != null) { digester.log.debug("[SetPropertiesRule]{" + digester.match + "} Set " + top.getClass().getName() + " properties"); } else { digester.log.debug("[SetPropertiesRule]{" + digester.match + "} Set NULL properties"); } } for (int i = 0; i < attributes.getLength(); i++) { String name = attributes.getLocalName(i); if ("".equals(name)) { name = attributes.getQName(i); } String value = attributes.getValue(i); if (digester.log.isDebugEnabled()) { digester.log.debug("[SetPropertiesRule]{" + digester.match + "} Setting property '" + name + "' to '" + value + "'"); } if (!digester.isFakeAttribute(top, name) && !IntrospectionUtils.setProperty(top, name, value) && digester.getRulesValidation()) { digester.log.warn("[SetPropertiesRule]{" + digester.match + "} Setting property '" + name + "' to '" + value + "' did not find a matching property."); } } } }
-
2.3 SetNextRule类
SetNextRule类的代码如下,其在end方法中使用IntrospectionUtils类的callMethod1方法在Digester对象栈栈顶元素和次栈顶元素间建立关联关系,即在次栈顶的对象上调用方法,参数是栈顶的对象。
以createStartDigester方法中的两行代码为例:public class SetNextRule extends Rule { // 省略一些代码 @Override public void end(String namespace, String name) throws Exception { // Identify the objects to be used Object child = digester.peek(0); Object parent = digester.peek(1); if (digester.log.isDebugEnabled()) { if (parent == null) { digester.log.debug("[SetNextRule]{" + digester.match + "} Call [NULL PARENT]." + methodName + "(" + child + ")"); } else { digester.log.debug("[SetNextRule]{" + digester.match + "} Call " + parent.getClass().getName() + "." + methodName + "(" + child + ")"); } } // Call the specified method IntrospectionUtils.callMethod1(parent, methodName, child, paramType, digester.getClassLoader()); } }
- Server/Listener表示在处理<Server>元素的<Listener>子元素后需要执行SetNextRule规则,此时栈顶应该是Listener对象,次栈顶是Server对象。SetNextRule会调用Server的addLifecycleListener方法,参数即是Listener对象,参数类型是org.apache.catalina.LifecycleListener;
- Server/Service/Listener表示在处理<Service>元素的<Listener>子元素后需要执行SetNextRule规则,此时栈顶应该是Listener对象,次栈顶是Service对象,再次栈顶才是Server对象。SetNextRule会调用Service的addLifecycleListener方法,参数即是Listener对象,参数类型是org.apache.catalina.LifecycleListener。
digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addSetNext("Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener");
3. 解析实例
下面以Tomcat自带的server.xml为例说明这些Rule的作用,server.xml的内容如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<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>
以Server模式为例,createStartDigester方法为其添加了三个Rule:
digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className");
digester.addSetProperties("Server");
digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");
当XML解析器遇到server.xml里面的<Server>元素时:
- 遇到<Server port="8005" shutdown="SHUTDOWN">时,首先ObjectCreateRule创建一个org.apache.catalina.core.StandardServer类型的实例,然后SetPropertiesRule将<Server>元素的属性和值设置到生成的实例上,即在StandardServer实例上调用setPort将port设置为8005、调用setShutdown将shutdown设置为SHUTDOWN;
- 遇到<Server/>时,Digester对象栈栈顶是生成的StandardServer实例,次栈顶对象是Catalina实例(上文提到开始解析前Catalina实例自身先入栈),SetNextRule在Catalina实例上调用setServer方法,参数是栈顶的StandardServer实例,方法参数类型是org.apache.catalina.Server。
4. 解析结果
对照createStartDigester方法和Tomcat自带的server.xml,XML解析后生成了如下组件:
-
4.1 Server元素
digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");
创建了一个StandardServer实例,关闭端口是8005,关闭命令是SHUTDOWN。
-
4.2 Listener元素
digester.addObjectCreate("Server/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Listener"); digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener");
分别创建了VersionLoggerListener、AprLifecycleListener、JreMemoryLeakPreventionListener、GlobalResourcesLifecycleListener和ThreadLocalLeakPreventionListener实例,这些Listener都被添加到了StandardServer上。
-
4.3 Service元素
digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service");
创建StandardService实例,这个实例被添加到了SnadardServer上。
-
4.4 Connector元素
digester.addRule("Server/Service/Connector", new ConnectorCreateRule()); digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"})); digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector");
ConnectorCreateRule的部分源码如下:
@Override public void begin(String namespace, String name, Attributes attributes) throws Exception { Service svc = (Service)digester.peek(); Executor ex = null; if ( attributes.getValue("executor")!=null ) { ex = svc.getExecutor(attributes.getValue("executor")); } Connector con = new Connector(attributes.getValue("protocol")); if (ex != null) { setExecutor(con, ex); } String sslImplementationName = attributes.getValue("sslImplementationName"); if (sslImplementationName != null) { setSSLImplementationName(con, sslImplementationName); } digester.push(con); }
ConnectorCreateRule的begin方法中调用了Connector类的构造方法,参数是xml属性protocol的值,相关代码如下:
public Connector(String protocol) { setProtocol(protocol); // Instantiate protocol handler ProtocolHandler p = null; try { Class<?> clazz = Class.forName(protocolHandlerClassName); p = (ProtocolHandler) clazz.getConstructor().newInstance(); } catch (Exception e) { log.error(sm.getString( "coyoteConnector.protocolHandlerInstantiationFailed"), e); } finally { this.protocolHandler = p; } if (Globals.STRICT_SERVLET_COMPLIANCE) { uriCharset = StandardCharsets.ISO_8859_1; } else { uriCharset = StandardCharsets.UTF_8; } } public void setProtocol(String protocol) { boolean aprConnector = AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseAprConnector(); if ("HTTP/1.1".equals(protocol) || protocol == null) { if (aprConnector) { setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol"); } else { setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol"); } } else if ("AJP/1.3".equals(protocol)) { if (aprConnector) { setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol"); } else { setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol"); } } else { setProtocolHandlerClassName(protocol); } } public void setProtocolHandlerClassName(String protocolHandlerClassName) { this.protocolHandlerClassName = protocolHandlerClassName; }
- setProtocol方法根据xml属性protocol的值设置成员变量protocolHandlerClassName的值;aprConnector变量的含义是是否使用Tomcat的APR库替代Java NIO,默认是false,如何启用请参阅Connector文档的protocol属性一栏;
- Connector构造方法接着根据protocolHandlerClassName利用反射实例化ProtocolHandler;
server.xml中的HTTP/1.1和AJP/1.3分别创建了用Http11NioProtocol处理的Connector和用AjpNioProtocol处理的Connector两个实例。
-
4.5 其他
其他元素的解析过程同理,在此不再赘述,有兴趣的读者可自行阅读完整的createStartDigester方法。
为Server实例设置目录属性
getServer方法返回XML解析过程中生成的Server实例,默认实现类是StandardServer,以下代码为该实例设置目录属性。
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
initStreams方法
initStreams方法调用System类的方法重定向标准输出和标准错误输出:
protected void initStreams() {
// Replace System.out and System.err with a custom PrintStream
System.setOut(new SystemLogHandler(System.out));
System.setErr(new SystemLogHandler(System.err));
}
Server实例初始化
Server实例初始化和后面Catalina类的start方法主要是调用StandardServer实例的init和start方法,它们都是org.apache.catalina.Lifecycle接口的方法,具体分析请参见后续文章。