之前介绍过 Catalina加载过程是Bootstrap的load调用的 Tomcat8源码笔记(二)Bootstrap启动
按照Catalina的load过程,大致如下: 接下来一步步分析加载过程
一.initDirs
从系统环境变量、VM参数中读取java.io.tmpdir, 并校验文件夹合法性; 未指定java.io.tmpdir,会抛出异常,所以我们启动时指定VM参数:
-Djava.io.tmpdir=E:/Tomcat_Source_Code/apache-tomcat-8.0.53-src/catalina-home/temp
二.initNaming
initNaming设置一些额外的环境变量,在创建Digester时可能会用到.
三.createStartDigester
createStartDigester作用: 实例化Digester,设置validating标志位false,rulesValidating标志位true,fakeAttributes为{Object.class=[className]},useContextClassLoader标志位true,以上几种属性标志位是为了进行解析server.xml而设置的 。 再接着添加了一大段的 addObjectCreate 、addSetProperties、addSetNext, 这些是用来设置解析server.xml的规则,Tomcat的server.xml我们没见过xsd、DTD来约束文档书写规则吧,但是也不能不按照顺序随意些,比如我们不能将Servers标签写到Service标签里面吧。下面会具体分析添加规则部分。
贴上一份Tomcat8中默认的Server.xml内容,虽然平时部署到Tomcat也没怎么仔细关注过它,可能关注也只是关注到Tomcat端口而已、或者简单配置虚拟图片服务器。现在仔细看这份xml文档,与常见的spring、mybatis的文档不同,前面没有声明DTD或XSD,所以Tomcat要自己定义规则、自己按照规则来解析这样一份XML。
四.Rule抽象类
Rule抽象类的定义如下,省略了setter/getter方法,可以看出来Rule两个属性:关联的Digester以及命名空间namespaceURI,此外的方法就是begin、body、end、finish,这就是一个Rule规则实例化的生命周期,就是按顺序解析XML,按顺序调用begin、body、end、finish方法就能实例化对象;
Rule具体的实现类下面分析,代码中Digester又是addObjectCreate 、addSetProperties、addSetNext一大串,先来addObjectCreate:
添加ObjectCreate时候,会先将Rule和Digester关联,然后每个规则加入到Digester的rules属性中,rules属性为Rules接口,默认采用RuleBase作为实现类。
Rules和RuleBase
RuleBase的add(String,Rule)方法:
rules属性Rule的集合,目的是有序地保存最开始注册的Rule规则;cache属性,按照pattern来保存,目的是比如Server组件,创建ObjectCreate、属性赋值setPropertiesRule、设置调用方法SetNextRule三个属性规则,通过pattern 字符串就能将同一组件的不同规则保存在一起. 而pattern中 / 用来分隔标签下的子标签,比如Server下的Service,Server/Service.
五.configFile
configFile默认为conf/server.xml,Bootstrap的catalinaBase之前Bootstrao启动时候静态代码块赋的值,从系统变量或VM参数中读取catalina.base;简而言之,就是读取catalina.base/conf/server.xml文件.
六.解析server.xml
inputStream就是上面server.xml的FileInputStream ,赋给InputSource实例. push以及parse方法下面记录.
六.一 Digester的push方法
Digester实例将Catalina实例存入stack中,并且Digester的root属性也为Catalina实例,这里的ArrayStack是Tomcat自己实现的类,继承自ArrayList。 这一步将Catalina放入Digester的stack集合相当于第一位置,下标为0的位置,很关键!
六.二 Digester的parse方法, 解析server.xml的地方
configure是初始化打印日志log ; getXmlReader深入发现就是调用SAXParserFactory.newInstance().newSAXParser(),采用SAX的方式解析server.xml,并且设置内容解析器为this对象,也就是Digester,Digester重写了解析的规则。
很想贴一张SAXParser解析流程图,找了半天没搜到,我简单介绍下,SAX解析过程中DefaultHandler类,Digester就继承了DefaultHandler2,当然也继承了DefaultHandler。这个接口呢,startDocument开始解析xml时执行,startElement开始解析元素时执行,characters解析元素内容,endElement结束元素解析,然后循环往复解析,直到解析完成之后endDocument . SAX解析xml优点内存占用小,解析速度快,因为一边读取xml一边解析。Digester类的startDocument就没必要看了,直接从startElement开始记录.
startElement解析过程
server.xml中元素namespaceURI都为空,match解析到Server就是Server,解析到Server下Service就是Server/Service,与之间加入到Digester的pattern是一致的,这里就按照这个规则找到Rule的集合,加入到matches中,并且遍历Rule集合分别调用begin方法,比如以Server元素为例。
Server标签解析:
Server一共有三个Rule,分别是ObjectCreateRule、SetPropertiesRule、SetNextRule
此时ObjectCreateRule的className为org.apache.catalina.core.StandardServer,attributeName为className。 尝试从xml解析的attributes中找className,没有的情况下就用前面的className,使用当前线程的类加载器加载StandardServer,并且使用空参构造器实例化一个StandardServer,具体的StandardServer实例化下章记录,实例化完成的server加入到digester的stack,就是前面说的那个ArrayList子类中,第一位是Catalina,所以StandardServer是第二位.
SetPropertiesRule
digester peek方法返回栈首,深入查看发现就是其stack中最后一位元素,即上面的StandardServer;之后遍历attributes,可以理解为键值对集合,如果键是”className“,isFakeAttribute返回true,后面设置属性的方法就不会执行;如果键不是“className“,就会给StandardServer设置属性,IntrospectionUtils.setProperty可以理解为给对象属性赋值的方法。 比如常见的 <Server port="8005" shutdown="SHUTDOWN"> ,这个xml解析之后attributes就是{port=8005,shutdown=SHUTDOWN},给对象属性赋值方法,也就是遍历对象的方法尝试寻找setPort/setShutdown,然后反射调用。
SetNextRule的begin方法没有做任何处理,跳过;另外server.xml中元素都没有body内容,characters我们也跳过;下面SAX解析发现又会跳进入到startElement中,因为Server元素没有结束,还有子元素要解析。
下面继续以一个Listener元素进行分析,仍然是startElement,之前match=”Server”,还没结束,现在match变成了“Server/Listener”
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);
之前向Digester添加解析规则时,添加了这样的规则:
所以现在通过Server/Listener同样可以得到三个Rule,和Server一样解析,调用空参构造器初始化第一个VersionLoggerListener,设置属性(虽然没有属性值要设置,因为className不在设置属性范围内),setNextRule(虽然这个begin什么也没做), 可以进入endElement分析,第一个endElement是Listener元素的:
endElement伪代码如下: matches也是ArrayStack类型的,pop返回ArrayList集合的最后一个元素,也就是Listener的List<Rule>,遍历Rule集合,分别调用其body方法;因为Tomcat的server.xml大部分或者几乎就没有元素体有内容的元素,所以Rule实现类的body方法都是空实现; 逆序遍历Rule集合,分别调用Rule的end方法 ; 最后结束一个元素,就将match的 / 去掉一重,保证下次解析正确性;
SetNextRule的end方法记录:
peek(0)返回digester中最后添加的一个元素,peek(1)返回倒数第二个添加的元素,分别是VersionLoggerListener以及StandardServer,反射调用peek(1)也就是StandardServer的addLifecycleListener,将peek(0)添加到StandardServer的监听器集合。
ObjectCreateRule的end方法
将当前正在创建的元素从digester中移除,ObjectCreateRule所以是最后执行的,start的时候顺序调用Rule集合,end时候逆序调用Rule集合;
上述就是Server元素所有子元素的解析流程,循环往复,直到最后才解析Server元素的endDocument方法,而前面分析得到Rule集合的end方法,只需要关注SetNextRule和ObjectCreateRule的end方法即可。到了Server时候Digester的stack中元素如下,[Catalina,StandardServer],所以按照分析会调用Catalina的setServer将解析完成的StandardServer赋给Catalina ; 而StandardServer的ObjectCreateRule会将 StandardServer从Digester移除; 加载过程中另外解析的xml,因为暂时不知道用途,这里就不记录了。
七. Catalina加载过程后续步骤
之前将StandardServer赋给了Catalina,现在也将Catalina赋给StandardServer,双向引用;之后给StandardServer设置catalina.home和catalina.base;初始化流,主要是控制台输出流改变,再之后调用StandardServer的init方法初始化容器!StandardServer初始化工作比较复杂,留作下篇博客记录。
八
简单按照Tomcat的server.xml解析规则,自定义了一个测试类,完成解析XML,目的是查看解析的效果:对Tomcat各个组件有个直观的了解。
import org.apache.catalina.*; import org.apache.catalina.Server; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardHost; import org.apache.catalina.deploy.NamingResourcesImpl; import org.apache.catalina.startup.*; import org.apache.tomcat.util.digester.ArrayStack; import org.apache.tomcat.util.digester.Digester; import org.apache.tomcat.util.digester.Rule; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.ext.DefaultHandler2; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; public class ParserTest { private String match=""; private ArrayStack<List<Rule>> stack=new ArrayStack(); public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException { ParserTest test = new ParserTest(); test.test(); } public void test() throws IOException, ParserConfigurationException, SAXException { SAXParserFactory factory = SAXParserFactory.newInstance(); InputSource inputSource = new InputSource(); inputSource.setByteStream(new FileInputStream("E:\Tomcat_Source_Code\apache-tomcat-8.0.53-src\conf\server.xml")); Digester digester=new Digester(); addRule(digester); digester.push(new Catalina()); factory.newSAXParser().parse(inputSource,new DefaultHandler2(){ @Override public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException { String name = localName; if ((name == null) || (name.length() < 1)) { name = qName; } if (match.length()>0) match+="/"; match+=name; List<Rule> rules = digester.getRules().match(uri, ParserTest.this.match); stack.push(rules); if(rules!=null && rules.size()>0){ for(int i=0;i<rules.size();i++){ Rule rule = rules.get(i); try { rule.begin(uri,name,attributes); } catch (Exception e) { e.printStackTrace(); } } } } @Override public void endElement(final String uri, final String localName, final String qName) throws SAXException { String name = localName; if ((name == null) || (name.length() < 1)) { name = qName; } List<Rule> rules = stack.pop(); if(rules!=null && rules.size()>0){ for(int i=0;i<rules.size();i++){ Rule rule = rules.get(i); try { rule.body(uri,name,""); } catch (Exception e) { e.printStackTrace(); } } } if(rules!=null){ for(int j=rules.size()-1;j>=0;j--){ Rule rule = rules.get(j); try { rule.end(uri,name); } catch (Exception e) { e.printStackTrace(); } } } if(match.lastIndexOf("/")>0){ match=match.substring(0,match.lastIndexOf("/")); }else{ match=""; } } @Override public void characters(final char[] ch, final int start, final int length) throws SAXException { super.characters(ch, start, length); } @Override public void endDocument() throws SAXException { if(stack.size()>0){ stack.pop(); } Iterator<Rule> iterator = digester.getRules().rules().iterator(); while(iterator.hasNext()){ Rule rule = iterator.next(); try { rule.finish(); } catch (Exception e) { e.printStackTrace(); } } match=""; stack.clear(); } }); System.out.println("解析XML完毕:"); Catalina catalina = (Catalina) digester.getRoot(); System.out.println("Cataline信息:"+catalina); Server server = catalina.getServer(); System.out.println("Server信息:"+server); System.out.printf("Server端口:%s,SHUTDOWN:%s ",server.getPort(),server.getShutdown()); NamingResourcesImpl globalNamingResources = server.getGlobalNamingResources(); System.out.println("GlobalNamingResources信息:"+globalNamingResources); LifecycleListener[] listeners = server.findLifecycleListeners(); for (LifecycleListener listener:listeners) { System.out.println("Listener信息:"+listener); } Service[] services = server.findServices(); for (Service service:services) { System.out.println("Service信息:"+service.getName()); Connector[] connectors = service.findConnectors(); for(Connector connector:connectors){ System.out.println("Connector信息:"+connector.getProtocol()); } Container container = service.getContainer(); System.out.println("Container容器信息:"+container.getName()); Container[] children = container.findChildren(); int i=1; for(Container child:children){ System.out.println(container.getName()+"子容器"+(i++)+"信息:"+child.getName()); if(child instanceof StandardHost){ StandardHost host= (StandardHost) child; System.out.printf("APP_BASE: %s, UnpackWARS: %s, Auto_Deploy:%s " ,host.getAppBase(),host.isUnpackWARs(),host.getAutoDeploy()); Valve[] valves = host.getPipeline().getValves(); for(Valve valve:valves){ System.out.println(valve.getClass()); } } } } } public static void addRule(Digester digester){ HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<>(); ArrayList<String> 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"); //初始化的Server对象StandardServer 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/")); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader. digester.addRule("Server/Service/Engine",new SetParentClassLoaderRule(Thread.currentThread().getContextClassLoader())); } public static class SetParentClassLoaderRule extends Rule { public SetParentClassLoaderRule(ClassLoader parentClassLoader) { this.parentClassLoader = parentClassLoader; } ClassLoader parentClassLoader = null; @Override public void begin(String namespace, String name, Attributes attributes) throws Exception { if (digester.getLogger().isDebugEnabled()) { digester.getLogger().debug("Setting parent class loader"); } Container top = (Container) digester.peek(); top.setParentClassLoader(parentClassLoader); } } }
查看解析效果: 也让我们大致对Tomca组件有个了解,Catalina持有Server,一个Server持有多个Service,Service组件持有多个Connector连接器,负责对外监听HTTP、AJP等等连接;Service组件只能有一个Container,通常是Engine,Engine容器中有Host容器,Host中就是Context容器;
九. 总结
Catalina加载的过程解析catalina.home下conf/server.xml,并完成实例化工作,并且调用了StandardServer init方法,总体流程是这样,具体细节后面再记录。