引入:

在上文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 中。

125621204.png


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

125807366.png


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

130109775.png


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

131008489.png

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


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

131355665.png


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

131516178.png


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

131804817.png


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

132209469.png



精华疑点解答1:

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


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

132627755.png

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

133116281.png

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

(1)加了1listener(SerializableSessionAttributeListener),

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

(3)加了若干标记库定义,如果高于web.xml版本高于2.3,则放在<jsp-config>元素下,如果版本低于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,所以不考虑这段代码。

133239597.png


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

133350563.png

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


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

133618345.png

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


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

133853650.png


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

133944567.png


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

134029823.png


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

134220103.png

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

134255647.png


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

134410147.png

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

134455511.png


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

134627370.png

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

134713330.png


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


精华疑点解答2:

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

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

135503753.png


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

135647395.png


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

135933405.png


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

140110840.png


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

140416849.png

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

140724857.png

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

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

140931628.png


总结:

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

(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,则放在<jsp-config>元素下,如果版本低于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有关的特殊初始上下文<context-param>

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个文件的根元素都是<web-app>,不同在于liferay-web.xml文件中包含了所有的过滤器,而web.xml中则是剩下的元素去除所有的一般过滤器,但是加上了Invoker Filter过滤器,这样可以起到“分而治之”的作用。

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