专栏名称: 预流
敲代码的
目录
相关文章推荐
51好读  ›  专栏  ›  预流

Tomcat 7 启动分析(三)Digester 的使用

预流  · 掘金  ·  · 2018-01-28 05:27

正文

前一篇文章里最后看到 Bootstrap 的 main 方法最后会调用 org.apache.catalina.startup.Catalina 对象的 load 和 start 两个方法,那么就来看看这两个方法里面到底做了些什么。

load 方法:


     1	    /**
     2	     * Start a new server instance.
     3	     */
     4	    public void load() {
     5	
     6	        long t1 = System.nanoTime();
     7	
     8	        initDirs();
     9	
    10	        // Before digester - it may be needed
    11	
    12	        initNaming();
    13	
    14	        // Create and execute our Digester
    15	        Digester digester = createStartDigester();
    16	
    17	        InputSource inputSource = null;
    18	        InputStream inputStream = null;
    19	        File file = null;
    20	        try {
    21	            file = configFile();
    22	            inputStream = new FileInputStream(file);
    23	            inputSource = new InputSource(file.toURI().toURL().toString());
    24	        } catch (Exception e) {
    25	            if (log.isDebugEnabled()) {
    26	                log.debug(sm.getString("catalina.configFail", file), e);
    27	            }
    28	        }
    29	        if (inputStream == null) {
    30	            try {
    31	                inputStream = getClass().getClassLoader()
    32	                    .getResourceAsStream(getConfigFile());
    33	                inputSource = new InputSource
    34	                    (getClass().getClassLoader()
    35	                     .getResource(getConfigFile()).toString());
    36	            } catch (Exception e) {
    37	                if (log.isDebugEnabled()) {
    38	                    log.debug(sm.getString("catalina.configFail",
    39	                            getConfigFile()), e);
    40	                }
    41	            }
    42	        }
    43	
    44	        // This should be included in catalina.jar
    45	        // Alternative: don't bother with xml, just create it manually.
    46	        if( inputStream==null ) {
    47	            try {
    48	                inputStream = getClass().getClassLoader()
    49	                        .getResourceAsStream("server-embed.xml");
    50	                inputSource = new InputSource
    51	                (getClass().getClassLoader()
    52	                        .getResource("server-embed.xml").toString());
    53	            } catch (Exception e) {
    54	                if (log.isDebugEnabled()) {
    55	                    log.debug(sm.getString("catalina.configFail",
    56	                            "server-embed.xml"), e);
    57	                }
    58	            }
    59	        }
    60	
    61	
    62	        if (inputStream == null || inputSource == null) {
    63	            if  (file == null) {
    64	                log.warn(sm.getString("catalina.configFail",
    65	                        getConfigFile() + "] or [server-embed.xml]"));
    66	            } else {
    67	                log.warn(sm.getString("catalina.configFail",
    68	                        file.getAbsolutePath()));
    69	                if (file.exists() && !file.canRead()) {
    70	                    log.warn("Permissions incorrect, read permission is not allowed on the file.");
    71	                }
    72	            }
    73	            return;
    74	        }
    75	
    76	        try {
    77	            inputSource.setByteStream(inputStream);
    78	            digester.push(this);
    79	            digester.parse(inputSource);
    80	        } catch (SAXParseException spe) {
    81	            log.warn("Catalina.start using " + getConfigFile() + ": " +
    82	                    spe.getMessage());
    83	            return;
    84	        } catch (Exception e) {
    85	            log.warn("Catalina.start using " + getConfigFile() + ": " , e);
    86	            return;
    87	        } finally {
    88	            try {
    89	                inputStream.close();
    90	            } catch (IOException e) {
    91	                // Ignore
    92	            }
    93	        }
    94	
    95	        getServer().setCatalina(this);
    96	
    97	        // Stream redirection
    98	        initStreams();
    99	
   100	        // Start the new server
   101	        try {
   102	            getServer().init();
   103	        } catch (LifecycleException e) {
   104	            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
   105	                throw new java.lang.Error(e);
   106	            } else {
   107	                log.error("Catalina.start", e);
   108	            }
   109	
   110	        }
   111	
   112	        long t2 = System.nanoTime();
   113	        if(log.isInfoEnabled()) {
   114	            log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
   115	        }
   116	
   117	    }

这个 117 行的代码看起来东西挺多,把注释、异常抛出、记录日志、流关闭、非空判断这些放在一边就会发现实际上真正做事的就这么几行代码:


Digester digester = createStartDigester();  
inputSource.setByteStream(inputStream);  
digester.push(this);  
digester.parse(inputSource);  
getServer().setCatalina(this);  
getServer().init();  

做的事情就两个,一是创建一个 Digester 对象,将当前对象压入 Digester 里的对象栈顶,根据 inputSource 里设置的文件 xml 路径及所创建的 Digester 对象所包含的解析规则生成相应对象,并调用相应方法将对象之间关联起来。二是调用 Server 接口对象的 init 方法。

这里碰到了 Digester,有必要介绍一下 Digester 的一些基础知识。一般来说 Java 里解析 xml 文件有两种方式:一种是 Dom4J 之类将文件全部读取到内存中,在内存里构造一棵 Dom 树的方式来解析。一种是 SAX 的读取文件流,在流中碰到相应的xml节点触发相应的节点事件回调相应方法,基于事件的解析方式,优点是不需要先将文件全部读取到内存中。

Digester 本身是采用 SAX 的解析方式,在其上提供了一层包装,对于使用者更简便友好罢了。最早是在 struts 1 里面用的,后来独立出来成为 apache 的 Commons 下面的一个单独的子项目。Tomcat 里又把它又封装了一层,为了描述方便,直接拿 Tomcat 里的 Digester 建一个单独的 Digester 的例子来介绍。


     1	package org.study.digester;
     2	
     3	import java.io.IOException;
     4	import java.io.InputStream;
     5	import java.util.ArrayList;
     6	import java.util.HashMap;
     7	import java.util.List;
     8	
     9	import junit.framework.Assert;
    10	
    11	import org.apache.tomcat.util.digester.Digester;
    12	import org.xml.sax.InputSource;
    13	
    14	public class MyDigester {
    15	
    16	    private MyServer myServer;
    17	
    18	    public MyServer getMyServer() {
    19	        return myServer;
    20	    }
    21	
    22	    public void setMyServer(MyServer myServer) {
    23	        this.myServer = myServer;
    24	    }
    25	
    26	    private Digester createStartDigester() {
    27	        // 实例化一个Digester对象
    28	        Digester digester = new Digester();
    29	
    30	        // 设置为false表示解析xml时不需要进行DTD的规则校验
    31	        digester.setValidating(false);
    32	
    33	        // 是否进行节点设置规则校验,如果xml中相应节点没有设置解析规则会在控制台显示提示信息
    34	        digester.setRulesValidation(true);
    35	
    36	        // 将xml节点中的className作为假属性,不必调用默认的setter方法(一般的节点属性在解析时将会以属性值作为入参调用该节点相应对象的setter方法,而className属性的作用是提示解析器用该属性的值来实例化对象)
    37	        HashMap, List> fakeAttributes = new HashMap, List>();
    38	        ArrayList attrs = new ArrayList();
    39	        attrs.add("className");
    40	        fakeAttributes.put(Object.class, attrs);
    41	        digester.setFakeAttributes(fakeAttributes);
    42	
    43	        // addObjectCreate方法的意思是碰到xml文件中的Server节点则创建一个MyStandardServer对象
    44	        digester.addObjectCreate("Server",
    45	                "org.study.digester.MyStandardServer", "className");
    46	        // 根据Server节点中的属性信息调用相应属性的setter方法,以上面的xml文件为例则会调用setPort、setShutdown方法,入参分别是8005、SHUTDOWN
    47	        digester.addSetProperties("Server");
    48	        // 将Server节点对应的对象作为入参调用栈顶对象的setMyServer方法,这里的栈顶对象即下面的digester.push方法所设置的当前类的对象this,就是说调用MyDigester类的setMyServer方法
    49	        digester.addSetNext("Server", "setMyServer",
    50	                "org.study.digester.MyServer");
    51	
    52	        // 碰到xml的Server节点下的Listener节点时取className属性的值作为实例化类实例化一个对象
    53	        digester.addObjectCreate("Server/Listener", null, "className");
    54	        digester.addSetProperties("Server/Listener");
    55	        digester.addSetNext("Server/Listener", "addLifecycleListener",
    56	                "org.apache.catalina.LifecycleListener");
    57	
    58	        digester.addObjectCreate("Server/Service",
    59	                "org.study.digester.MyStandardService", "className");
    60	        digester.addSetProperties("Server/Service");
    61	        digester.addSetNext("Server/Service", "addMyService",
    62	                "org.study.digester.MyService");
    63	
    64	        digester.addObjectCreate("Server/Service/Listener", null, "className");
    65	        digester.addSetProperties("Server/Service/Listener");
    66	        digester.addSetNext("Server/Service/Listener", "addLifecycleListener",
    67	                "org.apache.catalina.LifecycleListener");
    68	        return digester;
    69	    }
    70	
    71	    public MyDigester() {
    72	        Digester digester = createStartDigester();
    73	
    74	        InputSource inputSource = null;
    75	        InputStream inputStream = null;
    76	        try {
    77	            String configFile = "myServer.xml";
    78	            inputStream = getClass().getClassLoader().getResourceAsStream(
    79	                    configFile);
    80	            inputSource = new InputSource(getClass().getClassLoader()
    81	                    .getResource(configFile).toString());
    82	
    83	            inputSource.setByteStream(inputStream);
    84	            digester.push(this);
    85	            digester.parse(inputSource);
    86	        } catch (Exception e) {
    87	            e.printStackTrace();
    88	        } finally {
    89	            try {
    90	                inputStream.close();
    91	            } catch (IOException e) {
    92	                // Ignore
    93	            }
    94	        }
    95	
    96	        getMyServer().setMyDigester(this);
    97	    }
    98	
    99	    public static void main(String[] agrs) {
   100	        MyDigester md = new MyDigester();
   101	        Assert.assertNotNull(md.getMyServer());
   102	    }
   103	}

上面是我自己写的一个拿 Tomcat 里的 Digester 的工具类解析 xml 文件的例子,关键方法的调用含义已经在注释中写明,解析的是项目源文件发布的根目录下的 myServer.xml 文件。

<?xml version='1.0' encoding='utf-8'?>

<Server port="8005" shutdown="SHUTDOWN">

	<Listener
		className="org.apache.catalina.core.JasperListener" />

	<Listener
		className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

	<Service name="Catalina">

		<Listener
			className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
	</Service>
</Server>

Digester 的使用一般来说有4步:

  1. 实例化一个 Digester 对象,并在对象里设置相应的节点解析规则。
  2. 设置要解析的文件作为输入源( InputSource ),这里 InputSource 与 SAX 里的一样,用以表示一个 xml 实体。
  3. 将当前对象压入栈。
  4. 调用 Digester 的 parse 方法解析 xml,生成相应的对象。

第 3 步中将当前对象压入栈中的作用是可以保存一个到 Digester 生成的一系列对象直接的引用,方便后续使用而已,所以不必是当前对象,只要有一个地方存放这个引用即可。

这里有必要说明的是很多文章里按照代码顺序来描述 Digester 的所谓栈模型来讲述 addSetNext 方法时的调用对象,实际上我的理解 addSetNext 方法具体哪个调用对象与XML文件里的节点树形结构相关,当前节点的父节点是哪个对象该对象就是调用对象。可以试验一下把这里的代码顺序打乱仍然可以按规则解析出来,createStartDigester 方法只是在定义解析规则,具体解析与 addObjectCreate、addSetProperties、addSetNext 这些方法的调用顺序无关。Digester 自己内部在解析 xml 的节点元素时增加了一个 rule 的概念,addObjectCreate、addSetProperties、addSetNext 这些方法内部实际上是在添加 rule,在最后解析 xml 时将会根据读取到的节点匹配相应节点路径下的 rule,调用内部的方法。关于 Tomcat 内的 Digester 的解析原理以后可以单独写篇文章分析一下。







请到「今天看啥」查看全文