tomcat源码解读一 Digester的解析方式

1    Digester

Digester在tomcat中的作用是对conf下的server.xml文件进行实例化,其是从Catalian这个组件开始,创建Digester实例,再添加对应的规则,然后将其实例化,通过setServer方法,将其实例话的对象作为当前Catalian实例的句柄。这样就实现了对象句柄之间的关联引用,从而实现整个平台的递进启动。

1.1    UML类图

1.2    UML时序图

1.3    规则添加解析

1.3.1  添加对应解析规则

规则的添加实在Catalia.java的load()方法之中。规则主要是根据各个标签创建对应对象的规则,以及解析对象的通过何种方法设为相应句柄属性。其主要实现过程是创建Digester实例,设置规则

protected Digester createStartDigester() {
    long t1=System.currentTimeMillis();
    //创建一个digester实例
    Digesterdigester = new Digester();
    //是否需要验证xml文档的合法性,false表示不需要进行DTD规则校验
    digester.setValidating(false);
    //是否需要进行节点设置规则校验
    digester.setRulesValidation(true);
    //将xml节点中的className作为假属性,不用调用默认的setter方法
    //在解析时,调用相应对象的setter方法来设置属性值,setter的参数就是节点属性,
    //而有className的话,则直接使用className来直接实例化对象
    HashMap<Class<?>,List<String>> fakeAttributes = new HashMap<>();
    ArrayList<String> attrs = new ArrayList<>();
    attrs.add("className");
    fakeAttributes.put(Object.class, attrs);
   digester.setFakeAttributes(fakeAttributes);
    digester.setUseContextClassLoader(true);

    //遇到xml中Server节点,就创建一个StandardServer对象注意在这里只是添加了这个规则
    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");

    //根据Server节点中的属性信息,调用属性的setter方法,比如说server节点中会有port=“8080”属性,则会调用setPort方法
    digester.addSetProperties("Server");

    //在上面的load方法中有个digester.push(this),this对象就是栈顶了
    //这里将Server节点对应的对象作为参数,调用this对象,也就是Catalina对象的setServer方法
    //意思即将addObjectCreate 在解析后的对象通过this在digester.push(this)中通过setServer方法注入当前server对象
    //注意这里只是添加规则
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");

    //Server节点下的GlobalNamingResources节点,创建一个NamingResource对象
    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节点
    digester.addObjectCreate("Server/Listener",
                             null, // MUST bespecified in the element
                             "className");
    digester.addSetProperties("Server/Listener");
    digester.addSetNext("Server/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");
    //Server下的Service节点

    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节点
    digester.addObjectCreate("Server/Service/Listener",
                             null, // MUST bespecified 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");

    //给Connector添加规则,就是当遇到Connector的时候,会调用ConnectorCreateRule里面定义的规则
    //跟上面的作用是一样的,只不过该节点的规则比较多,就创建一个规则类
    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 bespecified in the element
                             "className");
    digester.addSetProperties("Server/Service/Connector/Listener");
    digester.addSetNext("Server/Service/Connector/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    // AddRuleSets 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/"));

    // Whenthe '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);
}

1.3.2  注入栈顶对象

将当前catalina压入栈顶,stack 是一个ArrayStack实例

digester.push(this);

具体代码如下:

       如果stack的大小为0,则将当前对象赋值给root,这样做的目的是在解析之后,能够直接根据root句柄,返回当前对象

public void push(Object object) {

    if (stack.size() == 0) {
        root = object;
    }
    stack.push(object);

}
 
public Object parse(InputSource input) throws IOException,SAXException {
    configure();
    getXMLReader().parse(input);
    return (root);

}

1.3.3  解析标签创建实例

在解析xml直接首先要获取的xml阅读器,在这里获取的是,其过程是通过getParser方法获取对应的SAXParserImpl工厂,然后调用SAXParserImpl实例的newSAXParser方法,创建SAXParserImpl实例,然后设置相关属性

public XMLReader getXMLReader() throws SAXException{
    if (reader == null) {
        reader =getParser().getXMLReader();
    }

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

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

    reader.setProperty("http://xml.org/sax/properties/lexical-handler", this);

    reader.setErrorHandler(this);
    return reader;
}

根据上述代码,可以知道getXMLReader().parse(input);实际上调用的是SAXParserImpl中的 parse方法对input资源进行解析。方法如下:

public void parse(InputSource inputSource)
    throws SAXException,IOException {
    if (fSAXParser != null && fSAXParser.fSchemaValidator!= null) {
        if (fSAXParser.fSchemaValidationManager!= null) {
            fSAXParser.fSchemaValidationManager.reset();
            fSAXParser.fUnparsedEntityHandler.reset();
        }
        resetSchemaValidator();
    }
    super.parse(inputSource);
}

由上看出其继续调用父类AbstractSAXParser的parse方法,在这个父类方法,其主要将资源文件转化为了XMLInputSource,设置其相关属性,而后调用其重载方法,对XMLInputSource进行解析最终经过一系列转化调用Digester的startDocument方法。这个方法主要是设置了一下编码。在startDocument之后继续开始扫描文档,主要方法是scanDocument,开始对整个文档开始进行解析,方法如下:

public boolean scanDocument(boolean complete)
throws IOException, XNIException {
    fEntityManager.setEntityHandler(this);

    int event =next();
    do {
        switch (event) {
            case XMLStreamConstants.START_DOCUMENT:
                break;
            case XMLStreamConstants.START_ELEMENT:
                break;
            case XMLStreamConstants.CHARACTERS :
               fDocumentHandler.characters(getCharacterData(),null);
                break;
            case XMLStreamConstants.SPACE:
                break;
            case XMLStreamConstants.ENTITY_REFERENCE:
                break;
            case XMLStreamConstants.PROCESSING_INSTRUCTION:
                fDocumentHandler.processingInstruction(getPITarget(),getPIData(),null);
                break;
            case XMLStreamConstants.COMMENT :
                fDocumentHandler.comment(getCharacterData(),null);
                break;
            case XMLStreamConstants.DTD :
                break;
            case XMLStreamConstants.CDATA:
                fDocumentHandler.startCDATA(null);
                //xxx: checkif CDATA values comes from getCharacterData() function
                fDocumentHandler.characters(getCharacterData(),null);
                fDocumentHandler.endCDATA(null);
                //System.out.println("in CDATA of the XMLNSDocumentScannerImpl");
                break;
            case XMLStreamConstants.NOTATION_DECLARATION:
                break;
            case XMLStreamConstants.ENTITY_DECLARATION:
                break;
            case XMLStreamConstants.NAMESPACE :
                break;
            case XMLStreamConstants.ATTRIBUTE :
                break;
            case XMLStreamConstants.END_ELEMENT:

                break;
            default :
                throw new InternalError("processingevent: " + event);

        }
        event = next();
    } while (event!=XMLStreamConstants.END_DOCUMENT&& complete);

    if(event ==XMLStreamConstants.END_DOCUMENT) {
        fDocumentHandler.endDocument(null);
        return false;
    }

    return true;

}

    开始调用startElement对元素开始解析,先拼接模式然后获取其对应的规则,遍历所有规则,调用其对应规则实例的begin方法,这要求所有规则实现抽象类Rule,规则的添加在上文解析过程中。

public void startElement(String namespaceURI, String localName, String qName, Attributes list)
        throws SAXException {



    list = updateAttributes(list);
    bodyTexts.push(bodyText);
    bodyText = new StringBuilder();
    String name = localName;
    if ((name == null) || (name.length() < 1)) {
        name = qName;
    }
    StringBuilder sb = new StringBuilder(match);
    if (match.length() > 0) {
        sb.append('/');
    }
    sb.append(name);
    match = sb.toString();
    if (debug) {
        log.debug("  New match='" + match + "'");
    }
    List<Rule> rules = getRules().match(namespaceURI, match);
    matches.push(rules);
    if ((rules != null) && (rules.size() > 0)) {
        for (int i = 0; i < rules.size(); i++) {
       
                Rule rule = rules.get(i);
                rule.begin(namespaceURI, name, list);
                 
    } 
}

  根据上文的解析规则与过程,下面介绍一些对哪些对象做了解析

1.3.4  Rule实现类begin方法

对应规则:

1.3.4.1  ObjectCreateRule的begin方法

 ===============================================================   

  创建对象是根据realClassName,根据类加载器创建其对应的实例,然后将这个实例给压入digester的栈中,在这里有必要解释一下attributes这个属性的集合来自于配置文件, getValue这个方法是根据attributeName==》className来获取对应的类名,这些值来自于server.xml中的解析,所以可以看出如果xml中存在,则优先使用xml中的值。只是默认server.xml中为空

@Override
public void  begin(Stringnamespace, String name, Attributes attributes)
        throws Exception {
    String realClassName = className;
    if (attributeName!= null) {
        String value =attributes.getValue(attributeName);
        if (value != null) {
            realClassName = value;
        }
    }
……………………
    Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
    Object instance =clazz.newInstance();
    digester.push(instance);
}

1.3.4.2  SetPropertiesRule

===============================================================     

   当前方法主要是对属性进行规则验证,如果需要进行规则验证,且其是一个不合法的属性,则输出警告日志。

public void begin(String namespace, String theName,Attributes attributes)
        throws Exception {

    // Populate thecorresponding properties of the top object
    Object top = digester.peek();

    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.isFakeAttribute(top,name)
                && !IntrospectionUtils.setProperty(top,name, value)
                && digester.getRulesValidation()){
            digester.log.warn("[SetPropertiesRule]{"+ digester.match +
                    "}Setting property '" + name + "' to '" +
                    value + "' didnot find a matching property.");
        }
    }

}

1.3.4.3  SetNextRule

===============================================================

  这个方法的begin什么事情都没有做

1.3.5  Rule实现类的body方法

这部分方法没有进行任何处理

1.3.6  Rule实现类的end方法

=============================================================

SetNextRule:

SetNextRule[methodName=,paramType=org.apache.catalina.LifecycleListener]

       该方法是在标签元素结束的时候调用,获取当前对象以及其父级对象,然后根据方法名和参数类型调用调用父类方法,将当前实例注入作为其句柄属性。

public void end(String namespace, String name) throws Exception {
    Object child = digester.peek(0);
    Object parent = digester.peek(1);
    IntrospectionUtils.callMethod1(parent,methodName,
            child, paramType, digester.getClassLoader());

}

1.3.6.1  SetNextRule

===============================================================

  这个方法的end什么事情都没有做

1.3.6.2  ObjectCreateRule

==============================================================

  这个方法是将当前实例元素给移除栈顶

public void end(String namespace, String name) throws Exception {     Object top = digester.pop(); }

1.3.7  标签解析值

1.3.7.1  Server标签

===============================================================

[className=org.apache.catalina.core.StandardServer,attributeName=className]

   其创建了一个StandardServer对象

   此时stack栈中的集合:

   Catalina@1590

   StandardServer@1788

===============================================================     

SetNextRule[methodName=setServer,paramType=org.apache.catalina.Server]

  默认实现方法中begin方法什么也没有做

===============================================================     

SetPropertiesRule[]

   验证属性是否符合规范并注入相应的值,在这里给StandardServer注入了port=8005shutdown=SHUTDOWN

1.3.7.2  Server/Listener标签

begin

===============================================================

ObjectCreateRule[className=null, attributeName=className]

   attributeName=>org.apache.catalina.startup.VersionLoggerListener

  从而创建对应实例然后压入到stack栈:

  Catalina@1590

  StandardServer@1788

  VersionLoggerListener@1974

begin

===============================================================

SetPropertiesRule[]

   这里并没有什么属性设置到当前实例

begin

===============================================================

SetNextRule[methodName=addLifecycleListener,paramType=org.apache.catalina.LifecycleListener]

   默认实现方法中begin方法什么也没有做

end

===============================================================

SetNextRule[methodName=addLifecycleListener,paramType=org.apache.catalina.LifecycleListener]

              调用StandardServer@1788这个实例中的addLifecycleListener
(LifecycleListener lifecycleListener)方法,将VersionLoggerListener添加到其句柄lifecycle这个LifecycleSupport实例中去

end
===============================================================
SetPropertiesRule[]
这里什么也没有做

end
===============================================================
ObjectCreateRule[className=null, attributeName=className]

   将栈顶元素从stack中移除,这里移除的是VersionLoggerListener@1974

实例,所以此时栈顶元素

   Catalina@1590

   StandardServer@1788

相同原理依次加入

    AprLifecycleListener

    JreMemoryLeakPreventionListener 

   GlobalResourcesLifecycleListener

   ThreadLocalLeakPreventionListener

1.3.7.3  Server/Service标签

begin

=============================================================

ObjectCreateRule[className=org.apache.catalina.core.StandardService,attributeName=className]

  创建StandardService实例压入到栈中,此时栈中的元素:

  Catalina@1590

  StandardServer@1788

  StandardService

begin

============================================================

SetPropertiesRule[]
  设置属性值,这里将其name设置为catalina

begin
===============================================================
SetNextRule[methodName=addService,paramType=org.apache.catalina.Service]

1.3.7.4  Server/Service/Connector标签

1.3.7.5  Server/Service/Engine标签

1.3.7.6  Server/Service/Realm标签

1.3.7.7  Server/Service/Host标签

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

聊聊selenium的webdriver的超时参数

selenium-api-2.53.1-sources.jar!/org/openqa/selenium/WebDriver.java

592
来自专栏程序员宝库

JDK 源码中的一些“小技巧”

均摘选自JDK源码 1 i++ vs i-- String源码的第985行,equals方法中: while (n--!= 0) { if...

3335
来自专栏everhad

App开发:模拟服务器数据接口 - MockApi

为了方便app开发过程中,不受服务器接口的限制,便于客户端功能的快速测试,可以在客户端实现一个模拟服务器数据接口的MockApi模块。本篇文章就尝试为使用gra...

2477
来自专栏JadePeng的技术博客

Angular快速学习笔记(4) -- Observable与RxJS

2612
来自专栏芋道源码1024

分布式事务 TCC-Transaction 源码分析 —— Dubbo 支持

1. 概述 本文分享 Dubbo 支持。 TCC-Transaction 通过 Dubbo 隐式传参的功能,避免自己对业务代码的入侵。可能有同学不太理解为什么说...

6227
来自专栏Spark学习技巧

重要 : 优化flink的四种方式

flink这个框架在逐步变为流处理的主流。本文,我们将针对flink性能调优讲四种不同的方法。

852
来自专栏我和未来有约会

Silverlight 3.0 中的 Local Connection

现在很多的需求中需要一个插件实例和另一个实例进行通讯。在同一个页面中调用Html、js等来通讯,而这个往往有一些限制,需要专门的去设置一些权限。在Silverl...

2027
来自专栏企鹅FM

深入浅出Kotlin协程

协程(Coroutines)在Kotlin1.x版本还处于实验阶段,android平台可以使用如下方式引入:

4823
来自专栏Java技术栈

通往大神之路,Java面试题前200页。

基本概念 操作系统中 heap 和 stack 的区别 什么是基于注解的切面实现 什么是 对象/关系 映射集成模块 什么是 Java 的反射机制 什么是 AC...

3926
来自专栏Android 研究

Retrofit解析6之面向接口编程

1、解析思路 2、Call接口 3、CallAdapter接口 4、Callback接口 5、Converter接口 6、ExecutorCallAd...

972

扫码关注云+社区