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


一、前言

框架代碼其實也沒那么難,大家不要看着源碼就害怕,現在去看 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 機制。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM