一、前言
框架代码其实也没那么难,大家不要看着源码就害怕,现在去看 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 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 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 getSuitableMethod(List 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,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
为了存储该关系,我们利用了 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 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
执行结果如下:
三、优化
这次的优化目标就是,去掉上面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 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
四、源码与总结
以上部分的源码在:
https://github.com/cctvckl/tomcat-saxtest
下篇将会正式进入 Tomcat 的 Digester 机制。