前言
Tomcat中的xml解析,是使用的apache開源組件digester
婴谱。在tomcat源碼中析砸,Digester類
所在位置為org.apache.tomcat.util.digester.Digester
厨钻,它把開源組件digester
的源代碼拷貝了過來咆畏。
本文,我們主要是想了解一下digester
的用法攒砖。以便在閱讀tomcat源碼的時候缸兔,看到xml解析相關(guān)代碼的時候不會一臉茫然和懵逼日裙。
digester
有兩種使用方式:
- 一種為tomat內(nèi)嵌的
org.apache.tomcat.util.digester.Digester
; - 另一種為
digester maven依賴
灶体。
本文采用第二種--maven依賴的方式阅签。
digester實現(xiàn)原理
digester
最初是作為struct的一個工具模塊,來完成xml解析的功能蝎抽。但是很快地政钟,有人發(fā)現(xiàn)并覺得digester不應(yīng)該僅僅局限在struct,而應(yīng)該變得更通用樟结。于是經(jīng)過apache的孵化养交,最終加入到了apache commons類庫家族中,并形成了一個xml另類解析的工具類庫瓢宦。
digester
底層是基于SAX
+事件驅(qū)動
+棧
的方式來搭建實現(xiàn)的碎连。那么在digester中,這三種元素分別起到什么作用呢驮履?
- SAX鱼辙,用于解析xml
- 事件驅(qū)動,在SAX解析的過程中加入事件來支持我們的對象映射
- 棧玫镐,當(dāng)解析xml元素的開始和結(jié)束的時候倒戏,需要通過xml元素映射的類對象的入棧和出棧來完成事件的調(diào)用
通過一些實實在在的場景和例子,我們發(fā)現(xiàn)一個元素的作用無非是在其解析前后加入一些擴(kuò)展邏輯恐似!例如:
- 開始解析某個節(jié)點的時候杜跷,是否需要創(chuàng)建一個類
- 開始解析某個節(jié)點的時候,是否需要入棧操作
- 結(jié)束解析某個節(jié)點的時候矫夷,是否需要執(zhí)行某個方法
- 結(jié)束解析某個節(jié)點的時候葛闷,是否需要出棧操作
如何引入依賴包
以maven為例,使用下面的dependency双藕。
<dependency>
<groupId>commons-digester</groupId>
<artifactId>commons-digester</artifactId>
<version>2.1</version>
</dependency>
如何使用
假如我們需要解析的xml為下面的格式淑趾。
<?xml version='1.0' encoding='utf-8'?>
<School name="Jen">
<Grade name="1">
<Class name="1" number="31"/>
<Class name="2" number="32"/>
</Grade>
<Grade name="2">
<Class name="1" number="41"/>
<Class name="2" number="42"/>
<Class name="3" number="37"/>
</Grade>
</School>
同時,我們假設(shè)下面的約定成立:
- 一個學(xué)校有名字屬性蔓彩,下面有多個年級
- 每個年級有名字屬性治笨,下面有多個班
- 每個班有名字和學(xué)生人數(shù)兩個屬性
根據(jù)上面的規(guī)則,我們需要創(chuàng)建關(guān)聯(lián)的3個類赤嚼,School
、Grade
和Class
顺又。
School有一個方法addGrade
用于往學(xué)校對象中添加年級更卒。
package com.juconcurrent.learn.apache.digester;
public class School {
private String name;
private Grade grades[] = new Grade[0];
private final Object servicesLock = new Object();
public void addGrade(Grade g) {
synchronized (servicesLock) {
Grade results[] = new Grade[grades.length + 1];
System.arraycopy(grades, 0, results, 0, grades.length);
results[grades.length] = g;
grades = results;
}
}
public Grade[] getGrades() {
return grades;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
同樣的,年級有一個addClass
方法稚照,用于往Grade對象
添加Class
班對象蹂空。
package com.juconcurrent.learn.apache.digester;
public class Grade {
private String name;
private Class classes[] = new Class[0];
private final Object servicesLock = new Object();
public void addClass(Class c) {
synchronized (servicesLock) {
Class results[] = new Class[classes.length + 1];
System.arraycopy(classes, 0, results, 0, classes.length);
results[classes.length] = c;
classes = results;
}
}
public Class[] getClasses() {
return classes;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Class就比較簡單了俯萌,只是一個簡單的POJO對象。
package com.juconcurrent.learn.apache.digester;
public class Class {
private String name;
private int number;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
好了上枕,我們已經(jīng)定義好了我們所需要創(chuàng)建對象的類咐熙。那么如何使用digester來創(chuàng)建我們所需的數(shù)據(jù)呢?我們先給出例子辨萍,然后再來詳細(xì)分析其中的關(guān)鍵方法棋恼。
package com.juconcurrent.learn.apache.digester;
import org.apache.commons.digester.Digester;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class DigesterTest {
// 屬性和get/set方法,假設(shè)我們解析出來的School對象放在這兒
private School school;
public School getSchool() {
return school;
}
public void setSchool(School s) {
this.school = s;
}
private void digester() throws IOException, SAXException {
// 讀取根據(jù)文件的路徑锈玉,創(chuàng)建InputSource對象爪飘,digester解析的時候需要用到
File file = new File("/Users/pro/ws/learn/learn-javaagent/src/main/resources/School.xml");
InputStream inputStream = new FileInputStream(file);
InputSource inputSource = new InputSource(file.toURI().toURL().toString());
inputSource.setByteStream(inputStream);
// 創(chuàng)建Digester對象
Digester digester = new Digester();
// 是否需要用DTD驗證XML文檔的合法性
digester.setValidating(true);
// 將當(dāng)前對象放到對象堆的最頂層,這也是這個類為什么要有school屬性的原因拉背!
digester.push(this);
/*
* 下面開始為Digester創(chuàng)建匹配規(guī)則
* Digester中的School师崎、School/Grade、School/Grade/Class椅棺,分別對應(yīng)School.xml的School犁罩、Grade、Class節(jié)點
*/
// 為School創(chuàng)建規(guī)則
/*
* Digester.addObjectCreate(String pattern, String className, String attributeName)
* pattern, 匹配的節(jié)點
* className, 該節(jié)點對應(yīng)的默認(rèn)實體類
* attributeName, 如果該節(jié)點有className屬性, 用className的值替換默認(rèn)實體類
*
* Digester匹配到School節(jié)點
*
* 1. 如果School節(jié)點沒有className屬性两疚,將創(chuàng)建com.juconcurrent.learn.apache.digester.School對象床估;
* 2. 如果School節(jié)點有className屬性,將創(chuàng)建指定的(className屬性的值)對象
*/
digester.addObjectCreate("School", School.class.getName(), "className");
// 將指定節(jié)點的屬性映射到對象鬼雀,即將School節(jié)點的name的屬性映射到School.java
digester.addSetProperties("School");
/*
* Digester.addSetNext(String pattern, String methodName, String paramType)
* pattern, 匹配的節(jié)點
* methodName, 調(diào)用父節(jié)點的方法
* paramType, 父節(jié)點的方法接收的參數(shù)類型
* Digester匹配到School節(jié)點顷窒,將調(diào)用DigesterTest(School的父節(jié)點)的setSchool方法,參數(shù)為School對象
*/
digester.addSetNext("School", "setSchool", School.class.getName());
// 為School/Grade創(chuàng)建規(guī)則
digester.addObjectCreate("School/Grade", Grade.class.getName(), "className");
digester.addSetProperties("School/Grade");
// Grade的父節(jié)點為School
digester.addSetNext("School/Grade", "addGrade", Grade.class.getName());
// 為School/Grade/Class創(chuàng)建規(guī)則
digester.addObjectCreate("School/Grade/Class", Class.class.getName(), "className");
digester.addSetProperties("School/Grade/Class");
digester.addSetNext("School/Grade/Class", "addClass", Class.class.getName());
// 解析輸入源
digester.parse(inputSource);
}
// 只是將School對象進(jìn)行控制臺輸出
private void print(School s) {
if (s != null) {
System.out.println(s.getName() + "有" + s.getGrades().length + "個年級");
for (int i = 0; i < s.getGrades().length; i++) {
if (s.getGrades()[i] != null) {
Grade g = s.getGrades()[i];
System.out.println(g.getName() + "年級 有 " + g.getClasses().length + "個班:");
for (int j = 0; j < g.getClasses().length; j++) {
if (g.getClasses()[j] != null) {
Class c = g.getClasses()[j];
System.out.println(c.getName() + "班有" + c.getNumber() + "人");
}
}
}
}
}
}
// 入口main()方法
public static void main(String[] args) throws IOException, SAXException {
DigesterTest digesterTest = new DigesterTest();
digesterTest.digester();
digesterTest.print(digesterTest.school);
}
}
這兒我們需要著重說明一下digester里面的幾個方法源哩,大體上我們可以將其方法分為兩類:操作類和規(guī)則類鞋吉。
- 操作類
- public void setValidating(boolean validating) //
是否根據(jù)DTD校驗XML
- public void push(Object object) //
將對象壓入棧
- public Object peek() //
獲取棧頂對象
- public Object pop() //
彈出棧頂對象
- public Object parse(InputSource input) //
解析輸入源
- public void setValidating(boolean validating) //
- 規(guī)則類
- public void addObjectCreate(String pattern, String className, String attributeName) //
增加對象創(chuàng)建規(guī)則,當(dāng)匹配到pattern模式時励烦,如果指定了attributeName谓着,則根據(jù)attributeName創(chuàng)建類對象;否則根據(jù)className創(chuàng)建類對象
- public void addSetProperties(String pattern) //
增加屬性設(shè)置規(guī)則坛掠,當(dāng)匹配到pattern模式時赊锚,就填充其屬性
- public void addSetNext(String pattern, String methodName, String paramType) //
增加設(shè)置下一個規(guī)則,當(dāng)匹配到pattern模式時屉栓,調(diào)用父節(jié)點的methodName方法舷蒲,paramType為方法傳入?yún)?shù)的類型
- public void addRule(String pattern, Rule rule) //
當(dāng)匹配到pattern模式時,增加一個自定義規(guī)則
- public void addRuleSet(RuleSet ruleSet) //
增加規(guī)則集友多,一個規(guī)則集指的是對一個節(jié)點及下面的所有后續(xù)節(jié)點(子節(jié)點牲平、子節(jié)點的子節(jié)點...)的解析
- public void addObjectCreate(String pattern, String className, String attributeName) //
Tomcat中的規(guī)則解析例子
上面我們寫了一個非常簡單的例子,相信通過這樣的例子我們可以很快地入門了域滥。那么tomcat里面又是怎樣寫的呢纵柿?我們看看org.apache.catalina.startup.Catalina.createStartDigester
這個方法蜈抓,這個方法用于定義對server.xml的解析。該方法比較長昂儒,但是我并不打算對這個方法進(jìn)行閹割和壓縮沟使,而是原封不動地拷貝到這兒,以便大家對此有一個比較完整的認(rèn)識渊跋。
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
// 這兒設(shè)置無效的屬性腊嗡,fake是贗品的意思,也就是在檢查到這些屬性直接認(rèn)為是無效的
Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
List<String> objectAttrs = new ArrayList<>();
objectAttrs.add("className");
fakeAttributes.put(Object.class, objectAttrs);
// Ignore attribute added by Eclipse for its internal tracking
List<String> contextAttrs = new ArrayList<>();
contextAttrs.add("source");
fakeAttributes.put(StandardContext.class, contextAttrs);
digester.setFakeAttributes(fakeAttributes);
// 設(shè)置是否使用線程上下文類加載器
digester.setUseContextClassLoader(true);
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addObjectCreate("Server/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
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", "sslImplementationName"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
digester.addSetNext("Server/Service/Connector/SSLHostConfig",
"addSslHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
new CertificateCreateRule());
digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
new SetAllPropertiesRule(new String[]{"type"}));
digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
"addCertificate",
"org.apache.tomcat.util.net.SSLHostConfigCertificate");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"setOpenSslConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"addCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
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");
digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
"addUpgradeProtocol",
"org.apache.coyote.UpgradeProtocol");
// 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/"));
// When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
// 根據(jù)t1和t2刹枉,算出整個server.xml的Digester創(chuàng)建花費(fèi)的時間
long t2=System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Digester for server.xml created " + ( t2-t1 ));
}
return (digester);
}
這兒我們看到叽唱,在tomcat中明顯地用到了前面例子中說明的幾個規(guī)則,我們再簡單羅列一下:
- addObjectCreate微宝,對象創(chuàng)建規(guī)則
- addSetProperties棺亭,屬性設(shè)置規(guī)則
- addSetNext,設(shè)置下一個規(guī)則
- addRule蟋软,自定義規(guī)則
- digester.addRuleSet镶摘,自定義規(guī)則集
總結(jié)
本文我們對digester做了一個使用說明。
我們首先簡單地說明了一下digester是什么岳守,內(nèi)部基于什么原理來實現(xiàn)的凄敢。然后通過一個School、Grade和Class這樣的生活中的例子來說明digester的用法湿痢。最后通過查看tomcat中關(guān)于digester的例子代碼涝缝,加深了我們對于digester的理解。
相信通過這篇文章譬重,讓我們在閱讀tomcat源碼的過程中不再對xml解析產(chǎn)生疑惑拒逮!