引入:

在上文http://supercharles888.blog.51cto.com/609344/1286631中,我们提到,deployDirectory过程非常复杂,而其中最重要的步骤之一就是更新webXML,它包含若干文件的更新,包括web.xml,liferay-web.xml ,而且这些xml文件是最后单独复制到liferay tomcat webapps的应用部署目录中的,当时我提到这一步复杂极了,需要另起一篇文章来讲解,所以这里就专门讲解这个updateWebXml()方法的细节:


调试过程:

(1)首先,它会去读我们$CATALINA_HOME/temp/时间戳目录下,也就是我们的war分发包展开后的web.xml文件,这个原始文件的内容我们会读到字符串content 中。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第1张图片


(2)它会在第1768-1775行读取web.xml中的元素,如果有,则从内容中去掉。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第2张图片


(3)它会在第1778-1785行获取web.xml的版本,因为web.xml对应的XML schema有好几个版本了,有2.3,现在2.4,2.5了,对应的版本信息不同,我们在其中添加内容的结构和方式也会有差异,所以这个信息也很重要,这个信息会从根元素的version属性获得,我们得到我们的web.xml 版本是2.4


(4)因为我们是updateWebXml方法,所以它必定对xml内容进行了改变,所以xml的内容必须从多个渠道收集,首先它在第1789行通过getExtraContent()获得了我们要对原始的xml进行改变的额外内容,这些内容会从许多途径收集完成,我们将其作为我们要加工的原始内容。

关于这一点非常复杂, 我准备放在“精华疑点解答”中。


(5)有了(4)产生的额外内容后,我们开始对这段内容进行加工。我们新建了一个newContent字符串,用于存储加工后的新字符串,它的初始值是在最老版本的来自war包的web.xml中, 的上面吧额外的那段内容(也就是(4)返回的extraContent)添加上进去。换句话说,它就是添加到web.xml的尾部。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第3张图片


(6)然后在(5)的基础上,在1798-1802行把老的包名替换为新的包名:


(7)接着在1806-1808行它会去创建并且更新liferay-web.xml,这个只对于web.xml版本高于2.3适用,它的主要作用是吧我们的web.xml拆分(内容存于newContent中),吧其中所有的过滤器(除了Invoker Filter)全部移到liferay-web.xml中,然后把剩下的部分加上Invoker Filter再反退给并且保存在newContent中具体的细节我会在“精华疑点解答”中给出。


(8)最后在第1812-1814行吧返回的newContent的内容更新到web.xml文件中。



精华疑点解答1:

(1)在构建新的web.xml内容时候,原始素材来自于getExtraContent()的方法调用,这个方法调用后到底加了哪些额外内容呢?为此我们进入getExtraContent()方法的调试。


首先我们看到在第122行它会调用超类的getExtraContent()方法来获取额外内容,要改变的内容都存放在变量StringBundler sb中。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第4张图片

不具体展开了,从父类的getExtraContent方法返回的内容如下:

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第5张图片

整体上看,加了以下内容:

(1)加了1listener(SerializableSessionAttributeListener),

(2)加了1Servlet(SetPortletClassLoaderServlet),并且这个Servletload-on-startup0

(3)加了若干标记库定义,如果高于web.xml版本高于2.3,则放在元素下,如果版本低于2.3,那么直接放在根元素下。

这些标记库包括:aui.tldliferay-portlet.tldliferay-portlet-ext.tldliferay-security.tldliferay-theme.tldliferay-ui.tldliferay-util.tld

回想我们上次研究的内容,这些对应的tld文件都会从portalImpl.jar中复制到对应的/WEB-INF/tld文件下,所以使用这些标记库中的标记都没问题。

(4)加了1filter(PortalClassLoaderFilter),并且它会利用CompoundSessionIdFilter过滤器,它的url-pattern是任意请求 /*


然后第127-135行是对于webSphere服务器,加一段服务器特有的配置到StringBundler中,因为我们服务器用的是tomcat,所以不考虑这段代码。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第6张图片


然后第142行会调用updatePortletXML方法,入参也是从我们部署时的portlet war包中解压出来的portlet.xml文件。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第7张图片

从上面看出,它其实就是读取portlet.xml内容,然后把其中的JSPPortlet转为MVCPortlet,因为我们的portlet.xml没有,所以原样返回。


然后在第144行调用sb.append(getServletContent(portletXML,webXML))来附加上和Servlet配置相关的内容到StringBundler中。我们来看下getServletContent的实现(核心)

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第8张图片

可以发现,它先会读取portlet.xml文件,然后对于其中的每个 进行迭代,创建1servlet定义。具体看就是定义一个servletPortletServlet, 这个servlet有个初始参数portlet-class为我们当前Portlet的类名,并且load-on-startup1,而且它的url-mapping是对于任意请求。然后web.xml文件,然后对于其中的进行迭代,因为我们文件中没有。定义,所以直接跳过。所以最后从getServletContent(portletXML,webXML)中返回到getExtraContent()后的信息就只有一段PortletServlet的定义。


然后从第146行它会去处理jsf文件,因为我们没有faces-config.xml,所以不做任何处理。


然后在第148行会判断,如果是sunjsfPortlet,那么会加上一段特殊配置到StringBundler中,因为我们不是用的SunJSFPortlet, 所以跳过:

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第9张图片


然后第162-166行加上一段PortletContextListener监听器到StringBundler中:

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第10张图片


然后第170行会会加上一组ignore过滤器到StringBundler中,它们用于当Portal类加载时候吧一些指定扩展名的文件过滤掉。这组过滤器的定义通过方法getIgnoreFiltersContent(srcFile)获取:

事实上,通过调试,他们都来自于portal-impl.jar文件:

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第11张图片


然后第174行会加上一组用于加速请求处理的过滤器到StringBundler中,它们用于当Portal类加载时候做一些加速处理,比如缓存,比如压缩。这组过滤器的定义通过方法getSpeedFiltersContent(srcFile)获取:

事实上,通过调试,他们也都来自于portal-impl.jar文件:

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第12张图片


最后,在第178行加上一组ServletContextInclude的过滤器到StringBundler中:

这方法调用后加上了以下内容:

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第13张图片


所以最后StringBundler中的内容返回后就是上述各种内容的总和。


精华疑点解答2:

在updateLiferayWebXml方法中到底更新了什么内容给liferay-web.xml,并且返回的剩余内容是什么呢?为此我们也进行调试.

我们发现,首先,它利用WebXMLBuilder吧我们传入的由getExtraContent参合进去的总的web.xml内容文件先格式化,包括元素缩进以及各个元素的排列顺序。


然后,它获取这段内容中从第一个到最后一个这一整段关于过滤器的定义的字符串,把坐标上下界分别存放到变量x,y中,然后把这段过滤器内容存放到filterContent字符串变量中:


接下来,它会先创建一个元素,然后把刚才的关于过滤器的全部定义插入到这个元素内,利用WebXMLBuilder对格式重新组织下,最后将其内容写入部署缓存目录的/WEB-INF/liferay-web.xml中。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第14张图片


我们对比下文件系统的文件时间戳,果然这个部署缓存目录下的liferay-web.xml是刚更新的。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第15张图片


接下来就是对liferay-web.xml之外的部分进行处理了:

可以看出,它先把web.xml中所有关于过滤器的部分去掉,然后中间填上InvokerFilter过滤器的定义部分,这部分是利用API getInvokerFilterContent()方法调用获得的。这样就形成了最终的web.xml,其实也很好理解,因为避免重复定义,所以web.xml中只保留了InvokerFilter,而其他的filter都放在了liferay-web.xml中。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第16张图片

这里从右边调试信息可以很明显的看出,现在的部分只有Invoker Filter而没有其他部分了。

我们从文件系统看,也可以看出这个web.xml的确是刚才生成的。

Liferay 部署war包中deployDirectory过程中调用updateWebXml()方法详细分析_第17张图片


总结:

以上这个过程太复杂了,我们有太多的发现和太多的知识需要总结了。

(1)updateWebXml不仅包含更新web.xml,还包括创建更新liferay-web.xml

(2)在创建更新web.xml时,它总有一个原始xml字符串供我们处理,这原始的web.xml字符串是由从我们war包中获取的web.xml内容的全部加上一些额外内容形成的。

(3)这些额外的内容由多个渠道获取的内容拼凑而成,并且封装在getExtraContent()方法中,具体来说,它包含下述内容:

a.加了1listener(SerializableSessionAttributeListener),

b.加了1Servlet(SetPortletClassLoaderServlet),并且这个Servletload-on-startup0

c.加了若干标记库定义,如果高于web.xml版本高于2.3,则放在元素下,如果版本低于2.3,那么直接放在根元素下。

这些标记库包括:aui.tldliferay-portlet.tldliferay-portlet-ext.tldliferay-security.tldliferay-theme.tldliferay-ui.tldliferay-util.tld

d.加了1filter(PortalClassLoaderFilter),并且它会利用CompoundSessionIdFilter过滤器,它的url-pattern是任意请求 /*

e.如果服务器类型是websphere,则加一段和websphere有关的特殊初始上下文

f. 加一段PortletServlet的定义

g.如果是JSFPortlet,则加上一段特殊的listener.

h.加一段PortletContextListener监听器定义

i.加若干 Ignore Filter过滤器,它们都来自portal-impl.jar中。

j.加若干 Speed Filter过滤器,它们都来自portal-impl.jar中。

k.加上ServletContextInclude过滤器。

(4)再把(3)的额外内容附加到原始的web.xml后,我们就对新的web.xml的内容进行后处理,处理的要点是吧这个web.xml的内容拆分为2个xml文件,一个是web.xml,一个是liferay-web.xml文件。这2个文件的根元素都是,不同在于liferay-web.xml文件中包含了所有的过滤器,而web.xml中则是剩下的元素去除所有的一般过滤器,但是加上了Invoker Filter过滤器,这样可以起到“分而治之”的作用。

(5)最后会吧这些资源文件都复制到tomcat/webapps下的相应应用的部署目录下。