Digester解析xml原理
Tomcat内部是使用Digester来解析xml文件的,将xml转化为java对象。
digester底层是基于SAX+事件驱动+栈的方式来搭建实现的,SAX主要用来解析xml,事件驱动主要是在解析的过程中加入事件来操作节点元素,栈主要是在节点解析开始和结束时对xml节点元素对应的对象操作入栈或出栈来实现事件的调用。
使用方法
定义一个Department部门类以及一个User用户类,部门中包含许多个用户
@Data
public class Department {
private String departmentName;
private List userList;
public Department() {
userList = new ArrayList<>();
}
public void addUser(User user) {
userList.add(user);
}
}
@Data
public class User {
private String userName;
private String age;
public void print() {
System.out.println("userName:" + userName + ", age:" + age);
}
}
xml文件
<?xml version="1.0" encoding="UTF-8"?>
默念
定义规则解析xml文件
1. ObjectCreateRule规则类
public void addObjectCreate(String pattern, String className, String attributeName) 当匹配到pattern模式节点时会创建对象,当在节点中指定了attributeName属性时,会创建类型为attributeName属性值的对象否则创建类名为className的对象,并将创建的对象push到stack栈顶
2. SetPropertiesRule规则类
public void addSetProperties(String pattern) 当匹配到pattern模式节点时会填充stack栈顶元素对象的属性值
3. CallMethodRule规则类
public void addCallMethod(String pattern, String methodName) (无参)当匹配到pattern模式节点时会调用stack栈顶元素对象的methodName方法
public void addCallMethod(String pattern, String methodName, int paramCount) (有参,指定参数个数与CallParamRule规则配合使用,设置参数值),构建空的参数数组并push到params栈顶
4. CallParamRule规则类
public void addCallParam(String pattern, int paramIndex) 当匹配到pattern模式时,以pattern模式节点的内容填充params栈顶元素参数的值
5. SetNextRule规则类
public void addSetNext(String pattern, String methodName, String paramType) 当匹配到pattern模式时,调用栈顶元素的上一个元素的methodName方法并以栈顶元素作为参数
6. 自定义规则类
public void addRule(String pattern, Rule rule) 当匹配到pattern模式时,执行自定义的规则
public class DigesterTest {
public static void main(String[] args) throws IOException, SAXException {
InputStream resource
= ClassLoader.getSystemClassLoader().getResourceAsStream("test.xml");
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
// 创建对象规则
digester.addObjectCreate("department", Department.class.getName());
// 填充属性规则
digester.addSetProperties("department");
digester.addObjectCreate("department/user", User.class.getName());
digester.addSetProperties("department/user");
// 调用方法规则
digester.addCallMethod("department/user", "print");
// 调用栈顶元素上一个元素的指定方法,以栈顶元素作为参数
digester.addSetNext("department/user", "addUser", User.class.getName());
// 解析test.xml文件 获取department对象
Department department = (Department) digester.parse(resource);
System.out.println(department);
}
}
运行程序可以看到调用digester的parse方法后成功解析获得department对象
原理
上面列出的规则类都继承了Rule这个抽象类,能够在匹配pattern模式时执行相应的事件方法,让我们看看Rule中定义了哪些方法
/**
* Concrete implementations of this class implement actions to be taken when
* a corresponding nested pattern of XML elements has been matched.
*/
public abstract class Rule {
// ----------------------------------------------------------- Constructors
/**
* Base constructor.
* Now the digester will be set when the rule is added.
*/
public Rule() {}
// ----------------------------------------------------- Instance Variables
/**
* The Digester with which this Rule is associated.
*/
// 这个规则关联的digester
protected Digester digester = null;
/**
* The namespace URI for which this Rule is relevant, if any.
*/
protected String namespaceURI = null;
// ------------------------------------------------------------- Properties
/**
* Identify the Digester with which this Rule is associated.
*
* @return the Digester with which this Rule is associated.
*/
public Digester getDigester() {
return digester;
}
/**
* Set the Digester with which this Rule is
* associated.
*
* @param digester The digester with which to associate this rule
*/
public void setDigester(Digester digester) {
this.digester = digester;
}
/**
* Return the namespace URI for which this Rule is relevant, if any.
*
* @return The namespace URI for which this rule is relevant or
* null if none.
*/
public String getNamespaceURI() {
return namespaceURI;
}
/**
* Set the namespace URI for which this Rule is relevant, if any.
*
* @param namespaceURI Namespace URI for which this Rule is relevant,
* or null to match independent of namespace.
*/
public void setNamespaceURI(String namespaceURI) {
this.namespaceURI = namespaceURI;
}
// --------------------------------------------------------- Public Methods
/**
* This method is called when the beginning of a matching XML element
* is encountered. The default implementation is a NO-OP.
*
* @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 节点属性值列表
*
* @throws Exception if an error occurs while processing the event
*/
// 遇到匹配xml元素的开头是调用此方法
public void begin(String namespace, String name, Attributes attributes) throws Exception {
// NO-OP by default.
}
/**
* This method is called when the body of a matching XML element is
* encountered. If the element has no body, this method is not called at
* all. The default implementation is a NO-OP.
*
* @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 text The text of the body of this element 节点文本内容
*
* @throws Exception if an error occurs while processing the event
*/
// 遇到匹配的 XML 元素的主体时调用此方法, 内容为空的话不调用
public void body(String namespace, String name, String text) throws Exception {
// NO-OP by default.
}
/**
* This method is called when the end of a matching XML element
* is encountered. The default implementation is a NO-OP.
*
* @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
*
* @throws Exception if an error occurs while processing the event
*/
// 当遇到匹配的 XML 元素的结尾时调用此方法。
public void end(String namespace, String name) throws Exception {
// NO-OP by default.
}
/**
* This method is called after all parsing methods have been
* called, to allow Rules to remove temporary data.
*
* @throws Exception if an error occurs while processing the event
*/
// 所有解析方法调用后调用此方法,允许规则删除临时产生的数据
public void finish() throws Exception {
// NO-OP by default.
}
}
接下来再来看看Digester的几个重要属性和方法
// 继承了SAX的DefaultHandler类,会在解析过程中接受到相应的通知
public class Digester extends DefaultHandler2 {
// 用来解析占位符属性 ${xxx}, 主要从System.getProperty(xxx)获取
protected IntrospectionUtils.PropertySource source[] = new IntrospectionUtils.PropertySource[] {
new SystemPropertySource() };
// 当前正在解析的节点内容
protected StringBuilder bodyText = new StringBuilder();
// 解析过程中产生的节点内容堆栈
protected ArrayStack bodyTexts = new ArrayStack<>();
// 解析过程中存储规则列表的堆栈 list中的每个规则有相同的pattern
protected ArrayStack> matches = new ArrayStack<>(10);
// 嵌套元素处理的当前匹配模式 例如 department 、 department/user
protected String match = "";
// 存储方法参数的堆栈
protected ArrayStack
sax解析xml的过程中无论是处理文档还是节点元素都会有开始解析节点、结束解析节点的通知,会调用子类Digester的相应方法,在方法中用事先定义的规则对节点元素进行事件处理。
接着尝试自定义一个规则来打印节点解析过程中的日志,方便我们更加清晰的明白其处理流程。
自定义规则CustomRule,继承Rule
@Slf4j(topic = "e")
public class CustomRule extends Rule {
@Override
public void begin(String namespace, String name, Attributes attributes) throws Exception {
log.info("开始解析" + name + "节点");
log.info("节点属性值:");
for (int i = 0; i < attributes.getLength(); i++) {
log.info(attributes.getLocalName(i) + "=" + attributes.getValue(i));
}
}
@Override
public void body(String namespace, String name, String text) throws Exception {
log.info("节点元素" + name + "内容:" + text);
}
@Override
public void end(String namespace, String name) throws Exception {
log.info("结束解析" + name + "节点");
}
}
在测试类中加入一行代码使我们自定义的规则发生作用,digester.addRule("department/user", new CustomRule()); 自定义规则匹配user节点,下图可以看到节点解析过程中调用方法传递的参数等信息
最后附上tomcat中的server.xml解析代码
server.xml
<?xml version="1.0" encoding="UTF-8"?>
org.apache.catalina.startup.Catalina#createStartDigester
/**
* Create and configure the Digester we will be using for startup.
* @return the main digester to parse server.xml
*/
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
Map, List> fakeAttributes = new HashMap<>();
List objectAttrs = new ArrayList<>();
objectAttrs.add("className");
fakeAttributes.put(Object.class, objectAttrs);
// Ignore attribute added by Eclipse for its internal tracking
List contextAttrs = new ArrayList<>();
contextAttrs.add("source");
fakeAttributes.put(StandardContext.class, contextAttrs);
digester.setFakeAttributes(fakeAttributes);
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/");
long t2=System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Digester for server.xml created " + ( t2-t1 ));
}
return (digester);
}