一、前言
框架程式碼其實也沒那麼難,大家不要看著原始碼就害怕,現在去看 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 }
執行結果如下:
三、優化
這次的優化目標就是,去掉上面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 }
四、原始碼與總結
以上部分的原始碼在:
https://github.com/cctvckl/tomcat-saxtest
下篇將會正式進入 Tomcat 的 Digester 機制。