• 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
    ",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
    " ,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方法,总体流程是这样,具体细节后面再记录。

     

     

  • 相关阅读:
    服务器响应状态码
    30
    29
    Java中参数始终是按值传递
    浅析 Java 中的继承和重写
    25
    super 关键字
    24
    Overriding
    23
  • 原文地址:https://www.cnblogs.com/lvbinbin2yujie/p/10660244.html
Copyright © 2020-2023  润新知