Tomcat源码阅读之Digester学习

667 阅读8分钟
最近在阅读Tomcat源码,读到Catalina类中getDigester()方法对XML文件的加载和处理部分,于是把Digester这个类的用法和底层原理学习一下。                                             
参考:
blog.csdn.net/shang02/art… 

blog.csdn.net/u011545486/…

blog.csdn.net/caihaijiang…

 一般用来读取xml文件的工具包有DOM、SAX和JDOM等,但用过的人都知道,它们属于比较底层的API,写起来代码量很大,而且如果修改了xml文件的格式,代码也要做大幅度的改动。而使用Apache Jakarta的Digester,解析XML文件非常方便且不需要过多的关心底层的具体解析过程。Digester本来仅仅是Jakarta Struts中的一个工具,用于处理struts-config.xml配置文件。显然,将XML文件转换成相应的Java对象是一项很通用的功能,这个工具理应具有更广泛的用途,所以很快它就在Jakarta Commons项目(用于提供可重用的Java组件库)中有了一席之地。Digester由"事件"驱动,通过调用预定义的规则操作对象栈,将XML文件转换为Java对象。

工作原理如下: Digester底层采用SAX(Simple API for XML)析XML文件,所以很自然的,对象转换由"事件"驱动,在遍历每个节点时,检查是否有匹配模式,如果有,则执行规则定义的操作,比如创建特定的Java对象,或调用特定对象的方法等。此处的XML元素根据匹配模式(matching pattern)识别,而相关操作由规则(rule)定义。 Digester依次读入XML文件,采用堆栈残存储实例,从父节点开始读入,创建父节点对应的实例压入堆栈,当读到结束标志时弹出堆栈,读到子节点,创建子节点对应实例压入堆栈,因此可以看出次栈顶实例是栈顶实例的父节点,同级的节点之间没有关联关系。

指定Digester的规则

XML元素匹配模式

左边是XML代码,右边是匹配模式

  <datasources> 'datasources' 
  <datasource>         'datasources/datasource' 
    <name/>            'datasources/datasource/name' 
    <driver/>          'datasources/datasource/driver'  
  </datasource> 
  <datasource>         'datasources/datasource' 
    <name/>            'datasources/datasource/name' 
    <driver/>          'datasources/datasource/driver'  
  </datasource> 
</datasources> 

配置Digester

Java对象

package com.model;
public User{
    private String userName;
    private String password;
    public String getUserName(){
        return this.userName;
    }
    public void setUserName(String userName){
        this.userName=userName;
    }
    public String getPassword(){
        return this.password;
    }
    public void setPassword(String password){
        this.password=password;
    }
}

XML代码

<?xml version="1.0" encoding="UTF-8?>  
<database>  
    <user userName="guest" password="guest">  
    </user>  
</database>  
  1. 先new一个Digeter对象,然后设置节点与Java对象的映射规则,pattern指定节点的筛选规则,class设置映射对象。SAX解析时,遇到pattern指定的节点,会创建一个class实例放入堆栈中。
  2. 比如:digester.addObectCreate("database/user","com.model.User").解析遇到user节点时,会创建一个User实例并放入堆栈中。
  3. addSetProperties(String pattern)
    设置节点的属性设置规则。当解析遇到符合pattern的节点时,根据属性列表中的属性值对,使用Java反射机制使用标准的JavaBean方法设置栈顶对象实例; 比如:digester.addSetProperties("database/user"),解析遇到user节点时,会获取键值对 userName=guest,password=guest,获得栈顶的User对象,设置实例的userName、password属性;
  4. addBeanPropertySetter(String pattern)
    该方法的作用及使用方法类似于addSetProperties,只不过它是用pattern所指定的标签来调用对象的setter。
  5. addSetNext(String pattern,String methodName)
    设置当前pattern节点与父节点的调用规则,当遇到pattern节点时,调用堆栈中的次栈顶元素调用methodName方法。将栈顶元素作为次顶元素指定方法的输入参数。
  6. addCallMethod(String pattern,String methodName,int paraNumber)
    该方法同样设置对象的属性,但更加灵活,不需要对象具有setter 根据pattern规则指定的属性,调用对象的methodName方法,paraNumber参数是表示方法需要的参数个数,当paraNumber=0时,可以单独使用,不然需要配合addCallParam方法。
    比如:digester.addCallMethod("database/user/userName","setUserName",0), 参数为xml当前值;无参方法:digester.addCallMethod( "pattern", "methodName" );.
  7. addCallParam(String pattern,int paraIndex,String attributeName)
    该方法与addCallMethod配合使用,根据pattern指定的标签属性来调用方法 paraIndex表明需要填充的方法形参序号,从0开始,方法由addCallMethdo指定,attributeName指定标签属性名;
  8.  addRule(String pattern, Rule rule)
    以上所有方法都是根据需求构造不同的rule,然后调用此方法对pattern节点添加规则。

源码分析

以上2-7方法都调用了addRule(),不同的方法只是对应的rule不同。

    public void addRule(String pattern, Rule rule) {
        rule.setDigester(this);
        this.getRules().add(pattern, rule);
    }

addRule方法中又调用了由getRules()得到的Rules对象的add方法。查看getRules()方法。

    public Rules getRules()
    {
        if ( this.rules == null )
        {
            this.rules = new RulesBase();
            this.rules.setDigester( this );
        }
        return ( this.rules );
    }

进一步查看可知RuleBase的关系。


上面调用的add方法实际上是AbstractRulesImpl的方法。

    //public abstract class AbstractRulesImpl
    public final void add( String pattern, Rule rule )
    {
        // set up rule
        if ( this.digester != null )
        {
            rule.setDigester( this.digester );
        }

        if ( this.namespaceURI != null )
        {
            rule.setNamespaceURI( this.namespaceURI );
        }

        registerRule( pattern, rule );
    }

最后一行调用的registerRule方法由其实现类来实现,这是一种模板方法设计模式。

    //public class RulesBase
    @Override
    protected void registerRule( 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 );
        }
        
        //cache使用一个HashMap,存储pattern和对应的rule列表的映射。
        List<Rule> list = cache.get( pattern );
        if ( list == null )
        {
            list = new ArrayList<Rule>();
            cache.put( pattern, list );
        }
        //为该rule列表添加rule
        list.add( rule );
        //rules是一个总的rule列表,具体作用暂不清楚
        rules.add( rule );
    }

对XML文件的读取和解析

当配置完成后,调用digester.parse(InputSource input)方法对读取的XML文件内容解析,从对象堆栈返回根元素。

源码分析

    /**
    *public class Digester
    *先通过流读取文件,然后转为InputSource,再使用org.xml.sax.XMLReader对读取的XML文件内容进行
    *解析,从对象堆栈返回根元素。
    */
    public Object parse(InputSource input) throws IOException, SAXException {
        configure();
        getXMLReader().parse(input);
        return root;
    }

主要方法是getXMLReader().parse(input)。先看getXMLReader().

//public class Digester
    /**
     * Return the XMLReader to be used for parsing the input document.
     *
     * FIXME: there is a bug in JAXP/XERCES that prevent the use of a parser that contains a schema with a DTD.
     *
     * @return the XMLReader to be used for parsing the input document.
     * @exception SAXException if no XMLReader can be instantiated
     */
    public XMLReader getXMLReader()
        throws SAXException
    {
        if ( reader == null )
        {
            //getParser获得的是SAXParserImpl,其getXMLReader方法获得的XMLReader实现类是JAXPSAXParser
            reader = getParser().getXMLReader();
        }

        reader.setDTDHandler( this );
        reader.setContentHandler( this );

        if ( entityResolver == null )
        {
            reader.setEntityResolver( this );
        }
        else
        {
            reader.setEntityResolver( entityResolver );
        }

        reader.setErrorHandler( this );
        return reader;
    }

首先,理解Digester首先需要理解SAX的使用,关于SAX相关的类在jre中rt.jar中,属于jdk自带且可以用于解析xml。SAX是一种事件方式驱动去进行解析xml,它通过逐行读取xml文件,并在解析各个部分时触发相应的事件,然后通过回调函数进行自己的事件处理。与DOM的方式不同,SAX解析的xml不会保存其结构和状态,没有前后父子的关系等。 SAX API中主要有四种处理事件的接口,它们分别是ContentHandler,DTDHandler, EntityResolver 和 ErrorHandle。Digester继承自DefaultHandler,而DefaultHandler实现了这四个接口,因此reader对这四个接口实现类对象的4个set方法均传入this作为参数。

因为获得的XMLReader对象由JAXPSAXParser实现,所以接下来看一下JAXPSAXParser。先看继承实现结构。


追踪源码看到parse方法一直到XMLParser中。

//public abstract class XMLParser
    public void parse(XMLInputSource inputSource)
        throws XNIException, IOException {
        // null indicates that the parser is called directly, initialize them
        if (securityManager == null) {
            securityManager = new XMLSecurityManager(true);
            fConfiguration.setProperty(Constants.SECURITY_MANAGER, securityManager);
        }
        if (securityPropertyManager == null) {
            securityPropertyManager = new XMLSecurityPropertyManager();
            fConfiguration.setProperty(Constants.XML_SECURITY_PROPERTY_MANAGER, securityPropertyManager);
        }

        reset();
        fConfiguration.parse(inputSource);

    } // parse(XMLInputSource)

fConfiguration是protected修饰的XML11Configuration变量,在之前已被赋值,存有之前提到的各种handler以及其他信息。通过debug断点调试看到在XML11Configuration的parse方法中最后是return fCurrentScanner.scanDocument(complete);

因为方法太多,直接看debug过程中调用的方法。


DefaultHandler的重要方法

startDocument():文档解析开始时调用,该方法只会调用一次 ;

startElement(String uri, String localName, String qName, Attributes attributes):标签(节点)解析开始时调用 uri:xml文档的命名空间 localName:标签的名字 qName:带命名空间的标签的名字 attributes:标签的属性集 ;

characters(char[] ch, int start, int length):解析标签的内容的时候调用 ch:当前读取到的TextNode(文本节点)的字节数组 start:字节开始的位置,为0则读取全部 length:当前TextNode的长度 ;

endElement(String uri, String localName, String qName):标签(节点)解析结束后调用; 

endDocument():文档解析结束后调用,该方法只会调用一次;

Catalina中对Digester的使用

通过createStartDigester()方法配置Digester,读取并解析Server.xml的内容,生成所需的组件对象(Server/Service/Connector等)。

    /**
     * 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<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);
        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;

    }