Tomcat源码分析——SERVER.XML文件的加载与解析
前言
作为Java程序员,对于Tomcat的server.xml想必都不陌生。本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载和解析的进行分析。
加载
server.xml也是文件,Tomcat加载它会不会有什么不同的实现呢?
Bootstrap的load方法是加载Tomcat的server.xml的入口,load方法实际通过反射调用catalinaDaemon(类型为Catalina)的load方法,见代码清单1。
代码清单1
/**
* Load daemon.
*/
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); }
顺便向所有熟悉Tomcat的开发者问个问题,为什么Tomcat要用反射来调用catalinaDaemon的load方法,而不是采用catalinaDaemon.load()的方式呢?有知道的人希望通过短消息或者回复,告诉我,我将不胜感激。
抛开这个问题,继续讲解server.xml的加载,代码清单1使用反射调用了Catalina的load方法,load方法的实现见代码清单2。
代码清单2
/**
* Start a new server instance.
*/
public void load() { long t1 = System.nanoTime(); initDirs(); // Before digester - it may be needed jiaan initNaming(); // Create and execute our Digester
Digester digester = createStartDigester(); InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource("file://" + file.getAbsolutePath());
} catch (Exception e) {
// Ignore
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
// Ignore
}
} // 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) {
// Ignore
}
} if ((inputStream == null) && (file != null)) {
log.warn("Can't load server.xml from " + file.getAbsolutePath());
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
return;
} try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
inputStream.close();
} catch (Exception e) {
log.warn("Catalina.start using "
+ getConfigFile() + ": " , e);
return;
} // 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"); }
这里对代明清单2进行分析,其执行步骤如下:
1) initDirs方法用于对catalina.home和catalina.base的一些检查工作。
2) initNaming方法给系统设置java.naming.factory.url.pkgs和java.naming.factory.initial。在创建JNDI上下文时,使用Context.INITIAL_CONTEXT_FACTORY("java.naming.factory.initial")属性,来指定创建JNDI上下文的工厂类;Context.URL_PKG_PREFIXES("java.naming.factory.url.pkgs")用在查询url中包括scheme方法id时创建对应的JNDI上下文,例如查询"java:/jdbc/test1"等类似查询上,即以冒号":"标识的shceme。Context.URL_PKG_PREFIXES属性值有多个java 包(package)路径,其中以冒号":"分隔各个包路径,这些包路径中包括JNDI相关实现类。当在JNDI上下文中查找"java:"这类包括scheme方案ID的url时,InitialContext类将优先查找Context.URL_PKG_PREFIXES属性指定的包路径中是否存在 scheme+"."+scheme + "URLContextFactory"工厂类(需要实现ObjectFactory接口),如果存在此工厂类,则调用此工厂类的getObjectInstance方法获得此scheme方案ID对应的jndi上下文,再在此上下文中继续查找对应的url。
3) createStartDigester方法创建并配置将要用来启动的Digester实例,并且设置一系列Rule,具体映射到server.xml。
4) 使用FileInputStream获取conf/server.xml配置文件输入流。
5) 将FileInputStream封装为InputSource,并且调用Digester的parse方法进行解析。
6) initStreams对输出流、错误流重定向。
7) 初始化server,具体实现本文不做分析。
我们看到Tomcat加载server.xml配置文件的方式,非常传统,正是使用FileInputStream进行加载的。
解析
当加载server.xml配置文件到内存后,开始对XML文件中的内容进行解析,主要包含两个步骤:
- 构造server.xml的规则,这些规则即可以用于构造Tomcat内部的容器(如StandardServer,StandardService等),也可以对server.xml进行合法性检查。如果server.xml不符合Tomcat内置的规则,在解析时将抛出异常,进而导致Tomcat无法启动。
- 使用SAX解析server.xml,边解析边应用规则,最终使用server.xml中的配置构建好Tomcat所需的各种容器。
server.xml解析的整个过程可以用图1来表示。
图1 server.xml解析过程
规则
Tomcat将server.xml文件中的所有元素上的属性都抽象为Rule,以Server元素为例,在内存中对应Server实例,Server实例的属性值就来自于Server元素的属性值。通过对规则(Rule)的应用,最终改变Server实例的属性值。Rule是一个抽象类,其中定义了以下方法:
- getDigester:获取Digester实例;setDigester:设置Digester实例;
- getNamespaceURI:获取Rule所在的相对命名空间URI;
- setNamespaceURI:设置Rule所在的相对命名空间URI;
- begin(String namespace, String name, Attributes attributes):此方法在遇到一个匹配的XML元素的开头时被调用,如<Server;
- body(String namespace, String name, String text):在遇到匹配XML元素的body时,此方法被调用,如进入标签内部时;
- end(String namespace, String name):此方法在遇到一个匹配的XML元素的末尾时被调用。如:</Server>;
Rule目前有很多实现类,如:NodeCreateRule、AbsoluteOrderingRule、CallParamRule、ConnectorCreateRule等,图2展示了Rule的部分实现类。
图2 Rule的部分实现类
这里以最常用的几个规则表示Rule的类继承体系,如图3所示。
图3 Rule的类继承体系
SAX
相比于 DOM 而言 SAX(Simple API for XML) 是一种速度更快,更有效,占用内存更少的解析 XML 文件的方法。它是逐行扫描,可以做到边扫描边解析,因此 SAX 可以在解析文档的任意时刻停止解析。SAX 是基于事件驱动的。SAX 不用解析完整个文档,在按内容顺序解析文档过程中, SAX 会判断当前读到的字符是否符合 XML 文件语法中的某部分。如果符合某部分,则会触发事件。所谓触发事件,就是调用一些回调方法。在用 SAX 解析 xml 文档时候,在读取到文档开始和结束标签时候就会回调一个事件,在读取到其他节点与内容时候也会回调一个事件。在 SAX 接口中,事件源是 org.xml.sax 包中的 XMLReader ,它通过 parser() 方法来解析 XML 文档,并产生事件。事件处理器是 org.xml.sax 包中 ContentHander 、 DTDHander 、 ErrorHandler ,以及 EntityResolver 这 4 个接口。表1列出了这些事件处理器的处理的事件及注册方法。
表1
事件处理器 | 事件处理器处理的事件 | XMLReader 注册方法 |
ContentHander | XML 文档的开始与结束 | setContentHandler(ContentHandler h) |
DTDHander | 处理 DTD 解析 | setDTDHandler(DTDHandler h) |
ErrorHandler | 处理 XML 时产生的错误 | setErrorHandler(ErrorHandler h) |
EntityResolver | 处理外部实体 | setEntityResolver(EntityResolver e) |
我们用来做内容解析的回调方法一般都定义在 ContentHandler 接口中 。ContentHandler 接口常用的方法如下:
- startDocument() :当遇到文档的开头的时候,调用这个方法,可以在其中做一些预处理的工作。
- endDocument() :当文档结束的时候,调用这个方法,可以在其中做一些善后的工作。
- startElement(String namespaceURI, String localName,String qName, Attributes atts):当读到开始标签的时候,会调用这个方法。 namespaceURI 就是命名空间, localName 是不带命名空间前缀的标签名, qName 是带命名空间前缀的标签名。通过 atts 可以得到所有的属性名和相应的值。
- endElement(String uri, String localName, String name):在遇到结束标签的时候,调用这个方法。
- characters(char[] ch, int start, int length):这个方法用来处理在 XML 文件中读到的内容。例如: 主要目的是获取 high 标签中的值。
使用 SAX 解析 XML 文件一般包括以下步骤:
- 创建一个 SAXParserFactory 对象;
- 调用 SAXParserFactory 中的 newSAXParser 方法创建一个 SAXParser 对象;
- 然后在调用 SAXParser 中的 getXMLReader 方法获取一个 XMLReader 对象;
- 实例化一个 DefaultHandler 对象;
- 连接事件源对象 XMLReader 到事件处理类 DefaultHandler 中;
- 调用 XMLReader 的 parse 方法从输入源中获取到的 xml 数据;
- 通过 DefaultHandler 返回我们需要的数据集合。
源码分析
构造server.xml的规则
前面在介绍Catalina的load方法时,遇见了createStartDigester方法,它的实现如代码清单3。
代码清单3
/**
* Create and configure the Digester we will be using for startup.
*/
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<Class<?>, List<String>>();
ArrayList<String> attrs = new ArrayList<String>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
digester.setClassLoader(StandardServer.class.getClassLoader()); // 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.NamingResources");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResources"); 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"); 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"); //Executor
digester.addObjectCreate("Server/Service/Executor",
"org.apache.catalina.core.StandardThreadExecutor",
"className");
digester.addSetProperties("Server/Service/Executor"); digester.addSetNext("Server/Service/Executor",
"addExecutor",
"org.apache.catalina.Executor"); digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
new SetAllPropertiesRule(new String[]{"executor"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector"); digester.addObjectCreate("Server/Service/Connector/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/Listener");
digester.addSetNext("Server/Service/Connector/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener"); // Add RuleSets for nested elements
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Host/Cluster/"));
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Cluster/")); long t2=System.currentTimeMillis();
if (log.isDebugEnabled())
log.debug("Digester for server.xml created " + ( t2-t1 ));
return (digester); }
代码清单3首先创建Digester,Digester继承了DefaultHandler,而DefaultHandler默认实现了ContentHander、DTDHander、ErrorHandler及EntityResolver 这4个接口,代码如下:
public class DefaultHandler
implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler
如果阅读DefaultHandler的源码,发现它的所有实现都是空实现,看来要发挥解析作用,只能依靠Digester自己了。Digester实现了以上接口中的方法,见代码清单4。
代码清单4
@Override
public void startDocument() throws SAXException { if (saxLog.isDebugEnabled()) {
saxLog.debug("startDocument()");
} configure();
} @Override
public void endDocument() throws SAXException { if (saxLog.isDebugEnabled()) {
if (getCount() > 1) {
saxLog.debug("endDocument(): " + getCount() +
" elements left");
} else {
saxLog.debug("endDocument()");
}
} while (getCount() > 1) {
pop();
} // Fire "finish" events for all defined rules
Iterator<Rule> rules = getRules().rules().iterator();
while (rules.hasNext()) {
Rule rule = rules.next();
try {
rule.finish();
} catch (Exception e) {
log.error("Finish event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Finish event threw error", e);
throw e;
}
} // Perform final cleanup
clear(); } @Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes list)
throws SAXException {
boolean debug = log.isDebugEnabled(); if (saxLog.isDebugEnabled()) {
saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
qName + ")");
} // Parse system properties
list = updateAttributes(list); // Save the body text accumulated for our surrounding element
bodyTexts.push(bodyText);
if (debug) {
log.debug(" Pushing body text '" + bodyText.toString() + "'");
}
bodyText = new StringBuilder(); // the actual element name is either in localName or qName, depending
// on whether the parser is namespace aware
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
} // Compute the current matching rule
StringBuilder sb = new StringBuilder(match);
if (match.length() > 0) {
sb.append('/');
}
sb.append(name);
match = sb.toString();
if (debug) {
log.debug(" New match='" + match + "'");
} // 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) {
log.error("Begin event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Begin event threw error", e);
throw e;
}
}
} else {
if (debug) {
log.debug(" No rules found matching '" + match + "'.");
}
} } @Override
public void endElement(String namespaceURI, String localName,
String qName) throws SAXException { boolean debug = log.isDebugEnabled(); if (debug) {
if (saxLog.isDebugEnabled()) {
saxLog.debug("endElement(" + namespaceURI + "," + localName +
"," + qName + ")");
}
log.debug(" match='" + match + "'");
log.debug(" bodyText='" + bodyText + "'");
} // Parse system properties
bodyText = updateBodyText(bodyText); // the actual element name is either in localName or qName, depending
// on whether the parser is namespace aware
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
} // 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) {
log.error("Body event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Body event threw error", e);
throw e;
}
}
} else {
if (debug) {
log.debug(" No rules found matching '" + match + "'.");
}
if (rulesValidation) {
log.warn(" No rules found matching '" + match + "'.");
}
} // Recover the body text from the surrounding element
bodyText = bodyTexts.pop();
if (debug) {
log.debug(" Popping body text '" + bodyText.toString() + "'");
} // 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) {
log.error("End event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("End event threw error", e);
throw e;
}
}
} // Recover the previous match expression
int slash = match.lastIndexOf('/');
if (slash >= 0) {
match = match.substring(0, slash);
} else {
match = "";
} }
代码清单3在创建完Digester后,会多次调用addObjectCreate、addSetProperties和addSetNext方法陆续添加很多Rule,这些方法的实现如代码清单5。
代码清单5
public void addObjectCreate(String pattern, String className,
String attributeName) { addRule(pattern,
new ObjectCreateRule(className, attributeName)); } public void addSetProperties(String pattern) { addRule(pattern,
new SetPropertiesRule()); } public void addSetNext(String pattern, String methodName,
String paramType) { addRule(pattern,
new SetNextRule(methodName, paramType)); }
从代码清单5,我们看到这三个方法分别创建ObjectCreateRule、SetPropertiesRule及SetNextRule。为了简化理解我们以Server相关的Rule为例,如代码清单6所示。
代码清单6
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
根据代码清单5的实现,我们知道最终会创建ObjectCreateRule、SetPropertiesRule及SetNextRule,并且调用addRule方法将这些规则添加到Digester中。addRule方法首先调用getRules方法获取RulesBase,然后调用RulesBase的add方法。addRule方法的实现见代码清单7。
代码清单7
public void addRule(String pattern, Rule rule) { rule.setDigester(this);
getRules().add(pattern, rule); } public Rules getRules() { if (this.rules == null) {
this.rules = new RulesBase();
this.rules.setDigester(this);
}
return (this.rules); }
RulesBase的add方法的实现见代码清单8。
代码清单8
public void add(String pattern, Rule rule) {
// to help users who accidently add '/' to the end of their patterns
int patternLength = pattern.length();
if (patternLength>1 && pattern.endsWith("/")) {
pattern = pattern.substring(0, patternLength-1);
} List<Rule> list = cache.get(pattern);
if (list == null) {
list = new ArrayList<Rule>();
cache.put(pattern, list);
}
list.add(rule);
rules.add(rule);
if (this.digester != null) {
rule.setDigester(this.digester);
}
if (this.namespaceURI != null) {
rule.setNamespaceURI(this.namespaceURI);
} }
其中,cache的数据结构为HashMap<String,List<Rule>>,每个键值维护一个List,由此可知,对Server标签来说,对应的Rule列表为ObjectCreateRule、SetPropertiesRule及SetNextRule。
使用SAX解析server.xml
Digester解析XML的入口是其parse方法(见代码清单9),其处理步骤如下:
- 创建XMLReader ;
- 使用XMLReader解析XML。
代码清单9
public Object parse(InputSource input) throws IOException, SAXException { configure();
getXMLReader().parse(input);
return (root); }
parse方法调用了getXMLReader,getXMLReader方法的实现见代码清单10。
代码清单10
public XMLReader getXMLReader() throws SAXException {
if (reader == null){
reader = getParser().getXMLReader();
} reader.setDTDHandler(this);
reader.setContentHandler(this); if (entityResolver == null){
reader.setEntityResolver(this);
} else {
reader.setEntityResolver(entityResolver);
} reader.setErrorHandler(this);
return reader;
}
从代码清单10看到,getXMLReader方法首先调用getParser创建SAXParser,然后调用SAXParser 的getXMLReader方法创建XMLReader。
getParser方法的实现见代码清单11。
代码清单11
public SAXParser getParser() { // Return the parser we already created (if any)
if (parser != null) {
return (parser);
} // Create a new parser
try {
parser = getFactory().newSAXParser();
} catch (Exception e) {
log.error("Digester.getParser: ", e);
return (null);
} return (parser); }
从代码清单11看到,getParser方法首先调用getFactory方法创建SAXParserFactory,然后调用SAXParserFactory的newSAXParser方法创建SAXParser。getFactory方法(见代码清单12)使用SAX的API生成SAXParserFactory实例。
代码清单12
public SAXParserFactory getFactory()
throws SAXNotRecognizedException, SAXNotSupportedException,
ParserConfigurationException { if (factory == null) {
factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
factory.setValidating(validating);
if (validating) {
// Enable DTD validation
factory.setFeature(
"http://xml.org/sax/features/validation",
true);
// Enable schema validation
factory.setFeature(
"http://apache.org/xml/features/validation/schema",
true);
}
}
return (factory); }
XMLReader解析XML时,会生成事件,回调Digester的startDocument方法,解析的第一个元素是Server,此时回调Digester的startElement方法,入参Attributes list即为Server元素的属性,如port、shutdown等,入参qName即为Server。startElement方法的实现见代码清单13。
代码清单13
@Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes list)
throws SAXException {
boolean debug = log.isDebugEnabled(); if (saxLog.isDebugEnabled()) {
saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
qName + ")");
} // Parse system properties
list = updateAttributes(list); // Save the body text accumulated for our surrounding element
bodyTexts.push(bodyText);
if (debug) {
log.debug(" Pushing body text '" + bodyText.toString() + "'");
}
bodyText = new StringBuilder(); // the actual element name is either in localName or qName, depending
// on whether the parser is namespace aware
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
} // Compute the current matching rule
StringBuilder sb = new StringBuilder(match);
if (match.length() > 0) {
sb.append('/');
}
sb.append(name);
match = sb.toString();
if (debug) {
log.debug(" New match='" + match + "'");
} // 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) {
log.error("Begin event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Begin event threw error", e);
throw e;
}
}
} else {
if (debug) {
log.debug(" No rules found matching '" + match + "'.");
}
} }
startElement方法的处理步骤如下:
- match刚开始为空字符串,拼接Server后变为Server。
- 调用RulesBase的match方法,返回cache中按照键值Server匹配的ObjectCreateRule、SetPropertiesRule及SetNextRule。
- 循环列表依次遍历ObjectCreateRule、SetPropertiesRule及SetNextRule,并调用它们的begin方法。
ObjectCreateRule的begin方法(见代码清单14)将生成Server的实例(默认为"org.apache.catalina.core.StandardServer",用户可以通过给Server标签指定className使用其它Server实现),最后将Server的实例压入Digester的栈中。
代码清单14
@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);
} // Instantiate the new object and push it on the context stack jiaan.gja
Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
Object instance = clazz.newInstance();
digester.push(instance); }
SetPropertiesRule的begin方法(见代码清单15)首先将刚才压入栈中的Server实例出栈,然后给Server实例设置各个属性值,如port、shutdown等。
代码清单15
@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");
}
} // set up variables for custom names mappings
int attNamesLength = 0;
if (attributeNames != null) {
attNamesLength = attributeNames.length;
}
int propNamesLength = 0;
if (propertyNames != null) {
propNamesLength = propertyNames.length;
} 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); // we'll now check for custom mappings
for (int n = 0; n<attNamesLength; n++) {
if (name.equals(attributeNames[n])) {
if (n < propNamesLength) {
// set this to value from list
name = propertyNames[n]; } else {
// set name to null
// we'll check for this later
name = null;
}
break;
}
} 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.");
}
} }
SetNextRule的begin不做什么动作。当遇到Server的结束标签时,还会依次调用ObjectCreateRule、SetPropertiesRule及SetNextRule的end方法,不再赘述。所有元素的解析都与Server标签同理,最终将server.xml文件中设置的元素及其属性值,构造出tomcat中的容器,如:Server、Service、Connector等。
如需转载,请标明本文作者及出处——作者:jiaan.gja,本文原创首发:博客园,原文链接:http://www.cnblogs.com/jiaan-geng/p/4866009.html
Tomcat源码分析——SERVER.XML文件的加载与解析的更多相关文章
- Tomcat源码分析——server.xml文件的加载
前言 作为Java程序员,对于tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载的进行分析. 源码分析 Bootstrap的 ...
- angular源码分析:angular的整个加载流程
在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...
- 6.Sentinel源码分析—Sentinel是如何动态加载配置限流的?
Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Senti ...
- laravel框架源码分析(一)自动加载
一.前言 使用php已有好几年,laravel的使用也是有好长时间,但是一直对于框架源码的理解不深,原因很多,归根到底还是php基础不扎实,所以源码看起来也比较吃力.最近有时间,所以开启第5.6遍的框 ...
- Spring源码分析(二十一)加载BeanFactory
摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.定制化BeanFactory 二.加载BeanDefinit ...
- springmvc源码分析----入门看springmvc的加载过程
接上一篇我们写的入门---http://www.cnblogs.com/duanxiaojun/p/6591448.html 今天从这个门里进去我们看springmvc是如何在容器启动的时候将各个模块 ...
- springmvc源码分析——入门看springmvc的加载过程
本文将分析springmvc是如何在容器启动的时候将各个模块加载完成容器的创建的. 我知道在web.xml文件中我们是这样配置springmvc的: 可以看到,springmvc的核心控制器就是Dis ...
- 【Spring源码分析系列】bean的加载
前言 以 BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beans.xml"));为例查看bean的加载过 ...
- tomcat 源码分析
Tomcat源码分析——Session管理分析(下) Tomcat源码分析——Session管理分析(上) Tomcat源码分析——请求原理分析(下) Tomcat源码分析——请 ...
随机推荐
- 异常处理:Sys.WebForms.PageRequestManagerParserErrorException:The message……
如果你为了使页面可以达到局部刷新的效果,并且用了UpdatePanel控件,这是如果你在后台页面用到Response对象时肯呢过会抛出一下异常: 解决方法:$(document).ready(func ...
- Linux下文件的压缩和解压
tar命令 解包:tar zxvf FileName.tar 打包:tar czvf FileName.tar DirName gz命令 解压1:gunzip FileName.gz 解压2:gzip ...
- i标签和em标签的区别
很多人以为i标签和em标签都只是HTML用于斜体的标签,并不知道他们有什么区别. 其实对于熟悉SEO的人来说,他们的区别挺大的(普通用户略过). i标签的作用:仅仅是为了让字体显示斜体,对于SEO没什 ...
- hadoop mapred-queue-acls 配置(转)
hadoop作业提交时可以指定相应的队列,例如:-Dmapred.job.queue.name=queue2通过对mapred-queue-acls.xml和mapred-site.xml配置可以对不 ...
- 深入分析Volatile的实现原理(转)
引言 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”.可见性的意思是当 ...
- iphone dev 入门实例2:Pass Data Between View Controllers using segue
Assigning View Controller Class In the first tutorial, we simply create a view controller that serve ...
- javaweb工程,Servlet里面获取当前WEB跟路径的文件绝对路径地址
String base_Url = request.getSession().getServletContext().getRealPath("/"); String window ...
- Redefining already defined constructor
报错:Strict standards: Redefining already defined constructor for class SplitWord in D:\wamp\www\wsc\i ...
- Python 汉字转拼音库 pypinyin
一.初衷: 一些开源软件的配置文件中识别区分的部分用英文,那么我们在批量生成配置文件的时候,可以从CMDB导入汉字(idc_name), 然后将它转换成拼音,再或者拼接上IP地址,以便更准确的识别.例 ...
- poj 1251 统计难题(字典树)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1251 AC代码: #include<iostream> #include<algor ...