您的位置:首页 > 运维架构 > Tomcat

Degister源码解析及在tomcat中的应用

2013-03-14 20:08 260 查看
上周研究学习了下tomcat的启动过程的源代码,了解了一下tomcat启动所做的事情,有一个地方是用Digester来加载我们conf/server.xml的配置。这个地方研究的不是很深入,只是说了Digester是一个xml引擎,能把对应的xml配置转换成javabean对象,但是这个强大的Digester是怎么工作的呢?在tomcat中又具体是如果操作的呢?这周抽了点时间研究了一下。

1.首先说Digester是什么?

Digester最初是Structs的一部分,由于它做的比较好,是一个很出色的xml引擎,能很方便的把xml配置转换成java对象,具有较强的通用性。所以被划入了apache commons项目,作为其一个模块。

2. Digester的实现原理是什么,为什么具有教强的通用性?

Digester的实现基于了SAX,SAX是一个比较有名的xml解析器,但是SAX提供的功能比较底层,我们在平时写代码如果xml配置文件写错了,经常会抛SAXParseException,相必大家在写ibatis的配置文件或者webx的配置文件的时候会遇到过。SAX是基于事件驱动的,当读到一个xml配置文件的起始标签或者结束标签的时候就会触发一个事件,然后调用一个 Handler回调函数。Digester就是扩展了这一点。Digester继承了SAX的DefaultHandler类,重写了这个Handler的一些回调方法。



上面就是这个Digester的签名信息。其重写的主要方法有startDocument、endDocument、startElement、endElement、characters等方法。

Digester的通用性就是靠用户在解析自定义xml文件时指定自定义rule来完成的。这个rule就是当SAX解析到一些标签的时候触发Digester 的handler函数所用到的一些处理规则,在handler函数里会去扫描到相应的规则来完成对象的构建工作。我网上找了个demo改吧改吧调试了一下,大体流程是这样的:



第一个红框就是我设置rule规则的地方,这个要依据你的xml文件来设置,第二个红框就是具体的解析触发动作,入参是我们的xml文件流,第三个红框是我们的main函数类。

Digester类包含了这么几个主要的成员变量:











解释一下这几个变量的作用,rules存放了我们用户自定义的一些规则,就是这些(是和xml文件的DOM结构相对应的):



每一个都是一个规则,rules是一个接口,在创建第一个rule的时候会被实例成RuleBase对象。



那么上面那几个Stack是干嘛的呢?由于我们解析的是xml文件,xml的结构是一个树形的,即根节点下面有好多的子节点。这几个Stack就是为了将节点转换成对象而准备的,一个栈用来存对象,一个栈用来存要对这个对象所做的操作即rule,一个栈用来存标签里面标签体的值,那具体是怎么样的呢,这个根据源码一点一点的来分析。

首先Digester里面持有了一个reader成员变量:



这个reader就是用来解析xml文件的。在我们这个demo中他被赋予了SAXParserImpl的一个内部类JAXPSAXParser的对象,我也不知道什么东西,反正知道是SAX的东西就行了,有空可以在研究一下SAX。



在获取这个reader的时候,Digester将SAX的事件触发函数设置成了它自己,以便事件触发时产生回调:



为什么能设置成他自己呢,上面也说了,因为Digester继承了DefaultHandler,而DefaultHandler实现了好几个接口:



所以这里的回调类就能传入它本身了,就是这里的this。当SAX解析的到第一个Element开始标签的时候就会调用我们Digester定义的回调函数startElement:

函数签名如下:



我所解析的xml文件是这个:



SAX解析器解析到的第一个元素标签是Chart,这个时候他会把我们设置在rules里的和chart这个模式匹配的规则全部获取出来,存到我们上面提到的Stack<List<Rule>>里,我们这个demo里Chart所对应的规则只有一个就是这个:

对应的调试信息也能看出来:



这个时候会遍历我们查出来的Rule,执行rule所指定的操作(其实是这样的,每个rule都定义了begin、body、end、finish方法)遍历的逻辑如下:



调用每一个rule对应的begin方法其实就是执行了一些操作,我们第一个rule是创建对象规则,所以此时创建了一个名为的chart的对象实例。



这个实例创建完毕之后就压入了我们上文定义的对象栈中:



没错就是存在了上文提到的那个栈中:



至此我们在xml节点设置的chart对象就被创建好了并且压入了栈中,但是此时我们的Chart对象里的属性还没有设置,那么设置对象属性值的操作是怎么样的呢?我们继续分析:我们来看当进入我们的一个属性标签legendVisible发生了什么:



同样的还是先获取到这个标签模式chart/legendVisible(上面设置的和xml文件dom相对应)对应的rule。



然后循环执行rule对应的begin方法,由于其没有属性,本身就是属性所以这个begin操作什么都没做。



解析完这个开始标签之后,就触发了Digester的Characters函数,这个函数是用来读取标签内定义的内容的。



可以看到已经把xml中定义的legendVisible标签的值false解析出来了。这个值暂存到了Digester的成员变量bodyText中,这个bodyText只是一个临时存储了一下这个value值,当执行到下一个开始标签节点的时候这个值会被压入栈bodyTexts中:



这个栈就是Digester定义的成员变量:



到此刻我们总结一下,此时这里一共提到了三个Stack,一个用来存储rule,一个用来存储对象,一个用来存储xml节点的内容。其实Digester还定义了一个栈,就是:



这个主要是用来存储rule执行的之后的参数对象列表,因为当rule执行一个操作的时候可能是给某个对象的属性赋值,那么在这个节点层次上涉及到的多个参数对象肯定是同一层次的,所以作为栈中的一个对象,至少我是这么理解的。

在读完一个开始节点标签和标签对应的内容之后就是读取对应的结束标签了。

当读到结束标签的时候会触发Digester的endElement函数,来做相应的操作,我们这里肯定就是给我们的Chart对象的属性赋值了。再分析一下:

在读结束标签的时候首先从规则stack中pop出相对应的rule:



然后循环的去执行rule对应的body方法:



这个body方法主要是做了对Rule的bodyText属性进行赋值(也就是说rule的body函数主要用来做一些必要的预处理的工作):



然后就循环执行了该栈层次上的rule的end方法:



在end方法里,首先是从对象栈中取出了我们的Chart对象:



然后利用反射将这个属性设置到我们的栈顶对象中:



这个setProperty是BeanUtils的函数。其他的属性依此类推都是依次解析起始标签,内容,结束标签,然后分别调用相应的回调函数startElement,characters,endElement,通过三个stack配合来完成对对象的赋值。我们这个Demo就分析完了,当然还有Digester还有许多复杂的操作,比如如何调用对象栈中对象的某个方法,这个下文在分析tomcat的时候在说一下。

Ps:分析的时候卡在一个地方,这里记录下,以免下次再看的时候忘掉。SAX在解析的时候使用了一个开关语句来调用事件对应的回调函数:



但是当时我就迷茫了,这个END_ELEMENT这个事件对应的回调被注释掉了没有执行啊,但是结果是正确的,那这个结束回调是怎么完成的呢,经分析发现原来这个回掉被封装在了第二个红框里(不得不说这样写代码让我们读起来很累,逻辑竟然分散到了两个地方).

还有一个就是读完这段代码我还有一个疑问没有分析明白,就是



这个栈的实际作用是什么,没太看出来这个栈的实际作用,谁要是知道可以告诉我。Commondigester的内容就分析这么多,下面回到正题tomcat是怎么用digester来解析server.xml的。

3. 下面再分析一下tomcat是怎么用Digester来解析server.xml文件的?

主要是catalina这个类的load函数,来解析server.xml文件,以及来设置rule的。在load函数里,首先实例化了一个Digester类,然后将Catalina实例自己压入了对象栈中:



Catalina对象最先入栈,那么他就是整个服务的配置的根对象,它通过继承的方式持有了一个Server实例。我们的server.xml文件配置了server的相关信息,Server对象在对象栈中要先于Catalina实例出栈,所以在server实例出栈的时候可以将Server实例通过指定的Rule规则将Server对象赋值给Catalina对象,此时弹栈获得就是构造好的Catalina实例,具体如下:

首先看一下catalina规则定义的和server.xml对应的配置规则。









以上就是默认的为了解析的全部的rule配置了。

我们来分析整个装载的过程,首先将每add一条rule就把这条rule添加到了Digester里的rules变量里。Rules对象持有了一个List<Rule>内置对象,所以一开始这些rule全都存到了一个List里面。然而当SAX解析到Server这个起始标签的时候就会调用相关的创建对象的方法,默认是初始化类,在规则里有设定:



第三个参数className供我们在xml配置的Server类,如果不配置就默认实例化一个StandardServer对象。Server.xml里面没有指定要实例的StandardServer对象,所以一个StandardServer实例会被压入对象栈。

接下来读到相关的Listender开始标签,看一下我们server.xml文件中Server根标签下直接相关的有这么5个监听器:



而Catalina在定义规则的时候只定义了一个:



此时我有点蒙了,为什么制定一个这个Rule就可以了呢,仔细想了一下确实好像可以的,因为第二个参数传的是null,所以根据xml里定义的类的类型来实例类,此时这个Rule会被保存在Rules里,那么规则栈里面会根据规则保存多个rule,因为每次都会去Rules检索相匹配的规则,每次检索到都会将Rule压栈,因为栈中的变量肯能被pop,但Rules中的变量不会少,所以只定义一个规则就可以了。

言归正传,当执行到Listener开始标签的时候,他会匹配到我们定义的这么几个规则:



这两个规则的意思是:digester.addSetProperties("Server/Listener");这个规则的意思是对Listener的每个属性调用Setter方法。然后执行digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener"); 这个规则,这个的意思是将当前对象弹栈,并调用父级对象的addLifecycleListener方法,此时Listener的父级对象是Server,所以会调用Server的addLifecycleListender 方法。那我们这个server是org.apache.catalina.core. StandardServer,则就会调用server的addLifecycleListender
方法,方法签名如下:



这样Server对象的Listener属性就被添加进来了。Server.xml的紧邻根标签Server的几个Listener都是这么设置进来的就不多说了。之后的几个对象都是以类似的方式被添加进来的。那么这里就不一一记录了。直说几个当时卡住的点:

Ps:digester的规则设置的时候有调用这个函数digester.addRuleSet,这个函数是什么意思?

源码是这样的:



这个我跟进去看了一下,发现这里面是添加了一组Rule,看一个:



其实我们从名字上面应该也能看出来,ruleSet嘛!

第二个指的注意的点是:

ObjectCreateRule也就是创建对象的rule是在执行rule的end函数的时候弹栈,在执行begin函数的时候创建对象。而操作类型的rule比如:调用方法为某个对象设值,一般是在执行end方法的时候给下层对象设值,这个注意一下。在整个流程执行完之后,server.xml文件设置的根元素是server,但是别忘了我们在解析server.xml文件之前就把catalina对象压入了栈中(看上文有说)。此时他会将对象赋值给catalina实例。当然还是根据这个规则:



此时我们的完整的catalina实例就根据server的配置构建起来,可以对外提供服务了。

这个是承接上周tomcat启动过程源码分析当中涉及到digester解析的过程的具体研究,这个留作笔记沉淀,也许我这么说大家看的云里雾里的,但大家重头看研究digester源码的时候,结合这个笔记将会少走弯路。有错误,求反馈,求指正!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: