Tomcat7.0原始碼分析——server.xml檔案的載入與解析
阿新 • • 發佈:2018-12-31
前言
作為Java程式設計師,對於Tomcat的server.xml想必都不陌生。本文基於Tomcat7.0的Java原始碼,對server.xml檔案是如何載入和解析進行分析。載入過程分析
Bootstrap的load方法用於載入Tomcat的server.xml,實際是通過反射呼叫Catalina的load方法,程式碼如下:/** * 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); }
Catalina的load方法實現如下:
/** * Start a new server instance. */ public void load() { 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 { 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"); }
這裡對上述程式碼進行分析:
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,具體實現本文不做分析。
規則
在正式介紹Digester的parse方法的解析過程前,我們先來掌握一些規則相關的內容。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時,此方法被呼叫,如進入<Server>標籤內部時。
- end(String namespace, String name):此方法在遇到一個匹配的XML元素的末尾時被呼叫。如:</Server>。
這裡以最常用的幾個規則表示Rule的類繼承體系,如下圖:
SAX(Simple API for XML)
相比於 DOM 而言 SAX 是一種速度更快,更有效,佔用記憶體更少的解析 XML 檔案的方法。它是逐行掃描,可以做到邊掃描邊解析,因此 SAX 可以在解析文件的任意時刻停止解析。SAX 是基於事件驅動的。SAX 不用解析完整個文件,在按內容順序解析文件過程中, SAX 會判斷當前讀到的字元是否符合 XML 檔案語法中的某部分。如果符合某部分,則會觸發事件。所謂觸發事件,就是呼叫一些回撥方法。在用 SAX 解析 xml 文件時候,在讀取到文件開始和結束標籤時候就會回撥一個事件,在讀取到其他節點與內容時候也會回撥一個事件。在 SAX 介面中,事件源是 org.xml.sax 包中的 XMLReader ,它通過 parser() 方法來解析 XML 文件,併產生事件。事件處理器是 org.xml.sax 包中 ContentHander 、 DTDHander 、 ErrorHandler ,以及 EntityResolver 這 4 個介面。事件處理器 | 事件處理器處理的事件 | 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 data="30"/> 主要目的是獲取 high 標籤中的值。
1 、建立一個 SAXParserFactory 物件;
2 、呼叫 SAXParserFactory 中的 newSAXParser 方法建立一個 SAXParser 物件;
3 、然後在呼叫 SAXParser 中的 getXMLReader 方法獲取一個 XMLReader 物件;
4 、例項化一個 DefaultHandler 物件;
5 、連線事件源物件 XMLReader 到事件處理類 DefaultHandler 中;
6 、呼叫 XMLReader 的 parse 方法從輸入源中獲取到的 xml 資料;
7 、通過 DefaultHandler 返回我們需要的資料集合。
解析過程分析
在介紹Catalina的load方法時,遇見了createStartDigester方法,它的實現如程式碼清單1:程式碼清單1
/**
* 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);
}
程式碼清單1首先建立Digester,Digester繼承了DefaultHandler,而DefaultHandler預設實現了ContentHander、DTDHander、ErrorHandler及EntityResolver 這4個介面,程式碼如下:
public class DefaultHandler
implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler
如果閱讀DefaultHandler的原始碼,發現它的所有實現都是空實現,看來要發揮解析作用,只能依靠Digester自己了,見程式碼清單2。
程式碼清單2
@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 = "";
}
}
程式碼清單1中建立完Digester後,會呼叫addObjectCreate、addSetProperties、addSetNext方法陸續新增很多Rule,這些方法的實現如程式碼清單3:
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));
}
從上述程式碼我們看到這三個方法分別建立ObjectCreateRule、SetPropertiesRule及SetNextRule。為了簡化理解我們以Server相關的Rule為例,如程式碼清單4:
程式碼清單4
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
根據程式碼清單3的實現,我們知道最終會建立ObjectCreateRule、SetPropertiesRule及SetNextRule,並且呼叫addRule方法。addRule方法首先呼叫getRules方法獲取RulesBase,然後呼叫RulesBase的add方法。addRule方法的實現如下:
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方法的實現如下:
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<Rule>,由此可知,對Server標籤來說,對應的Rule列表為ObjectCreateRule、SetPropertiesRule及SetNextRule。
Digester解析XML的入口是其parse方法,其處理步驟如下:
1.建立XMLReader ;
2.使用XMLReader解析XML。
parse方法的程式碼如下:
public Object parse(InputSource input) throws IOException, SAXException {
configure();
getXMLReader().parse(input);
return (root);
}
getXMLReader方法呼叫getParser建立SAXParser ,然後呼叫SAXParser 的getXMLReader方法建立XMLReader ,程式碼如下:
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;
}
getParser方法呼叫getFactory方法建立SAXParserFactory,然後呼叫SAXParserFactory的newSAXParser方法建立SAXParser ,程式碼如下:
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);
}
getFactory方法使用SAX的API生成SAXParserFactory例項,程式碼如下:
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方法的程式碼如下:
@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方法的處理步驟如下:
1.match剛開始為空字串,拼接Server後變為Server。
2.呼叫RulesBase的match方法,返回cache中按照鍵值Server匹配的ObjectCreateRule、SetPropertiesRule及SetNextRule。
3.迴圈列表依次遍歷ObjectCreateRule、SetPropertiesRule及SetNextRule,並呼叫它們的begin方法。
ObjectCreateRule的begin方法將生成Server的例項(預設為"org.apache.catalina.core.StandardServer",使用者可以通過給Server標籤指定className使用其它Server實現),最後將Server的例項壓入Digester的棧中,程式碼如下:
@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
Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
Object instance = clazz.newInstance();
digester.push(instance);
}
SetPropertiesRule的begin方法首先將剛才壓入棧中的Server例項出棧,然後給Server例項設定各個屬性值,如port、shutdown等,程式碼如下:
@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等。
後記:個人總結整理的《深入理解Spark:核心思想與原始碼分析》一書現在已經正式出版上市,目前京東、噹噹、天貓等網站均有銷售,歡迎感興趣的同學購買。