在Tomcat中使用了大量的xml文件去配置一些重要的信息,以达到程序与配置的解耦,比如server.xml、web.xml。解析XML文件的技术,Tomcat使用了Digester框架去解析xml。我们可能对Digester比较陌生,比较常用的解析xml的技术有JDK自带的SAX、还有比较出名的第三分Dom4j等等。Dom4j和SAX解析的区别
而Digester它是基于SAX技术,也可以理解为对SAX技术的包装。它的作用是将XML的节点信息转化为对象。下面我们以Tomcat解析server.xml文件为例,讲解这个技术的使用和原理
Tomcat是在Catalina的load方法里进行解析server.xml,具体关于Tomcat是什么时候执行Catalina的load方法,并且整个load方法的过程,可以看我之前讲Tomcat的初始化。现在我们主要看下Tomcat是如何利用Digester去解析server.xml,并且如何利用server.xml去创建相关的组件
首先我们看下Tomcat自带的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" />
<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>
<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>
Catalina
public void load() {
// 省略一些其他代码,只留下Digester相关的代码
/*
* 创建Digester对象,并且添加了一系列的Rule
*/
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
/*
* 获取server.xml的文件流
*/
file = configFile();
inputStream = new FileInputStream(file);
/*
* 把文件流转换为InputSource
*/
inputSource = new InputSource(file.toURI().toURL().toString());
inputSource.setByteStream(inputStream);
/*
* 把当前对象,也就是Catalina,压入栈中,这个栈很重要,后面会说
*/
digester.push(this);
/*
* 开启解析server.xml,把xml的节点信息转换为对象,并且进行一些对象的属性配置等等
*/
digester.parse(inputSource);
// 省略一些其他代码,只留下Digester相关的代码
}
首先我们来看下createStartDigester()方法,它主要创建Digester对象,并且为其添加了一系列的Rule
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// 创建Digester对象
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
/*
* 设置需要忽略的一些属性,在这里只添加了className属性
*/
HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<>();
ArrayList<String> attrs = new ArrayList<>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
/*
* 设置UseContextClassLoader=true,则从Thread.currentThread().getContextClassLoader();获取ClassLoader
*/
digester.setUseContextClassLoader(true);
/**
* 下面设置了一些解析节点的Rule
*/
/**
*
*/
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");
/**
* 解析Server标签下的Listener的Rule
*
*
*
*/
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");
/**
* 解析Service标签下的Listener的Rule
*
*
*
*
*
*/
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");
/**
* 解析Service标签下的Executor的Rule
*
*
*
*
*
*/
//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");
/**
* 解析Service标签下的Connector的Rule
*
*
*
*
*
*/
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");
/**
* 解析Connector标签下的Listener的Rule
*
*
*
*
*
*
*
*/
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");
/**
* 这些是Engine、Host、Valve标签解析
*/
// 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/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
long t2=System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Digester for server.xml created " + ( t2-t1 ));
}
return (digester);
}
由上面代码可知,我们可以总结用的比较多的几个方法有
下面我们看下上面常见的几个方法
public void addObjectCreate(String pattern, String className, String attributeName) {
// 实际上调用了addRule方法,并且创建了ObjectCreateRule对象
addRule(pattern, new ObjectCreateRule(className, attributeName));
}
public void addRule(String pattern, Rule rule) {
// 把当前Digester实例设置到传入的Rule中
rule.setDigester(this);
// 首先调用getRules(),然后调用RulesBase的add方法,保存rule
getRules().add(pattern, rule);
}
public Rules getRules() {
// 第一次进来这个rules 会为空,创建RulesBase对象
if (this.rules == null) {
this.rules = new RulesBase();
// 为RulesBase设置Digester实例
this.rules.setDigester(this);
}
return (this.rules);
}
public void add(String pattern, Rule rule) {
/*
* pattern可以理解为节点路径,可以回去看上面的createStartDigester的方法
* 比如说:
* Server 是父节点,那么pattern = “Server”
* GlobalNamingResources 是 Server 子节点,那么pattern = “Server/GlobalNamingResources”
* 以此类推
*/
int patternLength = pattern.length();
/*
* 如果pattern是以/结尾,那么需要把结尾的/去掉
*/
if (patternLength>1 && pattern.endsWith("/")) {
pattern = pattern.substring(0, patternLength-1);
}
/*
* protected HashMap> cache = new HashMap<>();
* 从cache中取出当前pattern的Rule集合,
*
*/
List<Rule> list = cache.get(pattern);
/*
* 如果list为空,那么需要初始化,并且把list放入cache中
*/
if (list == null) {
list = new ArrayList<>();
cache.put(pattern, list);
}
// 把rule添加到list
list.add(rule);
/*
* 同时把rule添加到rules中
* protected ArrayList rules = new ArrayList<>();
*/
rules.add(rule);
if (this.digester != null) {
rule.setDigester(this.digester);
}
if (this.namespaceURI != null) {
rule.setNamespaceURI(this.namespaceURI);
}
}
根据上述代码,我们可以得出结论就是
public void addSetProperties(String pattern) {
/*
* addRule,跟上面addObjectCreate的addRule是一样的,就先不分析了
*/
addRule(pattern, new SetPropertiesRule());
}
根据上述代码,我们可以得出结论就是
public void addSetNext(String pattern, String methodName, String paramType) {
addRule(pattern, new SetNextRule(methodName, paramType));
}
根据上述代码,我们可以得出结论就是
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方法
* 并且传入了Digester对象
*/
ruleSet.addRuleInstances(this);
setRuleNamespaceURI(oldNamespaceURI);
}
以HostRuleSet的addRuleInstances方法为例
HostRuleSet:
@Override
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container");
digester.addCallMethod(prefix + "Host/Alias",
"addAlias", 0);
//Cluster configuration start
digester.addObjectCreate(prefix + "Host/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Cluster");
digester.addSetNext(prefix + "Host/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
//Cluster configuration end
digester.addObjectCreate(prefix + "Host/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Listener");
digester.addSetNext(prefix + "Host/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));
digester.addObjectCreate(prefix + "Host/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Valve");
digester.addSetNext(prefix + "Host/Valve",
"addValve",
"org.apache.catalina.Valve");
}
所以addRuleSet的作用,其实就是一个Rule的集合,为了更好统一管理单个标签和其子标签的Rule,单一职责原则。
从上面得出,我们看到比较多的就是三个Rule,分别为:
这三个Rule,后面在说,我们回来Catalina的load方法,在里面调用了Digester的parse方法开始解析xml文件
Digester:
public Object parse(InputSource input) throws IOException, SAXException {
configure();
// 调用了getXMLReader().parse(input);开始解析server.xml
getXMLReader().parse(input);
return (root);
}
我们看下Digester的源码,我们之前说了它是对SAX技术的包装,那么自然就有几个比较典型的方法
public class Digester extends DefaultHandler2 {
/*
* 存储当前节点的rule集合的栈
*/
protected ArrayStack<List<Rule>> matches = new ArrayStack<>(10);
/*
* 存储当前节点的对象的栈
*/
protected ArrayStack<Object> stack = new ArrayStack<>();
/*
* 存储当前节点的内容
*/
protected StringBuilder bodyText = new StringBuilder();
/*
* 存储上一个节点的内容的栈
*/
protected ArrayStack<StringBuilder> bodyTexts = new ArrayStack<>();
/*
* 下面只列取3个关键的方法startElement、characters、endElement,省略了一些其他的方法和属性
* 以Server节点为例
*/
/*
* qName:当前标签名
*/
@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 + ")");
}
list = updateAttributes(list);
// 把上一个bodyText压入栈中
bodyTexts.push(bodyText);
// 新建一个当前节点的bodyText,用于存储当前节点的内容。每一个节点会对应一个新的bodyText
bodyText = new StringBuilder();
// 获取当前节点名称
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
}
/*
* match:表示之前解析过的节点路径
* 比如说我现在有三个节点
*
*
*
*
*
* 如果当我解析到c的时候,那么match:a/b
*/
StringBuilder sb = new StringBuilder(match);
// 如果match长度不为空,在match后面加上/
if (match.length() > 0) {
sb.append('/');
}
// 把当前节点添加上去
sb.append(name);
/*
* 获取当前节点完整路径,如上面的节点就是a/b/c,把当前路径赋值给成员变量match保存起来
*/
match = sb.toString();
if (debug) {
log.debug(" New match='" + match + "'");
}
/*
* 根据match【也就是当前节点】,获取当前路径下的Rule集合。
* 还记得之前调用addObjectCreate、addSetProperties、addSetNext等方法
* 添加的Rule,就在这里起作用了
*/
List<Rule> rules = getRules().match(namespaceURI, match);
// 把获取的rule集合压入matches栈中
matches.push(rules);
if ((rules != null) && (rules.size() > 0)) {
/*
* 依次调用该节点下的Rule集合的begin方法
* 上面我们所有常见的三个Rule中,
* ObjectCreateRule、SetPropertiesRule有重写begin方法
* 后面我们会分析这两个Rule
*/
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 characters(char buffer[], int start, int length) throws SAXException {
if (saxLog.isDebugEnabled()) {
saxLog.debug("characters(" + new String(buffer, start, length) + ")");
}
/*
* 把当前节点的内容,append到bodyText
* 上面startElement说过每个节点会创建一个bodyText【StringBuilder】去存储节点内容
*/
bodyText.append(buffer, start, length);
}
@Override
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException {
bodyText = updateBodyText(bodyText);
// 获取当前节点名称
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
}
/*
* 从matches栈顶取出当前节点的Rule,并且从栈顶移除
* pop:取出栈顶的值,并且移除栈顶的值
* peek:取出栈顶的值,但是不移除栈顶的值
*/
List<Rule> rules = matches.pop();
if ((rules != null) && (rules.size() > 0)) {
// 获取当前节点的内容
String bodyText = this.bodyText.toString();
// 循环当前的rule,并且调用rule的body方法
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 + "'.");
}
}
// 将当前bodyText从bodyTexts栈顶移除
bodyText = bodyTexts.pop();
if (rules != null) {
// 循环当前的rule,并且调用rule的end方法
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 = "";
}
}
}
下面我们来分析下比较常见的几个Rule【ObjectCreateRule、SetPropertiesRule、SetNextRule】
它重写了Rule的begin、end方法,所以我们主要看这两个方法。
public class ObjectCreateRule extends Rule {
// 省略一些成员变量、方法
public ObjectCreateRule(String className,
String attributeName) {
this.className = className;
this.attributeName = attributeName;
}
@Override
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
/*
* 把className赋值给realClassName ,比如server对应的:org.apache.catalina.core.StandardServer
*/
String realClassName = className;
/*
* 如果attributeName不为空。因为有些className要从xml文件中读取的,比如server.xml的Listener
* 举例:
* 1、有时候上层调用addObjectCreate会给className传入null
* digester.addObjectCreate("Server/Listener", null, "className");
* 那么这时候这个className=null,attributeName = "className",
*
* 2、digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
* 那么这时候这个className="org.apache.catalina.core.StandardServer",attributeName = "className"
*
*/
if (attributeName != null) {
/*
* 那么从节点中获取该属性的值
*/
String value = attributes.getValue(attributeName);
/*
* 如果获取到的值不为空,那么该值就是真正的className。
*/
if (value != null) {
realClassName = value;
}
}
/*
* 如果获取到的realClassName为空,那么说明没有设置className,则直接抛出空指针
*/
if (realClassName == null) {
throw new NullPointerException("No class name specified for " +
namespace + " " + name);
}
// 利用classLoader创建对象
Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
Object instance = clazz.getConstructor().newInstance();
// 并且把当前创建的对象,压入digester的栈中。Digester有一个成员变量存储对象的栈,叫做stack。可以回去看看
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());
}
}
}
所以我们根据上述代码,我们可以知道ObjectCreateRule
它重写了Rule的begin方法,所以我们主要看这个方法。
public class SetPropertiesRule extends Rule {
public void begin(String namespace, String theName, Attributes attributes)
throws Exception {
/*
* 从栈顶中取出之前在ObjectCreateRule创建的对象。
*/
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 + "'");
}
/*
* digester.isFakeAttribute(top, name):如果当前的属性,是要被忽略的属性,那么就不进行设置。
* 还记得在Catalina.createStartDigester的方法中,创建了一个fakeAttributes的HashMap
* 里面添加了一个className,并且把这个fakeAttributes设置到Digester中。这个fakeAttributes
* 就在这时候起作用了,没印象可以回去看看。
*
* IntrospectionUtils.setProperty(top, name, value):主要是利用反射把标签属性的值设置到对象中
* 这个方法源码,就不贴出来了,有兴趣可以去看,就是简单的反射调用属性的set方法然后进行设置。
* 举例:比如说属性为port,那么首先从方法中找到setPort方法,如果setPort方法不存在,那么它就
* 会找setProperty("name", "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.");
}
}
}
}
所以我们根据上述代码,我们可以知道SetPropertiesRule
它重写了Rule的end方法,所以我们主要看这个方法。
public class SetNextRule extends Rule {
public void end(String namespace, String name) throws Exception {
// 取出栈顶的元素,但不移除。为了好记,这个取名为child
Object child = digester.peek(0);
// 取出栈顶后面的一个元素,但不移除。为了好记,这个取名为parent
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 + ")");
}
}
/*
* 把child的对象利用反射注入到parent中
* 举例:以Server节点为例,在Catalina.createStartDigester中
* 调用了digester.addSetNext("Server",
* "setServer",
* "org.apache.catalina.Server");
* 而且在Catalina.load方法中,调用了digester.push(this);把Catalina的对象压入Digester的对象栈中
*
* 所以IntrospectionUtils.callMethod1方法的意思就是:反射调用Catalina.setServer(Server server)方法
*/
IntrospectionUtils.callMethod1(parent, methodName,
child, paramType, digester.getClassLoader());
}
}
所以我们根据上述代码,我们可以知道SetNextRule
整个Digester 解析XML的原理,就是这样了。理解Digester能更好知道Tomcat在初始化时基于server.xml创建了哪些对象。