曹工說Tomcat2:自己擼一個簡易Tomcat Digester

三國夢迴發表於2019-07-02

一、前言

框架程式碼其實也沒那麼難,大家不要看著原始碼就害怕,現在去看 Tomcat 3.0的程式碼,保證還是看得懂一半,照著擼一遍基本上很多問題都能搞定了。這次我們就模擬 Tomcat 中的 Digester(xml解析工具)來仿寫一個相當簡易的版本。上一篇說了如何利用 sax 模型來解析 xml,但是,該程式還有相當多的優化空間。這一篇,我們一起將程式進行一些優化。之前的版本有什麼問題呢?請看:

 1     @Override
 2     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
 3         System.out.println("startElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one");
 4 
 5         if ("Coder".equals(qName)) {
 6 
 7             Coder coder = new Coder();
 8 
 9             setProperties(attributes,coder);
10 
11             stack.push(coder);
12         } else if ("Girl".equals(qName)) {
13 
14             Girl girl = new Girl();
15             setProperties(attributes, girl);
16 
17             Coder coder = (Coder) stack.peek();
18             coder.setGirl(girl);
19         }
20     }

 

上圖為當前xml handler的程式碼,注意第5-6行,我們這裡寫死了,當元素為 Coder 的時候,就生成 Coder類的物件。那要是,我現在不想生成 Coder 類了,就得修改這裡的程式。這樣還是太不靈活了,所以我們考慮將 這個提取到 xml 中來。

1 <?xml version='1.0' encoding='utf-8'?>
2 
3 <Coder name="xiaoming" sex="man" love="girl" class="com.coder.Coder">
4     <Girl class = "com.coder.Girl" name="Catalina" height="170" breast="C++" legLength="150" isPregnant="true" />
5 </Coder>

 

如上圖所示,我們將其型別的資訊提取到了 元素的class 屬性中,以後假設想換個型別,那就很簡單了,只要修改 class 屬性即可。

 

二、大體思路與具體實現

1、Tomcat原始碼中的實現思路

我們先擷取了 Tomcat 中的 server.xml 一句:

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

 Tomcat 原始碼中負責定義如何解析上面這句的程式碼如下:

1          //source:org.apache.catalina.startup.Catalina#createStartDigester
2  
3          digester.addObjectCreate("Server",
4                                   "org.apache.catalina.core.StandardServer",
5                                   "className");
6          digester.addSetProperties("Server");
7          digester.addSetNext("Server",
8                              "setServer",
9                              "org.apache.catalina.Server");

 

我簡單解釋下,這裡沒有進行真實解析,只是定義解析規則,真實的解析發生在Digester.parse()方法中,彼時會回撥這裡定義的規則。 第三行表示,新增一條"Server"元素的規則,型別為ObjectCreate,這條規則,在遇到 "Server" 元素時,獲取 className 屬性的值,如果有的話,即建立指定型別的物件,否則預設建立 org.apache.catalina. core.StandardServer 型別的物件,並儲存到 digester 物件的內部棧中; 第6行表示,新增一條 "Server" 元素的規則,型別為 SetAllPropertiesRule,這條規則,會從 digester 當前的棧中,取出棧頂物件,並利用反射,來將 xml 元素中的 attribute 設定到該物件中。

 

2、仿寫開始:自定義 rule 介面及實現

 

package com.coder.rule;

import org.xml.sax.Attributes;

public interface ParseRule {
    /**
     * 遇到xml元素的開始標記時,呼叫該方法。
     * @param attributes 元素中的屬性
     */
    void startElement(Attributes attributes);

    void body(String body);

    void endElement();
}

 

我們先定義了一個解析規則,規則中有三個方法,分別在遇到 xml元素 的開始標記、內容、結束標記時呼叫。接下來,我們再定義一個規則:

 1 package com.coder.rule;
 2 
 3 import com.coder.GirlFriendHandler;
 4 import com.coder.GirlFriendHandlerVersion2;
 5 import org.xml.sax.Attributes;
 6 
 7 /**
 8  * desc:
 9  *
10  * @author : caokunliang
11  * creat_date: 2019/7/1 0001
12  * creat_time: 11:20
13  **/
14 public class CreateObjectParseRule implements ParseRule {
15     private String attributeNameForObjectType;
16 
17     private ClassLoader loader;
18 
19     private GirlFriendHandlerVersion2 girlFriendHandler;
20 
21   
23     public CreateObjectParseRule(String attributeNameForObjectType, GirlFriendHandlerVersion2 girlFriendHandler) {
24         this.attributeNameForObjectType = attributeNameForObjectType;
25         this.girlFriendHandler = girlFriendHandler;
26         //預設使用當前執行緒類載入器
27         loader = Thread.currentThread().getContextClassLoader();
28     }
29 
30     @Override
31     public void startElement(Attributes attributes) {
32         String clazzStr = attributes.getValue(attributeNameForObjectType);
33         if (clazzStr == null) {
34             throw new RuntimeException("element must has attribute :" + attributeNameForObjectType);
35         }
36 
37         Class<?> clazz;
38         try {
39             clazz = loader.loadClass(clazzStr);
40         } catch (ClassNotFoundException e) {
41             e.printStackTrace();
42             throw new RuntimeException("class not found:" + clazzStr);
43         }
44 
45         Object o;
46         try {
47             o = clazz.newInstance();
48         } catch (InstantiationException | IllegalAccessException e) {
49             e.printStackTrace();
50             throw new RuntimeException("new instance failed.");
51         }
52 
53         girlFriendHandler.push(o);
54     }
55 
56     @Override
57     public void body(String body) {
58 
59     }
60 
61     @Override
62     public void endElement() {
63 
64     }
65 }

 

重點關注兩個方法,一個是構造器,構造器兩個引數,一個 attributeNameForObjectType 意思是要從xml元素的那個 屬性中獲取 物件型別,一個 girlFriendHandler 其實就是我們的解析器handler。

然後要關注的方法是,startElement。32行,根據構造器中的attributeNameForObjectType 獲取對應的物件型別,然後利用類載入器來載入該類,獲取到class後,利用反射生成物件,並壓入 handler的棧中。

 

接下來,我們介紹另一個 rule:

  1 package com.coder.rule;
  2 
  3 import com.coder.GirlFriendHandlerVersion2;
  4 import com.coder.TwoTuple;
  5 import org.xml.sax.Attributes;
  6 
  7 import java.lang.reflect.InvocationTargetException;
  8 import java.lang.reflect.Method;
  9 import java.util.ArrayList;
 10 import java.util.Arrays;
 11 import java.util.List;
 12 import java.util.Objects;
 13 
 14 public class SetPropertiesParseRule implements ParseRule {
 15     private GirlFriendHandlerVersion2 girlFriendHandler;
 16 
 17     public SetPropertiesParseRule(GirlFriendHandlerVersion2 girlFriendHandler) {
 18         this.girlFriendHandler = girlFriendHandler;
 19     }
 20 
 21     @Override
 22     public void startElement(Attributes attributes) {
 23         Object object = girlFriendHandler.peek();
 24 
 25         setProperties(attributes,object);
 26     }
 27 
 28     @Override
 29     public void body(String body) {
 30 
 31     }
 32 
 33     @Override
 34     public void endElement() {
 35 
 36     }
 37 
 38     private void setProperties(Attributes attributes, Object object) {
 39         Method[] methods = object.getClass().getMethods();
 40         ArrayList<Method> list = new ArrayList<>();
 41         list.addAll(Arrays.asList(methods));
 42         list.removeIf(o -> o.getParameterCount() != 1);
 43 
 44 
 45         for (int i = 0; i < attributes.getLength(); i++) {
 46             // 獲取屬性名
 47             String attributesQName = attributes.getQName(i);
 48             String setterMethod = "set" + attributesQName.substring(0, 1).toUpperCase() + attributesQName.substring(1);
 49 
 50             String value = attributes.getValue(i);
 51             TwoTuple<Method, Object[]> tuple = getSuitableMethod(list, setterMethod, value);
 52             // 沒有找到合適的方法
 53             if (tuple == null) {
 54                 continue;
 55             }
 56 
 57             Method method = tuple.first;
 58             Object[] params = tuple.second;
 59             try {
 60                 method.invoke(object,params);
 61             } catch (IllegalAccessException | InvocationTargetException e) {
 62                 e.printStackTrace();
 63             }
 64         }
 65     }
 66 
 67     private TwoTuple<Method, Object[]> getSuitableMethod(List<Method> list, String setterMethod, String value) {
 68 
 69         for (Method method : list) {
 70 
 71             if (!Objects.equals(method.getName(), setterMethod)) {
 72                 continue;
 73             }
 74 
 75             Object[] params = new Object[1];
 76 
 77             /**
 78              * 1;如果引數型別就是String,那麼就是要找的
 79              */
 80             Class<?>[] parameterTypes = method.getParameterTypes();
 81             Class<?> parameterType = parameterTypes[0];
 82             if (parameterType.equals(String.class)) {
 83                 params[0] = value;
 84                 return new TwoTuple<>(method,params);
 85             }
 86 
 87             Boolean ok = true;
 88 
 89             // 看看int是否可以轉換
 90             String name = parameterType.getName();
 91             if (name.equals("java.lang.Integer")
 92                     || name.equals("int")){
 93                 try {
 94                     params[0] = Integer.valueOf(value);
 95                 }catch (NumberFormatException e){
 96                     ok = false;
 97                     e.printStackTrace();
 98                 }
 99                 // 看看 long 是否可以轉換
100             }else if (name.equals("java.lang.Long")
101                     || name.equals("long")){
102                 try {
103                     params[0] = Long.valueOf(value);
104                 }catch (NumberFormatException e){
105                     ok = false;
106                     e.printStackTrace();
107                 }
108                 // 如果int 和 long 不行,那就只有嘗試boolean了
109             }else if (name.equals("java.lang.Boolean") ||
110                     name.equals("boolean")){
111                 params[0] = Boolean.valueOf(value);
112             }
113 
114             if (ok){
115                 return new TwoTuple<Method,Object[]>(method,params);
116             }
117         }
118         return null;
119     }
120 }

 

該 rule,重點程式碼為23-25行,主要是設定物件的屬性。物件從哪來,從handler中獲取棧頂元素即可。設定屬性這部分,主要是利用反射來解決的。

 

3、元素與規則列表的對應關係

規則定義好了,我們再看看,針對具體某個xml元素,需要應用哪些規則呢? 這部分是需要我們預定義的。

 1 package com.coder;
 2 
 3 import com.coder.rule.CreateObjectParseRule;
 4 import com.coder.rule.ParseRule;
 5 import com.coder.rule.SetPropertiesParseRule;
 6 import org.xml.sax.Attributes;
 7 import org.xml.sax.SAXException;
 8 import org.xml.sax.helpers.DefaultHandler;
 9 
10 import javax.xml.parsers.ParserConfigurationException;
11 import javax.xml.parsers.SAXParser;
12 import javax.xml.parsers.SAXParserFactory;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.lang.reflect.InvocationTargetException;
16 import java.lang.reflect.Method;
17 import java.util.*;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.atomic.AtomicInteger;
20 
21 /**
22  * desc:
23  * @author: caokunliang
24  * creat_date: 2019/6/29 0029
25  * creat_time: 11:06
26  **/
27 public class GirlFriendHandlerVersion2 extends DefaultHandler {
28     private LinkedList<Object> stack = new LinkedList<>();
29 
30     /**
31      * 規則定義。每個元素可以有多條規則,所以value是一個list。解析時,會按順序呼叫各個規則
32      */
33     private ConcurrentHashMap<String, List<ParseRule>> ruleMap = new ConcurrentHashMap<>();
34 
35     {
36         ArrayList<ParseRule> rules = new ArrayList<>();
37         rules.add(new CreateObjectParseRule("class",this));
38         rules.add(new SetPropertiesParseRule(this));
39 
40         ruleMap.put("Coder",rules);
41 
42         rules = new ArrayList<>();
43         rules.add(new CreateObjectParseRule("class",this));
44         rules.add(new SetPropertiesParseRule(this));
45 
46         ruleMap.put("Girl",rules);
47     }
48 
49 }

 

為了儲存該關係,我們利用了 concurrenthashmap,key即為xml元素的名字,value為需要應用的規則列表。具體的規則定義,見第 36-46行。

 

4、startElement 實現

 @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        System.out.println("startElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one");

        List<ParseRule> rules = ruleMap.get(qName);
        for (ParseRule rule : rules) {
            rule.startElement(attributes);
        }

    }

 

該方法會在解析到 xml 元素開始時,被sax 解析模型呼叫。 第三個引數qName,即為xml元素的值。我們這裡,根據qName獲取到規則,然後依次應用這些規則。

 

5、endElement實現

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        System.out.println("endElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one");

        if ("Coder".equals(qName)){
            Object o = stack.pop();
            System.out.println(o);
        }else if ("Girl".equals(qName)){
            //彈出來的應該是girl
            Object o = stack.pop();

            //接下來獲取到coder
            Coder coder = (Coder) stack.peek();
            coder.setGirl((Girl) o);

        }
    }

 

該方法,會在解析到 xml元素的結束標記時被呼叫,我們這裡,主要關注 橙色行,這裡從棧中彈出第一個元素,應該是我們在 startElement 中 壓入棧內的 girl;然後繼續取棧頂元素,則應該取到 Coder 物件。然後我們這裡,手動將 girl 設定到 Coder裡面去。

這裡將在下一個版本的 handler 中進行優化。

 

6、執行測試程式碼

類的完整程式碼如下,執行main即可:

  1 package com.coder;
  2 
  3 import com.coder.rule.CreateObjectParseRule;
  4 import com.coder.rule.ParseRule;
  5 import com.coder.rule.SetPropertiesParseRule;
  6 import org.xml.sax.Attributes;
  7 import org.xml.sax.SAXException;
  8 import org.xml.sax.helpers.DefaultHandler;
  9 
 10 import javax.xml.parsers.ParserConfigurationException;
 11 import javax.xml.parsers.SAXParser;
 12 import javax.xml.parsers.SAXParserFactory;
 13 import java.io.IOException;
 14 import java.io.InputStream;
 15 import java.lang.reflect.InvocationTargetException;
 16 import java.lang.reflect.Method;
 17 import java.util.*;
 18 import java.util.concurrent.ConcurrentHashMap;
 19 import java.util.concurrent.atomic.AtomicInteger;
 20 
 21 /**
 22  * desc:
 23  * @author: caokunliang
 24  * creat_date: 2019/6/29 0029
 25  * creat_time: 11:06
 26  **/
 27 public class GirlFriendHandlerVersion2 extends DefaultHandler {
 28     private LinkedList<Object> stack = new LinkedList<>();
 29 
 30     /**
 31      * 規則定義。每個元素可以有多條規則,所以value是一個list。解析時,會按順序呼叫各個規則
 32      */
 33     private ConcurrentHashMap<String, List<ParseRule>> ruleMap = new ConcurrentHashMap<>();
 34 
 35     {
 36         ArrayList<ParseRule> rules = new ArrayList<>();
 37         rules.add(new CreateObjectParseRule("class",this));
 38         rules.add(new SetPropertiesParseRule(this));
 39 
 40         ruleMap.put("Coder",rules);
 41 
 42         rules = new ArrayList<>();
 43         rules.add(new CreateObjectParseRule("class",this));
 44         rules.add(new SetPropertiesParseRule(this));
 45 
 46         ruleMap.put("Girl",rules);
 47     }
 48 
 49     private AtomicInteger eventOrderCounter = new AtomicInteger(0);
 50 
 51     @Override
 52     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
 53         System.out.println("startElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one");
 54 
 55         List<ParseRule> rules = ruleMap.get(qName);
 56         for (ParseRule rule : rules) {
 57             rule.startElement(attributes);
 58         }
 59 
 60     }
 61 
 62 
 63 
 64     @Override
 65     public void endElement(String uri, String localName, String qName) throws SAXException {
 66         System.out.println("endElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one");
 67 
 68         if ("Coder".equals(qName)){
 69             Object o = stack.pop();
 70             System.out.println(o);
 71         }else if ("Girl".equals(qName)){
 72             //彈出來的應該是girl
 73             Object o = stack.pop();
 74 
 75             //接下來獲取到coder
 76             Coder coder = (Coder) stack.peek();
 77             coder.setGirl((Girl) o);
 78 
 79         }
 80     }
 81 
 82     public static void main(String[] args) {
 83         GirlFriendHandlerVersion2 handler = new GirlFriendHandlerVersion2();
 84 
 85         SAXParserFactory spf = SAXParserFactory.newInstance();
 86         try {
 87             SAXParser parser = spf.newSAXParser();
 88             InputStream inputStream = ClassLoader.getSystemClassLoader()
 89                     .getResourceAsStream("girlfriend.xml");
 90 
 91             parser.parse(inputStream, handler);
 92         } catch (ParserConfigurationException | SAXException | IOException e) {
 93             e.printStackTrace();
 94         }
 95     }
 96 
 97     /**
 98      * 棧內彈出棧頂物件
 99      * @return
100      */
101     public Object pop(){
102         return stack.pop();
103     }
104 
105     /**
106      * 棧頂push元素
107      * @param object
108      */
109     public void push(Object object){
110         stack.push(object);
111     }
112 
113     /**
114      * 返回棧頂元素,但不彈出
115      */
116     public Object peek(){
117         return stack.peek();
118     }
119 }
View Code

 

執行結果如下:

 

三、優化

這次的優化目標就是,去掉上面endElement裡面的硬編碼。我們給 girl 元素加一條rule,該rule 會在endElement時被呼叫,該rule的邏輯是,從棧中彈出 girl 元素,再從棧中取出棧頂元素(此時由於girl被彈出,此時棧頂為 coder)。

然後直接反射呼叫 coder 的 setGirl 方法,即可將girl 設定進去。

1、定義ParentChildRule 

 1 package com.coder.rule;
 2 
 3 import com.coder.GirlFriendHandlerVersion2;
 4 import org.xml.sax.Attributes;
 5 
 6 import java.lang.reflect.InvocationTargetException;
 7 import java.lang.reflect.Method;
 8 
 9 
10 public class ParentChildRule implements ParseRule{
11     /**
12      * 父物件的方法名,通過該方法將子物件設定進去
13      */
14     private String parentObjectSetter;
15 
16     private GirlFriendHandlerVersion2 girlFriendHandler;
17 
18     public ParentChildRule(String parentObjectSetter, GirlFriendHandlerVersion2 girlFriendHandler) {
19         this.parentObjectSetter = parentObjectSetter;
20         this.girlFriendHandler = girlFriendHandler;
21     }
22 
23     @Override
24     public void startElement(Attributes attributes) {
25 
26     }
27 
28     @Override
29     public void body(String body) {
30 
31     }
32 
33     @Override
34     public void endElement() {
35         // 獲取到棧頂物件child,該物件將作為child,被設定到parent中
36         Object child = girlFriendHandler.pop();
37         //棧頂的child被彈出後,繼續呼叫peek,將獲取到parent
38         Object parent = girlFriendHandler.peek();
39 
40         try {
41             Method method = parent.getClass().getMethod(parentObjectSetter, new Class[]{child.getClass()});
42             method.invoke(parent,child);
43         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
44             e.printStackTrace();
45         }
46     }
47 }

 

2、給 girl 新增規則

1         rules = new ArrayList<>();
2         rules.add(new CreateObjectParseRule("class",this));
3         rules.add(new SetPropertiesParseRule(this));
4         rules.add(new ParentChildRule("setGirl", this));
5 
6         ruleMap.put("Girl",rules);

 

3、修改endElement

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        System.out.println("endElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one");

        List<ParseRule> rules = ruleMap.get(qName);
        if (rules != null) {
            for (ParseRule rule : rules) {
                rule.endElement();
            }
        }

    }

 

這裡的邏輯不再硬編碼,根據元素獲取 rule 列表,然後按順序呼叫 rule 的 endElement 即可。這裡,就會呼叫 ParentChildRule ,將 girl 設定到 coder裡面去。

 

4、完整實現

  1 package com.coder;
  2 
  3 import com.coder.rule.CreateObjectParseRule;
  4 import com.coder.rule.ParentChildRule;
  5 import com.coder.rule.ParseRule;
  6 import com.coder.rule.SetPropertiesParseRule;
  7 import org.xml.sax.Attributes;
  8 import org.xml.sax.SAXException;
  9 import org.xml.sax.helpers.DefaultHandler;
 10 
 11 import javax.xml.parsers.ParserConfigurationException;
 12 import javax.xml.parsers.SAXParser;
 13 import javax.xml.parsers.SAXParserFactory;
 14 import java.io.IOException;
 15 import java.io.InputStream;
 16 import java.util.*;
 17 import java.util.concurrent.ConcurrentHashMap;
 18 import java.util.concurrent.atomic.AtomicInteger;
 19 
 20 /**
 21  * desc:
 22  * @author: caokunliang
 23  * creat_date: 2019/6/29 0029
 24  * creat_time: 11:06
 25  **/
 26 public class GirlFriendHandlerVersion2 extends DefaultHandler {
 27     private LinkedList<Object> stack = new LinkedList<>();
 28 
 29     /**
 30      * 規則定義。每個元素可以有多條規則,所以value是一個list。解析時,會按順序呼叫各個規則
 31      */
 32     private ConcurrentHashMap<String, List<ParseRule>> ruleMap = new ConcurrentHashMap<>();
 33 
 34     {
 35         ArrayList<ParseRule> rules = new ArrayList<>();
 36         rules.add(new CreateObjectParseRule("class",this));
 37         rules.add(new SetPropertiesParseRule(this));
 38 
 39         ruleMap.put("Coder",rules);
 40 
 41         rules = new ArrayList<>();
 42         rules.add(new CreateObjectParseRule("class",this));
 43         rules.add(new SetPropertiesParseRule(this));
 44         rules.add(new ParentChildRule("setGirl", this));
 45 
 46         ruleMap.put("Girl",rules);
 47     }
 48 
 49     private AtomicInteger eventOrderCounter = new AtomicInteger(0);
 50 
 51     @Override
 52     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
 53         System.out.println("startElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one");
 54 
 55         List<ParseRule> rules = ruleMap.get(qName);
 56         for (ParseRule rule : rules) {
 57             rule.startElement(attributes);
 58         }
 59 
 60     }
 61 
 62 
 63 
 64     @Override
 65     public void endElement(String uri, String localName, String qName) throws SAXException {
 66         System.out.println("endElement: " + qName + " It's the " + eventOrderCounter.getAndIncrement() + " one");
 67 
 68         List<ParseRule> rules = ruleMap.get(qName);
 69         if (rules != null) {
 70             for (ParseRule rule : rules) {
 71                 rule.endElement();
 72             }
 73         }
 74 
 75     }
 76 
 77     public static void main(String[] args) {
 78         GirlFriendHandlerVersion2 handler = new GirlFriendHandlerVersion2();
 79 
 80         SAXParserFactory spf = SAXParserFactory.newInstance();
 81         try {
 82             SAXParser parser = spf.newSAXParser();
 83             InputStream inputStream = ClassLoader.getSystemClassLoader()
 84                     .getResourceAsStream("girlfriend.xml");
 85 
 86             parser.parse(inputStream, handler);
 87             Object o = handler.stack.pop();
 88             System.out.println(o);
 89         } catch (ParserConfigurationException | SAXException | IOException e) {
 90             e.printStackTrace();
 91         }
 92     }
 93 
 94     /**
 95      * 棧內彈出棧頂物件
 96      * @return
 97      */
 98     public Object pop(){
 99         return stack.pop();
100     }
101 
102     /**
103      * 棧頂push元素
104      * @param object
105      */
106     public void push(Object object){
107         stack.push(object);
108     }
109 
110     /**
111      * 返回棧頂元素,但不彈出
112      */
113     public Object peek(){
114         return stack.peek();
115     }
116 }
View Code

 

四、原始碼與總結

以上部分的原始碼在:

https://github.com/cctvckl/tomcat-saxtest

 

下篇將會正式進入 Tomcat 的 Digester 機制。

 

相關文章