Tomcat8源码笔记(三)Catalina加载过程

之前介绍过 Catalina加载过程是Bootstrap的load调用的  Tomcat8源码笔记(二)Bootstrap启动

 

按照Catalina的load过程,大致如下: 接下来一步步分析加载过程

image

 

一.initDirs

从系统环境变量、VM参数中读取java.io.tmpdir, 并校验文件夹合法性; 未指定java.io.tmpdir,会抛出异常,所以我们启动时指定VM参数:image

 

-Djava.io.tmpdir=E:/Tomcat_Source_Code/apache-tomcat-8.0.53-src/catalina-home/temp

 

二.initNaming

initNaming设置一些额外的环境变量,在创建Digester时可能会用到.

image

 

 

三.createStartDigester

createStartDigester作用: 实例化Digester,设置validating标志位false,rulesValidating标志位true,fakeAttributes为{Object.class=[className]},useContextClassLoader标志位true,以上几种属性标志位是为了进行解析server.xml而设置的 。  再接着添加了一大段的 addObjectCreate 、addSetProperties、addSetNext, 这些是用来设置解析server.xml的规则,Tomcat的server.xml我们没见过xsd、DTD来约束文档书写规则吧,但是也不能不按照顺序随意些,比如我们不能将Servers标签写到Service标签里面吧。下面会具体分析添加规则部分。

image

 

贴上一份Tomcat8中默认的Server.xml内容,虽然平时部署到Tomcat也没怎么仔细关注过它,可能关注也只是关注到Tomcat端口而已、或者简单配置虚拟图片服务器。现在仔细看这份xml文档,与常见的spring、mybatis的文档不同,前面没有声明DTD或XSD,所以Tomcat要自己定义规则、自己按照规则来解析这样一份XML。

image

 

 

四.Rule抽象类

Rule抽象类的定义如下,省略了setter/getter方法,可以看出来Rule两个属性:关联的Digester以及命名空间namespaceURI,此外的方法就是begin、body、end、finish,这就是一个Rule规则实例化的生命周期,就是按顺序解析XML,按顺序调用begin、body、end、finish方法就能实例化对象;image

 

Rule具体的实现类下面分析,代码中Digester又是addObjectCreate 、addSetProperties、addSetNext一大串,先来addObjectCreate:

添加ObjectCreate时候,会先将Rule和Digester关联,然后每个规则加入到Digester的rules属性中,rules属性为Rules接口,默认采用RuleBase作为实现类。image

 

Rules和RuleBase

 

image

 

RuleBase的add(String,Rule)方法:

rules属性Rule的集合,目的是有序地保存最开始注册的Rule规则;cache属性,按照pattern来保存,目的是比如Server组件,创建ObjectCreate、属性赋值setPropertiesRule、设置调用方法SetNextRule三个属性规则,通过pattern 字符串就能将同一组件的不同规则保存在一起.  而pattern中 / 用来分隔标签下的子标签,比如Server下的Service,Server/Service.

 image

 

 

五.configFile

configFile默认为conf/server.xml,Bootstrap的catalinaBase之前Bootstrao启动时候静态代码块赋的值,从系统变量或VM参数中读取catalina.base;简而言之,就是读取catalina.base/conf/server.xml文件.

image

 

 

六.解析server.xml

inputStream就是上面server.xml的FileInputStream ,赋给InputSource实例.  push以及parse方法下面记录.

image

 

六.一  Digester的push方法

Digester实例将Catalina实例存入stack中,并且Digester的root属性也为Catalina实例,这里的ArrayStack是Tomcat自己实现的类,继承自ArrayList。 这一步将Catalina放入Digester的stack集合相当于第一位置,下标为0的位置,很关键!

image

 

六.二    Digester的parse方法, 解析server.xml的地方

configure是初始化打印日志log ; getXmlReader深入发现就是调用SAXParserFactory.newInstance().newSAXParser(),采用SAX的方式解析server.xml,并且设置内容解析器为this对象,也就是Digester,Digester重写了解析的规则。

image

 

很想贴一张SAXParser解析流程图,找了半天没搜到,我简单介绍下,SAX解析过程中DefaultHandler类,Digester就继承了DefaultHandler2,当然也继承了DefaultHandler。这个接口呢,startDocument开始解析xml时执行,startElement开始解析元素时执行,characters解析元素内容,endElement结束元素解析,然后循环往复解析,直到解析完成之后endDocument . SAX解析xml优点内存占用小,解析速度快,因为一边读取xml一边解析。Digester类的startDocument就没必要看了,直接从startElement开始记录.

 

startElement解析过程

server.xml中元素namespaceURI都为空,match解析到Server就是Server,解析到Server下Service就是Server/Service,与之间加入到Digester的pattern是一致的,这里就按照这个规则找到Rule的集合,加入到matches中,并且遍历Rule集合分别调用begin方法,比如以Server元素为例。

image

 

Server标签解析:

Server一共有三个Rule,分别是ObjectCreateRule、SetPropertiesRule、SetNextRule

此时ObjectCreateRule的className为org.apache.catalina.core.StandardServer,attributeName为className。 尝试从xml解析的attributes中找className,没有的情况下就用前面的className,使用当前线程的类加载器加载StandardServer,并且使用空参构造器实例化一个StandardServer,具体的StandardServer实例化下章记录,实例化完成的server加入到digester的stack,就是前面说的那个ArrayList子类中,第一位是Catalina,所以StandardServer是第二位.

image

 

SetPropertiesRule

digester peek方法返回栈首,深入查看发现就是其stack中最后一位元素,即上面的StandardServer;之后遍历attributes,可以理解为键值对集合,如果键是”className“,isFakeAttribute返回true,后面设置属性的方法就不会执行;如果键不是“className“,就会给StandardServer设置属性,IntrospectionUtils.setProperty可以理解为给对象属性赋值的方法。  比如常见的 <Server port="8005" shutdown="SHUTDOWN">  ,这个xml解析之后attributes就是{port=8005,shutdown=SHUTDOWN},给对象属性赋值方法,也就是遍历对象的方法尝试寻找setPort/setShutdown,然后反射调用。

image

 

SetNextRule的begin方法没有做任何处理,跳过;另外server.xml中元素都没有body内容,characters我们也跳过;下面SAX解析发现又会跳进入到startElement中,因为Server元素没有结束,还有子元素要解析。

image

 

下面继续以一个Listener元素进行分析,仍然是startElement,之前match=”Server”,还没结束,现在match变成了“Server/Listener”

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

 

之前向Digester添加解析规则时,添加了这样的规则:

image

所以现在通过Server/Listener同样可以得到三个Rule,和Server一样解析,调用空参构造器初始化第一个VersionLoggerListener,设置属性(虽然没有属性值要设置,因为className不在设置属性范围内),setNextRule(虽然这个begin什么也没做), 可以进入endElement分析,第一个endElement是Listener元素的:

 

endElement伪代码如下: matches也是ArrayStack类型的,pop返回ArrayList集合的最后一个元素,也就是Listener的List<Rule>,遍历Rule集合,分别调用其body方法;因为Tomcat的server.xml大部分或者几乎就没有元素体有内容的元素,所以Rule实现类的body方法都是空实现;  逆序遍历Rule集合,分别调用Rule的end方法 ; 最后结束一个元素,就将match的 / 去掉一重,保证下次解析正确性;

image

 

SetNextRule的end方法记录:

peek(0)返回digester中最后添加的一个元素,peek(1)返回倒数第二个添加的元素,分别是VersionLoggerListener以及StandardServer,反射调用peek(1)也就是StandardServer的addLifecycleListener,将peek(0)添加到StandardServer的监听器集合。 

image

 

ObjectCreateRule的end方法

将当前正在创建的元素从digester中移除,ObjectCreateRule所以是最后执行的,start的时候顺序调用Rule集合,end时候逆序调用Rule集合;

image

 

上述就是Server元素所有子元素的解析流程,循环往复,直到最后才解析Server元素的endDocument方法,而前面分析得到Rule集合的end方法,只需要关注SetNextRule和ObjectCreateRule的end方法即可。到了Server时候Digester的stack中元素如下,[Catalina,StandardServer],所以按照分析会调用Catalina的setServer将解析完成的StandardServer赋给Catalina ; 而StandardServer的ObjectCreateRule会将 StandardServer从Digester移除; 加载过程中另外解析的xml,因为暂时不知道用途,这里就不记录了。

 

七. Catalina加载过程后续步骤

之前将StandardServer赋给了Catalina,现在也将Catalina赋给StandardServer,双向引用;之后给StandardServer设置catalina.home和catalina.base;初始化流,主要是控制台输出流改变,再之后调用StandardServer的init方法初始化容器!StandardServer初始化工作比较复杂,留作下篇博客记录。

image

 

简单按照Tomcat的server.xml解析规则,自定义了一个测试类,完成解析XML,目的是查看解析的效果:对Tomcat各个组件有个直观的了解。

import org.apache.catalina.*;
import org.apache.catalina.Server;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.deploy.NamingResourcesImpl;
import org.apache.catalina.startup.*;
import org.apache.tomcat.util.digester.ArrayStack;
import org.apache.tomcat.util.digester.Digester;
import org.apache.tomcat.util.digester.Rule;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.ext.DefaultHandler2;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

public class ParserTest {
    private String match="";
    private ArrayStack<List<Rule>> stack=new ArrayStack();

    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        ParserTest test = new ParserTest();
        test.test();
    }

    public void test() throws IOException, ParserConfigurationException, SAXException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        InputSource inputSource = new InputSource();
        inputSource.setByteStream(new FileInputStream("E:\\Tomcat_Source_Code\\apache-tomcat-8.0.53-src\\conf\\server.xml"));
        Digester digester=new Digester();
        addRule(digester);
        digester.push(new Catalina());
        factory.newSAXParser().parse(inputSource,new DefaultHandler2(){
            @Override
            public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException {
                String name = localName;
                if ((name == null) || (name.length() < 1)) {
                    name = qName;
                }
                if (match.length()>0) match+="/";
                match+=name;
                List<Rule> rules = digester.getRules().match(uri, ParserTest.this.match);
                stack.push(rules);
                if(rules!=null && rules.size()>0){
                    for(int i=0;i<rules.size();i++){
                        Rule rule = rules.get(i);
                        try {
                            rule.begin(uri,name,attributes);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            @Override
            public void endElement(final String uri, final String localName, final String qName) throws SAXException {
                String name = localName;
                if ((name == null) || (name.length() < 1)) {
                    name = qName;
                }
                List<Rule> rules = stack.pop();
                if(rules!=null && rules.size()>0){
                    for(int i=0;i<rules.size();i++){
                        Rule rule = rules.get(i);
                        try {
                            rule.body(uri,name,"");
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }

                if(rules!=null){
                    for(int j=rules.size()-1;j>=0;j--){
                        Rule rule = rules.get(j);
                        try {
                            rule.end(uri,name);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
                if(match.lastIndexOf("/")>0){
                    match=match.substring(0,match.lastIndexOf("/"));
                }else{
                    match="";
                }
            }

            @Override
            public void characters(final char[] ch, final int start, final int length) throws SAXException {
                super.characters(ch, start, length);
            }

            @Override
            public void endDocument() throws SAXException {
                if(stack.size()>0){
                    stack.pop();
                }
                Iterator<Rule> iterator = digester.getRules().rules().iterator();
                while(iterator.hasNext()){
                    Rule rule = iterator.next();
                    try {
                        rule.finish();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                match="";
                stack.clear();
            }
        });
        System.out.println("解析XML完毕:");
        Catalina catalina = (Catalina) digester.getRoot();
        System.out.println("Cataline信息:"+catalina);
        Server server = catalina.getServer();
        System.out.println("Server信息:"+server);
        System.out.printf("Server端口:%s,SHUTDOWN:%s\r\n",server.getPort(),server.getShutdown());
        NamingResourcesImpl globalNamingResources = server.getGlobalNamingResources();
        System.out.println("GlobalNamingResources信息:"+globalNamingResources);
        LifecycleListener[] listeners = server.findLifecycleListeners();
        for (LifecycleListener listener:listeners) {
            System.out.println("Listener信息:"+listener);
        }
        Service[] services = server.findServices();
        for (Service service:services) {
            System.out.println("Service信息:"+service.getName());
            Connector[] connectors = service.findConnectors();
            for(Connector connector:connectors){
                System.out.println("Connector信息:"+connector.getProtocol());
            }
            Container container = service.getContainer();
            System.out.println("Container容器信息:"+container.getName());
            Container[] children = container.findChildren();
            int i=1;
            for(Container child:children){
                System.out.println(container.getName()+"子容器"+(i++)+"信息:"+child.getName());
                if(child instanceof StandardHost){
                    StandardHost host= (StandardHost) child;
                    System.out.printf("APP_BASE: %s, UnpackWARS: %s, Auto_Deploy:%s\r\n" ,host.getAppBase(),host.isUnpackWARs(),host.getAutoDeploy());
                    Valve[] valves = host.getPipeline().getValves();
                    for(Valve valve:valves){
                        System.out.println(valve.getClass());
                    }
                }
            }
        }
    }

    public static void addRule(Digester digester){
        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);

        // Configure the actions we will be using
        digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");  //初始化的Server对象StandardServer
        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"}));
        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/"));
        digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));

        // When the 'engine' is found, set the parentClassLoader.
        digester.addRule("Server/Service/Engine",new SetParentClassLoaderRule(Thread.currentThread().getContextClassLoader()));
    }

    public static class SetParentClassLoaderRule extends Rule {

        public SetParentClassLoaderRule(ClassLoader parentClassLoader) {

            this.parentClassLoader = parentClassLoader;

        }

        ClassLoader parentClassLoader = null;

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

            if (digester.getLogger().isDebugEnabled()) {
                digester.getLogger().debug("Setting parent class loader");
            }

            Container top = (Container) digester.peek();
            top.setParentClassLoader(parentClassLoader);

        }
    }
}

 

查看解析效果: 也让我们大致对Tomca组件有个了解,Catalina持有Server,一个Server持有多个Service,Service组件持有多个Connector连接器,负责对外监听HTTP、AJP等等连接;Service组件只能有一个Container,通常是Engine,Engine容器中有Host容器,Host中就是Context容器;

image

 

九. 总结

Catalina加载的过程解析catalina.home下conf/server.xml,并完成实例化工作,并且调用了StandardServer init方法,总体流程是这样,具体细节后面再记录。

 

 

 

 

posted @ 2019-04-05 22:35  喜欢日向雏田一样的女子啊  阅读(985)  评论(0编辑  收藏  举报