文档章节

Tommcat源码学习(四)--Tomcat_7.0.70 server.xml文件的加载与解析

火龙战士
 火龙战士
发布于 2016/07/06 11:54
字数 4245
阅读 103
收藏 8

1、文件的加载

Bootstrap的load方法是加载Tomcat的server.xml的入口,load方法实际通过反射调用catalinaDaemon(类型为Catalina)的load方法:

/**
 * Load daemon.
 */
private void load(String[] arguments)
    throws Exception {

    // Call the load() method
    String methodName = "load";
    Object param[];
    Class<?> paramTypes[];
    if (arguments==null || arguments.length==0) {
        paramTypes = null;
        param = null;
    } else {
        paramTypes = new Class[1];
        paramTypes[0] = arguments.getClass();
        param = new Object[1];
        param[0] = arguments;
    }
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);//通过反射机制调用Catalina类的load方法。
    if (log.isDebugEnabled())
        log.debug("Calling startup class " + method);
    method.invoke(catalinaDaemon, param);

}

Catalina类的load方法:

 /*
 * Load using arguments
 */
public void load(String args[]) {

    try {
        if (arguments(args)) {
            load();//调用自身的load方法
        }
    } catch (Exception e) {
        e.printStackTrace(System.out);
    }
}

 /**
 * Start a new server instance.
 */
public void load() {

    long t1 = System.nanoTime();

    initDirs(); //用于对catalina.home和catalina.base的一些检查工作

    // Before digester - it may be needed

    initNaming();//给系统设置java.naming.factory.url.pkgs和java.naming.factory.initial

    // Create and execute our Digester
    Digester digester = createStartDigester();//实例化Digester

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try {
        try {
            file = configFile();//获取conf/server.xml配置文件
            inputStream = new FileInputStream(file);获取conf/server.xml配置文件输入流
            inputSource = new InputSource(file.toURI().toURL().toString());
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("catalina.configFail", file), e);
            }
        }
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream(getConfigFile());
                inputSource = new InputSource
                    (getClass().getClassLoader()
                     .getResource(getConfigFile()).toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                            getConfigFile()), e);
                }
            }
        }

        // This should be included in catalina.jar
        // Alternative: don't bother with xml, just create it manually.
        if( inputStream==null ) {
            try {
                inputStream = getClass().getClassLoader()
                        .getResourceAsStream("server-embed.xml");
                inputSource = new InputSource
                (getClass().getClassLoader()
                        .getResource("server-embed.xml").toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                            "server-embed.xml"), e);
                }
            }
        }


        if (inputStream == null || inputSource == null) {
            if  (file == null) {
                log.warn(sm.getString("catalina.configFail",
                        getConfigFile() + "] or [server-embed.xml]"));
            } else {
                log.warn(sm.getString("catalina.configFail",
                        file.getAbsolutePath()));
                if (file.exists() && !file.canRead()) {
                    log.warn("Permissions incorrect, read permission is not allowed on the file.");
                }
            }
            return;
        }

        try {
            inputSource.setByteStream(inputStream);//将FileInputStream封装为InputSource
            digester.push(this);
            digester.parse(inputSource);//调用Digester的parse方法进行解析
        } catch (SAXParseException spe) {
            log.warn("Catalina.start using " + getConfigFile() + ": " +
                    spe.getMessage());
            return;
        } catch (Exception e) {
            log.warn("Catalina.start using " + getConfigFile() + ": " , e);
            return;
        }
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                // Ignore
            }
        }
    }

    getServer().setCatalina(this);

    // Stream redirection
    initStreams();//initStreams对输出流、错误流重定向

    // Start the new server
    try {
        getServer().init();//初始化server
    } catch (LifecycleException e) {
        if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new java.lang.Error(e);
        } else {
            log.error("Catalina.start", e);
        }

    }

    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
        log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
    }

}

下面开始分析整个过程:

1.1、initDirs()方法用于对catalina.home和catalina.base的一些检查工作

protected void initDirs() {

    String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP);
    if (catalinaHome == null) {
        // Backwards compatibility patch for J2EE RI 1.3
        String j2eeHome = System.getProperty("com.sun.enterprise.home");
        if (j2eeHome != null) {
            catalinaHome=System.getProperty("com.sun.enterprise.home");
        } else if (System.getProperty(Globals.CATALINA_BASE_PROP) != null) {
            catalinaHome = System.getProperty(Globals.CATALINA_BASE_PROP);
        }
    }
    // last resort - for minimal/embedded cases.
    if(catalinaHome==null) {
        catalinaHome=System.getProperty("user.dir");
    }
    if (catalinaHome != null) {
        File home = new File(catalinaHome);
        if (!home.isAbsolute()) {
            try {
                catalinaHome = home.getCanonicalPath();
            } catch (IOException e) {
                catalinaHome = home.getAbsolutePath();
            }
        }
        System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHome);
    }

    if (System.getProperty(Globals.CATALINA_BASE_PROP) == null) {
        System.setProperty(Globals.CATALINA_BASE_PROP,
                           catalinaHome);
    } else {
        String catalinaBase = System.getProperty(Globals.CATALINA_BASE_PROP);
        File base = new File(catalinaBase);
        if (!base.isAbsolute()) {
            try {
                catalinaBase = base.getCanonicalPath();
            } catch (IOException e) {
                catalinaBase = base.getAbsolutePath();
            }
        }
        System.setProperty(Globals.CATALINA_BASE_PROP, catalinaBase);
    }

    String temp = System.getProperty("java.io.tmpdir");
    if (temp == null || (!(new File(temp)).exists())
            || (!(new File(temp)).isDirectory())) {
        log.error(sm.getString("embedded.notmp", temp));
    }

}

1.2、initNaming()方法给系统设置java.naming.factory.url.pkgs和java.naming.factory.initial

protected void initNaming() {
    // Setting additional variables
    if (!useNaming) {
        log.info( "Catalina naming disabled");
        System.setProperty("catalina.useNaming", "false");
    } else {
        System.setProperty("catalina.useNaming", "true");
        String value = "org.apache.naming";
        String oldValue =
            System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
        if (oldValue != null) {
            value = value + ":" + oldValue;
        }
        System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);
        if( log.isDebugEnabled() ) {
            log.debug("Setting naming prefix=" + value);
        }
        value = System.getProperty
            (javax.naming.Context.INITIAL_CONTEXT_FACTORY);
        if (value == null) {
            System.setProperty
                (javax.naming.Context.INITIAL_CONTEXT_FACTORY,
                 "org.apache.naming.java.javaURLContextFactory");
        } else {
            log.debug( "INITIAL_CONTEXT_FACTORY already set " + value );
        }
    }
}

在创建JNDI上下文时,使用Context.INITIAL _ CONTEXT _ FACTORY("java.naming.factory.initial")属性,来指定创建JNDI上下文的工厂类;Context.URL _ PKG _ PREFIXES("java.naming.factory.url.pkgs")用在查询url中包括scheme方法id时创建对应的JNDI上下文.

1.3、createStartDigester()创建并配置将要用来启动的Digester实例,并且设置一些列Rule,具体映射到server.xml

/**
 * Create and configure the Digester we will be using for startup.
 */
protected Digester createStartDigester() {
    long t1=System.currentTimeMillis();
    // Initialize the digester
    Digester digester = new Digester();
    digester.setValidating(false);
    digester.setRulesValidation(true);
    HashMap<Class<?>, List<String>> fakeAttributes =
        new HashMap<Class<?>, List<String>>();
    ArrayList<String> attrs = new ArrayList<String>();
    attrs.add("className");
    fakeAttributes.put(Object.class, attrs);
    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.NamingResources");
    digester.addSetProperties("Server/GlobalNamingResources");
    digester.addSetNext("Server/GlobalNamingResources",
                        "setGlobalNamingResources",
                        "org.apache.catalina.deploy.NamingResources");

    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"}));
    digester.addSetNext("Server/Service/Connector",
                        "addConnector",
                        "org.apache.catalina.connector.Connector");


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

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

}

从上面的代码可以看出:首先创建Digester,Digester继承了DefaultHandler,而DefaultHandler默认实现了ContentHander、DTDHander、ErrorHandler及EntityResolver 这4个接口,代码如下:

public class DefaultHandler implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler

通过源码可以发现DefaultHandler的所有实现都是空实现,所以解析还需要Digester。(具体分析后面在说)。

1.4、configFile()获取配置文件conf/server.xml,并使用FileInputStream获取conf/server.xml配置文件输入流

/**
 * Return a File object representing our configuration file.
 */
protected File configFile() {

    File file = new File(configFile);
    if (!file.isAbsolute()) {
        file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), configFile);
    }
    return (file);

}

1.5、将FileInputStream封装为InputSource,并且调用Digester的parse方法进行解析

inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);

1.6、initStreams()对输出流、错误流重定向

  protected void initStreams() {
    // Replace System.out and System.err with a custom PrintStream
    System.setOut(new SystemLogHandler(System.out));
    System.setErr(new SystemLogHandler(System.err));
}	

1.7、初始化server

// Start the new server
    try {
        getServer().init();
    } catch (LifecycleException e) {
        if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new java.lang.Error(e);
        } else {
            log.error("Catalina.start", e);
        }

    }

2、文件的解析

当加载server.xml配置文件到内存后,开始对XML文件中的内容进行解析,主要包含两个步骤:

  1. 构造server.xml的规则,这些规则即可以用于构造Tomcat内部的容器(如StandardServer,StandardService等),也可以对server.xml进行合法性检查。如果server.xml不符合Tomcat内置的规则,在解析时将抛出异常,进而导致Tomcat无法启动。
  2. 使用SAX解析server.xml,边解析边应用规则,最终使用server.xml中的配置构建好Tomcat所需的各种容器。

Tomcat将server.xml文件中的所有元素上的属性都抽象为Rule,以Server元素为例,在内存中对应Server实例,Server实例的属性值就来自于Server元素的属性值。通过对规则(Rule)的应用,最终改变Server实例的属性值。Rule是一个抽象类,其中定义了以下方法:

  • getDigester:获取Digester实例;
  • setDigester:设置Digester实例;
  • getNamespaceURI:获取Rule所在的相对命名空间URI;
  • setNamespaceURI:设置Rule所在的相对命名空间URI;
  • begin(String namespace, String name, Attributes attributes):此方法在遇到一个匹配的XML元素的开头时被调用,如<Server>;
  • body(String namespace, String name, String text):在遇到匹配XML元素的body时,此方法被调用,如进入标签内部时;
  • end(String namespace, String name):此方法在遇到一个匹配的XML元素的末尾时被调用。如:< /Server>;

Rule目前有很多实现类,如:NodeCreateRule、AbsoluteOrderingRule、CallParamRule、ConnectorCreateRule等。下图展示了Rule的部分实现类:

Tomcat使用SAX(Simple API for XML)解析XML:

SAX解析XML采用的是从上而下的基于事件驱动的解析方式,在解析过程中会视情况自动调用ContentHandler接口中的startDocument()、startElement()、characters()、endElement()、endDocument()等相关的方法。

由编译执行的结果分析:

  • startDocument()方法只会在文档开始解析的时候被调用,每次解析只会调用一次。
  • startElement()方法每次在开始解析一个元素,即遇到元素标签开始的时候都会调用。
  • characters()方法也是在每次解析到元素标签携带的内容时都会调用,即使该元素标签的内容为空或换行。而且如果元素内嵌套元素,在父元素结束标签前, characters()方法会再次被调用,此处需要注意。
  • endElement()方法每次在结束解析一个元素,即遇到元素标签结束的时候都会调用。
  • endDocument()方法只会在文档解析结束的时候被调用,每次解析只会调用一次。

使用SAX解析XML的好处:

  • SAX 不用解析完整个文档
  • 相比于 DOM 而言 SAX 是一种速度更快,更有效,占用内存更少的解析 XML 文件的方法
  • 逐行扫描,可以做到边扫描边解析,因此 SAX 可以在解析文档的任意时刻停止解析

由于SAX是基于事件驱动的,不用解析完整个文档,在按内容顺序解析文档过程中, SAX 会判断当前读到的字符是否符合 XML 文件语法中的某部分。如果符合某部分,则会触发事件。所谓触发事件,就是调用一些回调方法。在用 SAX 解析 xml 文档时候,在读取到文档开始和结束标签时候就会回调一个事件,在读取到其他节点与内容时候也会回调一个事件。在 SAX 接口中,事件源是 org.xml.sax 包中的 XMLReader ,它通过 parser() 方法来解析 XML 文档,并产生事件。事件处理器是 org.xml.sax 包中 ContentHander 、 DTDHander 、 ErrorHandler ,以及 EntityResolver 这4个接口:

  1. ContentHander ( XML 文档的开始与结束)setContentHandler(ContentHandler h)
  2. DTDHander ( 处理 DTD 解析) setDTDHandler(DTDHandler h)
  3. ErrorHandler ( 处理 XML 时产生的错误)setErrorHandler(ErrorHandler h)
  4. EntityResolver (处理外部实体) setEntityResolver(EntityResolver e)

回调方法一般都定义在ContentHandler接口中,上面已经对这些回调方法的加载顺序已经说了就不在介绍了。

使用 SAX 解析 XML 文件一般有以下五个步骤:

  1. 创建一个 SAXParserFactory 对象;
  2. 调用 SAXParserFactory 中的 newSAXParser 方法创建一个 SAXParser 对象;
  3. 然后在调用 SAXParser 中的 getXMLReader 方法获取一个 XMLReader 对象;
  4. 实例化一个 DefaultHandler 对象;
  5. 连接事件源对象 XMLReader 到事件处理类 DefaultHandler 中;
  6. 调用 XMLReader 的 parse 方法从输入源中获取到的 xml 数据;
  7. 通过 DefaultHandler 返回我们需要的数据集合。

我们通过源码可以发现DefaultHandler的所有实现都是空实现,所以解析还需要Digester自身,代码如下:

@Override
public void startDocument() throws SAXException {

    if (saxLog.isDebugEnabled()) {
        saxLog.debug("startDocument()");
    }

    // ensure that the digester is properly configured, as 
    // the digester could be used as a SAX ContentHandler
    // rather than via the parse() methods.
    configure();
}

@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 + ")");
    }
    
    // Parse system properties
    list = updateAttributes(list);
    
    // Save the body text accumulated for our surrounding element
    bodyTexts.push(bodyText);
    bodyText = new StringBuilder();

    // the actual element name is either in localName or qName, depending 
    // on whether the parser is namespace aware
    String name = localName;
    if ((name == null) || (name.length() < 1)) {
        name = qName;
    }

    // Compute the current matching rule
    StringBuilder sb = new StringBuilder(match);
    if (match.length() > 0) {
        sb.append('/');
    }
    sb.append(name);
    match = sb.toString();
    if (debug) {
        log.debug("  New match='" + match + "'");
    }

    // Fire "begin" events for all relevant rules
    List<Rule> rules = getRules().match(namespaceURI, match);
    matches.push(rules);
    if ((rules != null) && (rules.size() > 0)) {
        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 endDocument() throws SAXException {

    if (saxLog.isDebugEnabled()) {
        if (getCount() > 1) {
            saxLog.debug("endDocument():  " + getCount() +
                         " elements left");
        } else {
            saxLog.debug("endDocument()");
        }
    }

    while (getCount() > 1) {
        pop();
    }

    // Fire "finish" events for all defined rules
    Iterator<Rule> rules = getRules().rules().iterator();
    while (rules.hasNext()) {
        Rule rule = rules.next();
        try {
            rule.finish();
        } catch (Exception e) {
            log.error("Finish event threw exception", e);
            throw createSAXException(e);
        } catch (Error e) {
            log.error("Finish event threw error", e);
            throw e;
        }
    }

    // Perform final cleanup
    clear();

}

@Override
public void endElement(String namespaceURI, String localName,
                       String qName) throws SAXException {

    boolean debug = log.isDebugEnabled();

    if (debug) {
        if (saxLog.isDebugEnabled()) {
            saxLog.debug("endElement(" + namespaceURI + "," + localName +
                    "," + qName + ")");
        }
        log.debug("  match='" + match + "'");
        log.debug("  bodyText='" + bodyText + "'");
    }

    // Parse system properties
    bodyText = updateBodyText(bodyText);

    // the actual element name is either in localName or qName, depending 
    // on whether the parser is namespace aware
    String name = localName;
    if ((name == null) || (name.length() < 1)) {
        name = qName;
    }

    // Fire "body" events for all relevant rules
    List<Rule> rules = matches.pop();
    if ((rules != null) && (rules.size() > 0)) {
        String bodyText = this.bodyText.toString();
        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 + "'.");
        }
    }

    // Recover the body text from the surrounding element
    bodyText = bodyTexts.pop();

    // Fire "end" events for all relevant rules in reverse order
    if (rules != null) {
        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 = "";
    }

}

当我们创建好Digester后,会调用addObjectCreate、addSetProperties、addSetNext方法陆续添加很多Rule,这些方法的实现如代码:

 public void addObjectCreate(String pattern, String className,
                            String attributeName) {

    addRule(pattern,
            new ObjectCreateRule(className, attributeName));

}

 public void addSetProperties(String pattern) {

    addRule(pattern,
            new SetPropertiesRule());

}

public void addSetNext(String pattern, String methodName,
                       String paramType) {

    addRule(pattern,
            new SetNextRule(methodName, paramType));

}

这三个方法分别创建ObjectCreateRule、SetPropertiesRule及SetNextRule,为了便于理解,我们举例说明(Server标签):

digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
digester.addSetProperties("Server");
digester.addSetNext("Server","setServer", "org.apache.catalina.Server");

我们知道最终会创建ObjectCreateRule、SetPropertiesRule及SetNextRule,并且调用addRule方法。

public void addRule(String pattern, Rule rule) {

    rule.setDigester(this);
    getRules().add(pattern, rule);

}

从代码可以看出,addRule方法首先调用getRules方法获取RulesBase,然后调用RulesBase的add方法。代码如下:

//getRules()获取RulesBase
public Rules getRules() {

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

}

//RulesBase的add方法
@Override
public void add(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);
    }
    
    
    List<Rule> list = cache.get(pattern);
    if (list == null) {
        list = new ArrayList<Rule>();
        cache.put(pattern, list);
    }
    list.add(rule);
    rules.add(rule);
    if (this.digester != null) {
        rule.setDigester(this.digester);
    }
    if (this.namespaceURI != null) {
        rule.setNamespaceURI(this.namespaceURI);
    }

}

其中,cache的数据结构为HashMap<String,List<Rule>>,每个键值维护一个List<Rule>,由此可知,对Server标签来说,对应的Rule列表为ObjectCreateRule、SetPropertiesRule及SetNextRule。

Digester解析XML的入口是其parse方法,其处理步骤如下:

1.创建XMLReader ;

2.使用XMLReader解析XML。

public Object parse(InputSource input) throws IOException, SAXException {

    configure();
    getXMLReader().parse(input);
    return (root);

}
//configure()
protected void configure() {

    // Do not configure more than once
    if (configured) {
        return;
    }

    log = LogFactory.getLog("org.apache.tomcat.util.digester.Digester");
    saxLog = LogFactory.getLog("org.apache.tomcat.util.digester.Digester.sax");

    // Perform lazy configuration as needed
    initialize(); // call hook method for subclasses that want to be initialized once only
    // Nothing else required by default

    // Set the configuration flag to avoid repeating
    configured = true;

}

getXMLReader方法调用getParser创建SAXParser ,然后调用SAXParser 的getXMLReader方法创建XMLReader.

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;
}

getParser方法调用getFactory方法创建SAXParserFactory,然后调用SAXParserFactory的newSAXParser方法创建SAXParser

public SAXParser getParser() {

    // Return the parser we already created (if any)
    if (parser != null) {
        return (parser);
    }

    // Create a new parser
    try {
        parser = getFactory().newSAXParser();
    } catch (Exception e) {
        log.error("Digester.getParser: ", e);
        return (null);
    }

    return (parser);

}

getFactory方法使用SAX的API生成SAXParserFactory实例.

public SAXParserFactory getFactory() throws SAXNotRecognizedException, SAXNotSupportedException,ParserConfigurationException {

    if (factory == null) {
        factory = SAXParserFactory.newInstance();

        factory.setNamespaceAware(namespaceAware);
        // Preserve xmlns attributes
        if (namespaceAware) {
            factory.setFeature(
                    "http://xml.org/sax/features/namespace-prefixes",
                    true);
        }

        factory.setValidating(validating);
        if (validating) {
            // Enable DTD validation
            factory.setFeature(
                    "http://xml.org/sax/features/validation",
                    true);
            // Enable schema validation
            factory.setFeature(
                    "http://apache.org/xml/features/validation/schema",
                    true);
        }
    }
    return (factory);

}

XMLReader解析XML时,会生成事件,回调Digester的startDocument方法,解析的第一个元素是Server,此时回调Digester的startElement方法,入参Attributes list即Server上的属性,如port、shutdown等,入参qName即为Server.

 @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 + ")");
    }
    
    // Parse system properties
    list = updateAttributes(list);
    
    // Save the body text accumulated for our surrounding element
    bodyTexts.push(bodyText);
    bodyText = new StringBuilder();

    // the actual element name is either in localName or qName, depending 
    // on whether the parser is namespace aware
    String name = localName;
    if ((name == null) || (name.length() < 1)) {
        name = qName;
    }

    // Compute the current matching rule
    StringBuilder sb = new StringBuilder(match);
    if (match.length() > 0) {
        sb.append('/');
    }
    sb.append(name);
    match = sb.toString();
    if (debug) {
        log.debug("  New match='" + match + "'");
    }

    // Fire "begin" events for all relevant rules
    List<Rule> rules = getRules().match(namespaceURI, match);
    matches.push(rules);
    if ((rules != null) && (rules.size() > 0)) {
        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 + "'.");
        }
    }

}

startElement方法的处理步骤如下:

1.match刚开始为空字符串,拼接Server后变为Server。

2.调用RulesBase的match方法,返回cache中按照键值Server匹配的ObjectCreateRule、SetPropertiesRule及SetNextRule。

3.循环列表依次遍历ObjectCreateRule、SetPropertiesRule及SetNextRule,并调用它们的begin方法。

ObjectCreateRule的begin方法将生成Server的实例(默认为"org.apache.catalina.core.StandardServer",用户可以通过给Server标签指定className使用其它Server实现),最后将Server的实例压入Digester的栈中.

@Override
public void begin(String namespace, String name, Attributes attributes)
        throws Exception {

    // Identify the name of the class to instantiate
    String realClassName = className;
    if (attributeName != null) {
        String value = attributes.getValue(attributeName);
        if (value != null) {
            realClassName = value;
        }
    }
    if (digester.log.isDebugEnabled()) {
        digester.log.debug("[ObjectCreateRule]{" + digester.match +
                "}New " + realClassName);
    }

    if (realClassName == null) {
        throw new NullPointerException("No class name specified for " +
                namespace + " " + name);
    }

    // Instantiate the new object and push it on the context stack
    Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
    Object instance = clazz.newInstance();
    digester.push(instance);
}

SetPropertiesRule的begin方法首先将刚才压入栈中的Server实例出栈,然后给Server实例设置各个属性值,如port、shutdown等:

@Override
public void begin(String namespace, String theName, Attributes attributes)
        throws Exception {
    
    // Populate the corresponding properties of the top object
    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");
        }
    }
    
    // set up variables for custom names mappings
    int attNamesLength = 0;
    if (attributeNames != null) {
        attNamesLength = attributeNames.length;
    }
    int propNamesLength = 0;
    if (propertyNames != null) {
        propNamesLength = propertyNames.length;
    }
    
    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);
        
        // we'll now check for custom mappings
        for (int n = 0; n<attNamesLength; n++) {
            if (name.equals(attributeNames[n])) {
                if (n < propNamesLength) {
                    // set this to value from list
                    name = propertyNames[n];
                
                } else {
                    // set name to null
                    // we'll check for this later
                    name = null;
                }
                break;
            }
        } 
        
        if (digester.log.isDebugEnabled()) {
            digester.log.debug("[SetPropertiesRule]{" + digester.match +
                    "} Setting property '" + name + "' to '" +
                    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.");
        }
    }

}

SetNextRule的begin不做什么动作。当遇到Server的结束标签时,还会依次调用ObjectCreateRule、SetPropertiesRule及SetNextRule的end方法,不再赘述。所有元素的解析都与Server标签同理,最终将server.xml文件中设置的元素及其属性值,构造出tomcat中的容器,如:Server、Service、Connector等.

© 著作权归作者所有

火龙战士

火龙战士

粉丝 119
博文 136
码字总数 100857
作品 0
北京
后端工程师
私信 提问
Tommcat源码学习(三)--Tomcat_7.0.70停止服务过程分析

Tomcat关闭命令(Linux下,大部分生产环境都是部署在Linux系统下): 执行这个命令之后,tomcat会为我们做了哪些操作呢?下面就来简单分析下。 shutdown.sh代码清单如下: Better OS/400 dete...

火龙战士
2016/07/05
59
0
Tomcat源码学习(二)--Tomcat_7.0.70 启动分析

项目build成功后,刷新整个项目,会发现多出一个output目录: 为了让应用跑起来,可以检查一下outputbuildconf下是否已经有配置文件,这些文件实际是从项目根路径conf目录下拷贝过来的。 找到...

火龙战士
2016/07/01
162
0
Java Web开发入门 - 第3章 Tomcat

Tomcat安装与运行 Web服务器完成底层的网络处理,包括HTTP协议报文格式的编解码、管理具体web请求处理线程等操作。 Tomcat目前最流行最常见的基于Java的web应用服务器软件。 Tomcat Apache ...

抢小孩糖吃
2016/08/19
174
0
一台CentOS主机上运行多个Tomcat7的配置

环境: CentOS 6.5 x64 JDK8 apache-tomcat-7.0.70 1、规划并配置端口: 2、下载tomcat并解压,清空webapps下内容,新建ROOT目录,然后修改server.xml内容。 在大概126行下,添加应用的配置:...

leizhimin
2016/07/14
0
0
tomcat6.0.x启动过程-tomcat 6.x 源码阅读

2013-09-07 周末来啊,宅的日子又到了...我们知道tomcat的基本是容器,通过容器来完成servlet容器的设计。从上一篇blog中看到了tomcat的结构框架图,图中容器嵌套,组件组合,tomcat如何设置...

douglaswei
2013/09/07
395
2

没有更多内容

加载失败,请刷新页面

加载更多

哪些情况下适合使用云服务器?

我们一直在说云服务器价格适中,具备弹性扩展机制,适合部署中小规模的网站或应用。那么云服务器到底适用于哪些情况呢?如果您需要经常原始计算能力,那么使用独立服务器就能满足需求,因为他...

云漫网络Ruan
今天
9
0
Java 中的 String 有没有长度限制

转载: https://juejin.im/post/5d53653f5188257315539f9a String是Java中很重要的一个数据类型,除了基本数据类型以外,String是被使用的最广泛的了,但是,关于String,其实还是有很多东西...

低至一折起
今天
17
0
OpenStack 简介和几种安装方式总结

OpenStack :是一个由NASA和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenSta...

小海bug
昨天
11
0
DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
昨天
9
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部