好久不写博文,罪过罪过。因为最近公司比较忙加上琐事有点多,所以隔了好久才来更新博文。
apache digest本来是struts2框架中来加载xml文件并实例化对象的一个jar包,后来使用的越来越多。我们都知道tomcat的conf文件夹下有一个server.xml配置文件,我们经常会其中的来进行配置以来运行一个java web项目,也经常修改中的port属性以来实现修改tomcat监听的端口。其实每个标签基本上都对应着一个对象,那tomcat是如何将这些对象实例化到java 虚拟机的运行内存中的呢,这就是apache digest类做的事情。 上篇讲到catalina的load的方法,在load方法中调用了两个函数1 initDirs();2 // Before digester - it may be needed3 initNaming();上述两个函数之后调用createStartDigester()。
PRotected Digester createStartDigester() { long t1=System.currentTimeMillis(); // Initialize the digester Digester digester = new Digester(); digester.setValidating(false); digester.setRulesValidation(true); HashMap, List> fakeAttributes = new HashMap<>(); ArrayList attrs = new ArrayList<>(); attrs.add("className"); fakeAttributes.put(Object.class, attrs); digester.setFakeAttributes(fakeAttributes); digester.setUseContextClassLoader(true); // Configure the actions we will be using digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); digester.addObjectCreate("Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResourcesImpl"); digester.addSetProperties("Server/GlobalNamingResources"); digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResourcesImpl"); digester.addObjectCreate("Server/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Listener"); digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); digester.addObjectCreate("Server/Service/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Listener"); digester.addSetNext("Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); //Executor digester.addObjectCreate("Server/Service/Executor", "org.apache.catalina.core.StandardThreadExecutor", "className"); digester.addSetProperties("Server/Service/Executor"); digester.addSetNext("Server/Service/Executor", "addExecutor", "org.apache.catalina.Executor"); digester.addRule("Server/Service/Connector", new ConnectorCreateRule()); digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(new String[]{"executor"})); digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector"); digester.addObjectCreate("Server/Service/Connector/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Connector/Listener"); digester.addSetNext("Server/Service/Connector/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // Add RuleSets for nested elements digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); digester.addRuleSet(new EngineRuleSet("Server/Service/")); digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/"); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader. digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(parentClassLoader)); addClusterRuleSet(digester, "Server/Service/Engine/Cluster/"); long t2=System.currentTimeMillis(); if (log.isDebugEnabled()) { log.debug("Digester for server.xml created " + ( t2-t1 )); } return (digester);}我们可以看到创建了一个digester对象之后,暂时不看对digester对象的各种属性的设置set方法,我们可以看到一大堆的
addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");digester.addSetProperties("Server");digester.addSetNext("Server","setServer", "org.apache.catalina.Server");
这里就是digest对象中的所谓的各种规则了,这里看到的server就是我们在tomcat源码学习后期看到的server对象啦,这个以后再说。
load()方法中,在创建了digester对象后,接下来调用了digester.parse(inputSource);方法即开始解析xml文件并根据上述规则开始实例化各种对象了。这里的xml文件即为conf文件下的server.xml文件拉。回过头来再看digester对象。以下为digester类的成员变量。protected StringBuilder bodyText = new StringBuilder(); protected ArrayStack bodyTexts = new ArrayStack<>(); protected ArrayStack> matches = new ArrayStack<>(10); protected ClassLoader classLoader = null; protected boolean configured = false; protected EntityResolver entityResolver; protected HashMap entityValidator = new HashMap<>(); protected ErrorHandler errorHandler = null; protected SAXParserFactory factory = null;protected Locator locator = null; protected String match = ""; protected boolean namespaceAware = false; protected HashMap> namespaces = new HashMap<>(); protected ArrayStackparams = new ArrayStack<>(); protected SAXParser parser = null; protected String publicId = null; protected XMLReader reader = null; protected Object root = null; protected Rules rules = null; protected ArrayStackstack = new ArrayStack<>(); protected boolean useContextClassLoader = false; protected boolean validating = false; protected boolean rulesValidation = false; protected Map, List> fakeAttributes = null;
在讨论digester类是如何实现在解析xml时来实例化相应的类前,我们先看一篇博文,关于java如何解析xml的文件的 http://www.VEvb.com/topic/763895
原生java包中已经提供了sax来解析源码的api,在解析的时候触发不同的事件,对事件函数进行处理,我们只要继承org.xml.sax.helpers.DefaultHandler类来实现我们的业务需求即可。最主要的是重写startDocument(),startElement(String uri, String localName, String qName, Attributes attributes),endElement(String uri, String localName, String qName) ,characters(char[] ch, int start, int length) 函数。我们来看Digetster类的成员变量,果然含有SAXParser类和XMLReader的成员对象,这里tomcat调用的是XMLReader的parse方法。我们从public void addObjectCreate(String pattern, String className,String attributeName) { addRule(pattern,new ObjectCreateRule(className, attributeName));}ObjectCreateRule 顾名思义,这是一个创建对象的规则了,继承Rule类,Rule是一个抽象类,成员变量为protected类型,可以被子类获取到,分别为 protected Digester digester = null; protected String namespaceURI = null;。注释写的很明白。还可以看到除了set,get方法之外的begin() body() end() finish()方法。这些方法将会被子类重写以实现业务需求。后面我们将看到这些方法将会被上面谈到的startElement()等方法使用到。接着看Digester类的方法
public void addRule(String pattern, Rule rule) { rule.setDigester(this); getRules().add(pattern, rule);}
将rule绑定到digester上,getRules()返回的是Digester类的成员变量rules,为Rules类型的成员变量,Rules是一个接口,实现有RulesBase类,成员变量如下
protected HashMap> cache = new HashMap<>();protected Digester digester = null;protected String namespaceURI = null;protected ArrayList rules = new ArrayList<>();
rules的add方法
@Override public void add(String pattern, Rule rule) { // to help users who accidently add '/' to the end of their patterns int patternLength = pattern.length(); if (patternLength>1 && pattern.endsWith("/")) { pattern = pattern.substring(0, patternLength-1); } List list = cache.get(pattern); if (list == null) { list = new ArrayList<>(); cache.put(pattern, list); } list.add(rule); rules.add(rule); if (this.digester != null) { rule.setDigester(this.digester); } if (this.namespaceURI != null) { rule.setNamespaceURI(this.namespaceURI); }}
即将需要匹配的pattern和rule放置到cache的hashmap中,并绑定digester和namespaceURI
后面还有两种digester.addSetProperties("Server");digester.addSetNext("Server","setServer","org.apache.catalina.Server");
分别是两种不同的规则,就不再一一赘述了。
后面继续还有addRuleSet,我们会发现该函数设置了一下namespace,还有增加了一堆以上的规则,比如说NamingRuleSet就增加了一堆EJB相关的规则以对EJB进行支持等等,EngineRuleSet,HostRuleSet,ContextRuleSet 应该分别对应tomcat的几大组件engine,host,context,当然还有wrapper,也就是servlet相关的没有出现。其中有一个addClusterRuleSet 函数是调用了反射,通过Class.forName来动态加载一个RuleSet,不知道为什么,这里先记下。上面说到是XML的parse方法。在catalina类的load方法中调用了digester的parse方法public Object parse(InputSource input) throws IOException,SAXException { configure(); getXMLReader().parse(input); return (root);}protected void configure() { // Do not configure more than once if (configured) { return; } log = LogFactory.getLog("org.apache.tomcat.util.digester.Digester"); saxLog = LogFactory.getLog("org.apache.tomcat.util.digester.Digester.sax"); // Set the configuration flag to avoid repeating configured = true;}public XMLReader getXMLReader() throws SAXException { if (reader == null){ reader = getParser().getXMLReader(); } reader.setDTDHandler(this); reader.setContentHandler(this); if (entityResolver == null){ reader.setEntityResolver(this); } else { reader.setEntityResolver(entityResolver); } reader.setProperty( "http://xml.org/sax/properties/lexical-handler", this); reader.setErrorHandler(this); return reader;}public SAXParser getParser() { // Return the parser we already created (if any) if (parser != null) { return (parser); } // Create a new parser try { parser = getFactory().newSAXParser(); } catch (Exception e) { log.error("Digester.getParser: ", e); return (null); } return (parser);}
可以看到getXMLReader 方法中,设置XMLReader的解析处理handler均为digester类。主要看
reader.setContentHandler(this);
即在解析xml文件中将调用digester类中的startElement,endElement,endElement方法等。
我们先来看startElement方法
@Override public void startElement(String namespaceURI, String localName, String qName, Attributes list) throws SAXException { boolean debug = log.isDebugEnabled(); if (saxLog.isDebugEnabled()) { saxLog.debug("startElement(" + namespaceURI + "," + localName + "," + qName + ")"); } // Parse system properties list = updateAttributes(list); // Save the body text accumulated for our surrounding element bodyTexts.push(bodyText); if (debug) { log.debug(" Pushing body text '" + bodyText.toString() + "'"); } bodyText = new StringBuilder(); // the actual element name is either in localName or qName, depending // on whether the parser is namespace aware String name = localName; if ((name == null) || (name.length() < 1)) { name = qName; } // Compute the current matching rule StringBuilder sb = new StringBuilder(match); if (match.length() > 0) { sb.append('/'); } sb.append(name); match = sb.toString(); if (debug) { log.debug(" New match='" + match + "'"); } // Fire "begin" events for all relevant rules List<Rule> rules = getRules().match(namespaceURI, match); matches.push(rules); if ((rules != null) && (rules.size() > 0)) { for (int i = 0; i < rules.size(); i++) { try { Rule rule = rules.get(i); if (debug) { log.debug(" Fire begin() for " + rule); } rule.begin(namespaceURI, name, list); } catch (Exception e) { log.error("Begin event threw exception", e); throw createSAXException(e); } catch (Error e) { log.error("Begin event threw error", e); throw e; } } } else { if (debug) { log.debug(" No rules found matching '" + match + "'."); } } }
我们主要看这个函数
List<Rule> rules = getRules().match(namespaceURI, match);根据namespaceURI 和 match来匹配rule,match即xml中的标签,我们打个断点可以发现,第一个匹配server的其实就是上述按顺序的
digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");digester.addSetProperties("Server");digester.addSetNext("Server","setServer","org.apache.catalina.Server");
然后分别调用以上rule的begin方法
ObjectCreateRule的begin方法即利用反射创建了相关对象,并将该对象push到digest的成员变量protected ArrayStack<Object> stack = new ArrayStack<>();中
Class<?> clazz = digester.getClassLoader().loadClass(realClassName);Object instance = clazz.newInstance();digester.push(instance);
SetPropertiesRule的begin方法即会对处于digest的stack的栈顶元素的属性进行设置,隐藏的很隐蔽
if (!digester.isFakeAttribute(top, name)&& !IntrospectionUtils.setProperty(top, name, value)&& digester.getRulesValidation()) { digester.log.warn("[SetPropertiesRule]{" + digester.match + "} Setting property '" + name + "' to '" + value + "' did not find a matching property.");}
在!IntrospectionUtils.setProperty(top, name, value)中进行了设置。
SetNextRule中未对begin方法进行重载,不进行描述
接下来是characters(char buffer[], int start, int length) 方法
@Override public void characters(char buffer[], int start, int length) throws SAXException { if (saxLog.isDebugEnabled()) { saxLog.debug("characters(" + new String(buffer, start, length) + ")"); } bodyText.append(buffer, start, length); }
仅是对bodyText进行append
接下来就是end方法了
无非即对其中的stack进行一系列的pop,并调用了rule的body方法,这里很多继承的rule并未重写body方法,这里不做表述。
还有一点要说明的是,digest类中的成员变量:protected ArrayStack<Object> stack = new ArrayStack<>();是一个通过ArrayList实现的 stack
其中startElement和endElement未做描述,感兴趣的可以自己查阅源代码看,至此结束。
我们可以认为digest使用了decorator设计模式,为了在该类上实现更多的功能,利用一个list,来不断调用不同rule中的方法。
有不足或错误,敬请指正,thanks。
新闻热点
疑难解答