tomcat原始碼解析(三)——Digester類原始碼解析及Rule分析
在這篇文章中主要針對tomcat原始碼中Rule部分的解析;這部分功能主要涉及server.xml檔案載入和tomcat容器中各元件初始化的過程。在我之前的文章中《tomcat原始碼解析(一)——Bootstrap和Catalina啟動部分》和《tomcat原始碼解析(二)——xml解析過程分析》分別對啟動過程和xml解析進行了分析。
之前的文章中遺留了幾個問題,將在這篇文章中進行解答;問題如下:
1、第一篇文章啟動過程中Catalina類createStartDigester中設定過程及原因。
2、第二篇文章中關於rule,呼叫的
rule.begin(namespaceURI, name, list);
rule.body(namespaceURI, name, bodyText);
rule.end(namespaceURI, name);
rule.finish();
的具體過程和分析方法和Digester類的stack作用。
如果對tomcat使用SAX解析xml已經瞭解的可以較好理解本文的思路。
對於Rule的分析首先需要檢視org.apache.tomcat.util.digester.Rule的原始碼,內容比較簡單,就不放原始碼內容了;從中可以看到一些比較重要的資訊:
1、Rule.java是一個是抽象類,對於單繼承的java來說,其子類的功能職責一目瞭然。
2、Rule.java類所有的方法都不是抽象方法,而且具體實現都是空的(代表其子類可以自由的選擇覆蓋父類的方法,也可以不覆蓋)
3、Rule.java類的子類比較多,這裡只介紹部分,其他的類似。
現在可以正式分析Catalina的啟動部分原始碼createStartDigester():
關於createStartDigester的原始碼如下:
以上程式碼的功能分析如下:/** * 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.setValidating(false);設定SAX解析xml的DTD校驗和schema校驗,呼叫原始碼可參考Digester類的getFactory()和Digester類的getParser();
2、digester.setRulesValidation(true);看原始碼中只和日誌列印的功能相關,和邏輯功能無關,在後文的原始碼中可以看到。
3、digester.setFakeAttributes(fakeAttributes);設定需要過濾的屬性,如果設定後在進行類初始化時,配置的屬性不會賦值給例項化後的物件,具體功能和實現在下文中會有詳細說明。
4、digester.setClassLoader(StandardServer.class.getClassLoader());由於啟動時首先啟動的是StandardServer,設定StandardServer所在的的classloader。
5、之後的操作可以發現實際上呼叫的方法分為如下幾個:
digester.addObjectCreate(String pattern, String className, String attributeName)
digester.addSetProperties(String pattern)
digester.addSetNext(String pattern, String methodName, String paramType)
digester.addRule(String pattern, Rule rule)
digester.addRuleSet(RuleSet ruleSet)
到這裡createStartDigester()的主體功能就算分析完了;
關於上面第5點這些方法,我們繼續檢視Digester類的實現原始碼,可以總結如下規律:
1、digester.addObjectCreate(String pattern, String className, String attributeName)實際上呼叫的是addRule(String pattern, Rule rule)方法;且第二個引數新建立了一個物件ObjectCreateRule(className, attributeName);
2、同理可以發現digester.addSetProperties(String pattern)呼叫的也是addRule(String pattern, Rule rule)方法,建立了SetPropertiesRule()物件
3、digester.addSetNext(String pattern, String methodName, String paramType)呼叫的也是addRule(String pattern, Rule rule)方法,建立了SetNextRule(methodName, paramType)物件
4、只有digester.addRuleSet(RuleSet ruleSet)不同,這個需要專門分析。
所以可以理解為createStartDigester()中第5部分只執行了
digester.addRule(String pattern, Rule rule)
digester.addRuleSet(RuleSet ruleSet)
兩種操作。所關注的Rule也集中在:ObjectCreateRule、SetPropertiesRule、SetNextRule這三個類上。
細跟digester.addRule(String pattern, Rule rule)原始碼,如下:
/**
* <p>Register a new Rule matching the specified pattern.
* This method sets the <code>Digester</code> property on the rule.</p>
*
* @param pattern Element matching pattern
* @param rule Rule to be registered
*/
public void addRule(String pattern, Rule rule) {
rule.setDigester(this);
getRules().add(pattern, rule);
}
相應的 getRules()原始碼:
/**
* Return the <code>Rules</code> implementation object containing our
* rules collection and associated matching policy. If none has been
* established, a default implementation will be created and returned.
*/
public Rules getRules() {
if (this.rules == null) {
this.rules = new RulesBase();
this.rules.setDigester(this);
}
return (this.rules);
}
可以發現pattern和rule的對映關係是通過RulesBase()進行管理的;
繼續檢視RulesBase的add(String pattern, Rule rule)原始碼:
/**
* Register a new Rule instance matching the specified pattern.
*
* @param pattern Nesting pattern to be matched for this Rule
* @param rule Rule instance to be registered
*/
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 list = (List) cache.get(pattern);
if (list == null) {
list = new ArrayList();
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);
}
}
從中可以看到pattern和rule的對映關係是一對多的,且通過map方式進行管理的(cache實際上是map型別的),例如pattern為"Server"時,會對應三個Rule;而且RulesBase中會單獨維護rules(額外的List物件)去記錄所有新增的rule。
到這裡,可以發現pattern的存在至關重要,全部都是Server開頭,例如“Server/GlobalNamingResources”,“Server/Service/Connector”等,這些都會對應到server.xml的各個元素節點的層級關係。
現在可以結合上一篇文章的內容,分析出tomcat利用SAX解析server.xml時,在觸發例如startElement的回撥函式時,通過傳入localName和qname引數生成match(例如:引數“Server”)取出對應的所有rules,並分別執行begin方法。
具體以下以解析pattern為"Server"的執行過程為例;
首先在Catalina類的createStartDigester()中執行了
// 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");
實際是在pattern為"Server"中設定了3個Rule,他們分別是ObjectCreateRule(className, attributeName),SetPropertiesRule(),SetNextRule(methodName, paramType);
當Catalina的load()函式執行到digester.parse(inputSource);時,通過SAX解析server.xml後,遇到開始節點<Server>,並在開始解析時觸發Digester類的startElement方法。
在startElement方法中生成match=“Server”;即xml中的元素節點就是pattern,它們之間的對應關係生成規則可參考Digester類的startElement原始碼:
// Compute the current matching rule
StringBuffer sb = new StringBuffer(match);
if (match.length() > 0) {
sb.append('/');
}
sb.append(name);
match = sb.toString();
match是全域性變數,每次修改都是基於上一次的match;現在得到match及傳入的引數namespaceURI就可以從RulesBase類中找到對應的Rules
// Fire "begin" events for all relevant rules
List rules = getRules().match(namespaceURI, match);
裡面具體獲取rules的原始碼讀者可以自己跟進閱讀。
獲取到rules先放到matches(棧結構)中,再分別執行rule.begin(namespaceURI, name, list);方法(在下文中會繼續說明)。
繼續執行到xml中</Server>時會觸發endElement方法,先通過matches.pops得到對應的rules(由於是棧結構,到這裡得到的自然是match=“Server”時設定的rules)。
然後分別將這些rules執行rule.body(namespaceURI, name, bodyText);和 rule.end(namespaceURI, name);方法;
match在endElement方法中,會返回到上一級pattern。
// Recover the previous match expression
int slash = match.lastIndexOf('/');
if (slash >= 0) {
match = match.substring(0, slash);
} else {
match = "";
}
最後執行endDocument時,獲取到加入過的所有pattern對應的rules,並執行rule.finish();
// Fire "finish" events for all defined rules
Iterator rules = getRules().rules().iterator();
while (rules.hasNext()) {
Rule 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;
}
}
到這裡我們梳理了一個比較完整的xml解析流程。現在關注點程式碼集中在
rule.begin(namespaceURI, name, list);
rule.body(namespaceURI, name, bodyText);
rule.end(namespaceURI, name);
rule.finish();
這四個部分;而關注的主要rule也集中在如下幾個類上:
ObjectCreateRule
SetPropertiesRule
SetNextRule
除此之外還有很多Rule,遇到時我再具體分析。以上三個類比較有特點;
ObjectCreateRule的原始碼分析:
當代碼執行rule.begin(namespaceURI, name, list);時,由於ObjectCreateRule沒有重寫begin(String namespace, String name, Attributes attributes)方法,所以呼叫Rule的begin(String namespace, String name, Attributes attributes)方法,如下所示:
/**
* This method is called when the beginning of a matching XML element
* is encountered. The default implementation delegates to the deprecated
* method {@link #begin(Attributes) begin} without the
* <code>namespace</code> and <code>name</code> parameters, to retain
* backwards compatibility.
*
* @param namespace the namespace URI of the matching element, or an
* empty string if the parser is not namespace aware or the element has
* no namespace
* @param name the local name if the parser is namespace aware, or just
* the element name otherwise
* @param attributes The attribute list of this element
* @since Digester 1.4
*/
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
begin(attributes);
}
其中ObjectCreateRule重寫了begin(Attributes attributes);原始碼如下:
/**
* Process the beginning of this element.
*
* @param attributes The attribute list of this element
*/
public void begin(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);
}
裡面的功能是將addObjectCreate在建構函式中傳入的className進行例項化,例如將pattern為"Server"的org.apache.catalina.core.StandardServer例項化,並放入Digester類的棧stack物件中(Digester類的root物件一般是Catalina類的例項化物件自身,這個stack後面會專門講解);
除此之外ObjectCreateRule重寫了end()方法,但沒有具體功能性程式碼,只是記錄日誌和執行stack.pop()。ObjectCreateRule並沒有重寫其它的Rule.java類的方法,對它的分析到這就算結束了。
SetPropertiesRule的原始碼分析:
在Digester類中呼叫原始碼如下
/**
* Add a "set properties" rule for the specified parameters.
*
* @param pattern Element matching pattern
* @see SetPropertiesRule
*/
public void addSetProperties(String pattern) {
addRule(pattern,
new SetPropertiesRule());
}
對應的SetPropertiesRule建構函式
/**
* Base constructor.
*/
public SetPropertiesRule() {
// nothing to set up
}
檢視SetPropertiesRule原始碼部分可知,它重寫的方法是begin(Attributes attributes);新加了addAlias(String attributeName, String propertyName)方法;
addAlias(String attributeName, String propertyName)方法是為屬性新增別名,具體可以檢視原始碼,其不影響主要流程的功能分析,不做講解;
SetPropertiesRule的begin(Attributes attributes)的原始碼如下:
/**
* Process the beginning of this element.
*
* @param attributes The attribute list of this element
*/
public void begin(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.");
}
}
}
這部分原始碼的功能如下:
1、Object top = digester.peek();與之對應的是在ObjectCreateRule類的 begin(Attributes attributes)方法中會執行digester.push(instance);由於這裡面實際儲存的物件的結構是棧,且每個pattern會先執行ObjectCreateRule,再執行SetPropertiesRule;則這個函式得到的物件就是ObjectCreateRule類begin方法中例項化的物件。
2、得到對應的屬性名稱和屬性值;(中間部分涉及到別名的處理,目前解析的過程中暫時用不到)
3、執行以下原始碼(這部分是解析重點):
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.");
}
先檢視digester.isFakeAttribute(top, name)和digester.getRulesValidation()的原始碼:
/**
* Determine if an attribute is a fake attribute.
*/
public boolean isFakeAttribute(Object object, String name) {
if (fakeAttributes == null) {
return false;
}
List<String> result = fakeAttributes.get(object.getClass());
if (result == null) {
result = fakeAttributes.get(Object.class);
}
if (result == null) {
return false;
} else {
return result.contains(name);
}
}
/**
* Return the rules validation flag.
*/
public boolean getRulesValidation() {
return (this.rulesValidation);
}
其中的fakeAttributes和rulesValidation是我們在Catalina類的createStartDigester()中設定的:
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);
可以看出isFakeAttribute(Object object, String name)實際上是對屬性進行檢查;在之前Catalina類的createStartDigester()中設定了Object.class和attrs的對應關係,如果被檢查的屬性在attrs中設定過則返回true,不再執行SetPropertiesRule類後面的兩個函式。
由此可以總結設定fakeAttribute其實就是設定需要過濾的屬性,這些屬性會與相應的類對應;如果被設定過濾,則在對類進行例項化是不會執行後面的屬性賦值操作。
這裡最重點的功能在IntrospectionUtils.setProperty(top, name, value)函式;它的作用是將解析xml所獲取到的屬性值,設定到之前ObjectCreateRule例項化的物件中,如果設定失敗再進行digester.getRulesValidation()判斷,判斷是否列印屬性值設定失敗的日誌。
IntrospectionUtils.setProperty(top, name, value)是功能的重點,檢視其中原始碼:
/**
* Find a method with the right name If found, call the method ( if param is
* int or boolean we'll convert value to the right type before) - that means
* you can have setDebug(1).
*/
public static boolean setProperty(Object o, String name, String value) {
return setProperty(o,name,value,true);
}
public static boolean setProperty(Object o, String name, String value,boolean invokeSetProperty) {
if (dbg > 1)
d("setProperty(" + o.getClass() + " " + name + "=" + value + ")");
String setter = "set" + capitalize(name);
try {
Method methods[] = findMethods(o.getClass());
Method setPropertyMethodVoid = null;
Method setPropertyMethodBool = null;
// First, the ideal case - a setFoo( String ) method
for (int i = 0; i < methods.length; i++) {
Class paramT[] = methods[i].getParameterTypes();
if (setter.equals(methods[i].getName()) && paramT.length == 1
&& "java.lang.String".equals(paramT[0].getName())) {
methods[i].invoke(o, new Object[] { value });
return true;
}
}
// Try a setFoo ( int ) or ( boolean )
for (int i = 0; i < methods.length; i++) {
boolean ok = true;
if (setter.equals(methods[i].getName())
&& methods[i].getParameterTypes().length == 1) {
// match - find the type and invoke it
Class paramType = methods[i].getParameterTypes()[0];
Object params[] = new Object[1];
// Try a setFoo ( int )
if ("java.lang.Integer".equals(paramType.getName())
|| "int".equals(paramType.getName())) {
try {
params[0] = new Integer(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( long )
}else if ("java.lang.Long".equals(paramType.getName())
|| "long".equals(paramType.getName())) {
try {
params[0] = new Long(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( boolean )
} else if ("java.lang.Boolean".equals(paramType.getName())
|| "boolean".equals(paramType.getName())) {
params[0] = new Boolean(value);
// Try a setFoo ( InetAddress )
} else if ("java.net.InetAddress".equals(paramType
.getName())) {
try {
params[0] = InetAddress.getByName(value);
} catch (UnknownHostException exc) {
d("Unable to resolve host name:" + value);
ok = false;
}
// Unknown type
} else {
d("Unknown type " + paramType.getName());
}
if (ok) {
methods[i].invoke(o, params);
return true;
}
}
// save "setProperty" for later
if ("setProperty".equals(methods[i].getName())) {
if (methods[i].getReturnType()==Boolean.TYPE){
setPropertyMethodBool = methods[i];
}else {
setPropertyMethodVoid = methods[i];
}
}
}
// Ok, no setXXX found, try a setProperty("name", "value")
if (invokeSetProperty && (setPropertyMethodBool != null || setPropertyMethodVoid != null)) {
Object params[] = new Object[2];
params[0] = name;
params[1] = value;
if (setPropertyMethodBool != null) {
try {
return (Boolean) setPropertyMethodBool.invoke(o, params);
}catch (IllegalArgumentException biae) {
//the boolean method had the wrong
//parameter types. lets try the other
if (setPropertyMethodVoid!=null) {
setPropertyMethodVoid.invoke(o, params);
return true;
}else {
throw biae;
}
}
} else {
setPropertyMethodVoid.invoke(o, params);
return true;
}
}
} catch (IllegalArgumentException ex2) {
log.warn("IAE " + o + " " + name + " " + value, ex2);
} catch (SecurityException ex1) {
if (dbg > 0)
d("SecurityException for " + o.getClass() + " " + name + "="
+ value + ")");
if (dbg > 1)
ex1.printStackTrace();
} catch (IllegalAccessException iae) {
if (dbg > 0)
d("IllegalAccessException for " + o.getClass() + " " + name
+ "=" + value + ")");
if (dbg > 1)
iae.printStackTrace();
} catch (InvocationTargetException ie) {
if (dbg > 0)
d("InvocationTargetException for " + o.getClass() + " " + name
+ "=" + value + ")");
if (dbg > 1)
ie.printStackTrace();
}
return false;
}
這部分程式碼比較長,但執行的功能還是比較單一的,首先根據屬性值獲取到對應的set方法名稱,然後根據反射找到所對應引數型別,執行set方法,或者通過setProperty方法設定屬性值。總之,屬性值在這裡會被設定到之前例項化的物件中;例如server.xml解析時會將port="8005"的屬性值設定到org.apache.catalina.core.StandardServer類的物件中。到這裡SetPropertiesRule的分析也結束了。
接下來是SetNextRule的原始碼:
在在Digester類中執行的SetNextRule的構造方法如下:
/**
* Construct a "set next" rule with the specified method name.
*
* @param methodName Method name of the parent method to call
* @param paramType Java class of the parent method's argument
* (if you wish to use a primitive type, specify the corresonding
* Java wrapper class instead, such as <code>java.lang.Boolean</code>
* for a <code>boolean</code> parameter)
*/
public SetNextRule(String methodName,
String paramType) {
this.methodName = methodName;
this.paramType = paramType;
}
在這裡它覆蓋的方法只有一個end()方法,內容如下:
/**
* Process the end of this element.
*/
public void end() 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());
}
通過我之前的文章介紹過end()方法是在讀取xml結束標籤時會觸發執行的。現在裡面的功能分析如下:
1、Object child = digester.peek(0);和Object parent = digester.peek(1);這兩個分別是獲取執行當前標籤開始時所得到的例項化物件和父一級標籤所對應的例項化物件。
2、Call the specified method。這裡的methodName和paramType是呼叫SetNextRule的建構函式時的引數,具體的功能是通過反射將child 物件設定到parent 物件中;methodName和paramType代表在父類需要執行方法的名稱和型別。
IntrospectionUtils.callMethod1內部的實現過程完全是基於反射的,且功能職責比較單一,比較容易理解,就不繼續跟進了。
至於將child 物件設定到parent 物件的原因,是因為這與tomcat結構設計有關,每個子模組通過將自己註冊到父模組進行管理,模組之間耦合度會很小,它們通過實現統一的介面保證行為的一致性。這部分會在以後的文章中進行繼續分析。
而server.xml結構是tomca設計架構本身對映到配置檔案中的結果;Server管理Service,Service管理Connector和Engine,Engine管理Host,等等。
所以這裡會將child 物件設定到parent 物件中。
到這裡分析完了三個比較重要的Rule,其它的Rule可以根據類內部實現的方法名稱和解析xml所觸發函式來確定其功能。
接下來就是分析digester.addRuleSet(RuleSet ruleSet)這部分的方法了。
RuleSet介面定義了getNamespaceURI(),和addRuleInstances(Digester digester)兩個方法;
繼續檢視Digester類的addRuleSet(RuleSet ruleSet)原始碼:
/**
* Register a set of Rule instances defined in a RuleSet.
*
* @param ruleSet The RuleSet instance to configure from
*/
public void addRuleSet(RuleSet ruleSet) {
String oldNamespaceURI = getRuleNamespaceURI();
String newNamespaceURI = ruleSet.getNamespaceURI();
if (log.isDebugEnabled()) {
if (newNamespaceURI == null) {
log.debug("addRuleSet() with no namespace URI");
} else {
log.debug("addRuleSet() with namespace URI " + newNamespaceURI);
}
}
setRuleNamespaceURI(newNamespaceURI);
ruleSet.addRuleInstances(this);
setRuleNamespaceURI(oldNamespaceURI);
}
在這段原始碼中可以看到在執行ruleSet.addRuleInstances(this)之前,先將ruleSet之中的namespaceURI設定到Digester中,在執行之後會將原有的namespaceURI設定回來。
要理解這麼做的原因先要看ruleSet.addRuleInstances(this)做了些什麼。
我這裡隨便拿了一個EngineRuleSet類進行舉例:
EngineRuleSet繼承了RuleSetBase類,而RuleSetBase類是實現了RuleSet介面的抽象方法。
getNamespaceURI()方法在RuleSetBase中實現了,EngineRuleSet中主要實現了addRuleInstances(Digester digester)方法;與之前的ruleSet.addRuleInstances(this)相對應。
檢視EngineRuleSet類的addRuleInstances(Digester digester)原始碼:
/**
* <p>Add the set of Rule instances defined in this RuleSet to the
* specified <code>Digester</code> instance, associating them with
* our namespace URI (if any). This method should only be called
* by a Digester instance.</p>
*
* @param digester Digester instance to which the new Rule instances
* should be added.
*/
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Engine",
"org.apache.catalina.core.StandardEngine",
"className");
digester.addSetProperties(prefix + "Engine");
digester.addRule(prefix + "Engine",
new LifecycleListenerRule
("org.apache.catalina.startup.EngineConfig",
"engineConfigClass"));
digester.addSetNext(prefix + "Engine",
"setContainer",
"org.apache.catalina.Container");
//Cluster configuration start
digester.addObjectCreate(prefix + "Engine/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Cluster");
digester.addSetNext(prefix + "Engine/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
//Cluster configuration end
digester.addObjectCreate(prefix + "Engine/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Listener");
digester.addSetNext(prefix + "Engine/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addRuleSet(new RealmRuleSet(prefix + "Engine/"));
digester.addObjectCreate(prefix + "Engine/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Valve");
digester.addSetNext(prefix + "Engine/Valve",
"addValve",
"org.apache.catalina.Valve");
}
走到這裡,就會發現裡面所有程式碼的邏輯功能都已經分析過了,只是設定的引數變了。增加了一類的以Engine為關鍵字的規則,當server.xml的層級規則符合這些設定規則時就會例項化相應的類,並設定相應的屬性值等。
現在就可以具體分析addRuleSet(RuleSet ruleSet)中namespaceURI設定的原因了。
setRuleNamespaceURI(newNamespaceURI);
ruleSet.addRuleInstances(this);
setRuleNamespaceURI(oldNamespaceURI);
由這段程式碼可知,ruleSet.addRuleInstances(this);執行過程中一定會有namespaceURI的賦值操作;
namespaceURI的賦值位置則需要跟進程式碼,例如EngineRuleSet裡,根據前面的分析最後一定會走到Digester類的addRule(String pattern, Rule rule)中:
public void addRule(String pattern, Rule rule) {
rule.setDigester(this);
getRules().add(pattern, rule);
}
這裡執行的 getRules().add(pattern, rule)操作實際上是執行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 list = (List) cache.get(pattern);
if (list == null) {
list = new ArrayList();
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);
}
}
最後會為每個增加的rule設定對應namespaceURI;而且每個rule對應的namespaceURI可以不相同。走到這裡,關於tomcat的Rule規則就介紹完成了,至於其他未介紹的Rule或者RuleSet,其實原理完全都是一樣的。
接下來會解釋本文中遺留下來的問題:
Digester類的stack物件有什麼用,它裡面有什麼?
關於stack,是一個棧結構,裡面存放的是在解析server.xml過程中例項化的物件;
它的呼叫則需要從Catalina類的load()方法開始,以下是擷取的程式碼片段:
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
inputStream.close();
由這裡可知:
1、digester.push(this);會將當前物件放入stack中;
2、digester.push(this);在digester.parse(inputSource);之前執行,就代表後面解析xml所例項化的物件及操作,都在stack有值的基礎上進行操作的。且這個基礎的物件就是Catalina類的例項化物件。
繼續檢視push原始碼:
/**
* Push a new object onto the top of the object stack.
*
* @param object The new object
*/
public void push(Object object) {
if (stack.size() == 0) {
root = object;
}
stack.push(object);
}
這裡的root物件實際就是Catalina類的例項化物件。
現在知道stack棧第一個物件是Catalina類的例項化物件,那麼再結合之前分析過的SetNextRule類的end()方法;
當解析xml的第一個節點server的結束標籤時,棧中只有兩個物件:Catalina和StandardServer兩個類的例項化物件;
當執行end()方法的過程中,實際上是執行Catalina類的setServer方法將StandardServer設定進去;
到這裡就可以明白Catalina管理著StandardServer;
通過類似的方法也可以發現StandardServer管理著NamingResources、StandardService等;StandardService管理著Connector、StandardThreadExecutor、StandardEngine等等。
具體各服務元件之間的關係會在以後的文章中進行說明。
stack的作用基本就是這些。
最後在補一張Catalina的類圖,方便更好的理解。