曹工说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 机制。
标签:xml,coder,String,Tomcat,rules,曹工,Tomcat2,new,import 来源: https://www.cnblogs.com/grey-wolf/p/11114336.html