引入:
在上文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 中。
(2)它会在第1768-1775行读取web.xml中的<display-name>元素,如果有,则从内容中去掉。
(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中,在</web-app> 的上面吧额外的那段内容(也就是(4)返回的extraContent)添加上进去。换句话说,它就是添加到web.xml的尾部。
(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中。
不具体展开了,从父类的getExtraContent方法返回的内容如下:
整体上看,加了以下内容:
(1)加了1个listener(SerializableSessionAttributeListener),
(2)加了1个Servlet(SetPortletClassLoaderServlet),并且这个Servlet的load-on-startup为0
(3)加了若干标记库定义,如果高于web.xml版本高于2.3,则放在<jsp-config>元素下,如果版本低于2.3,那么直接放在根元素下。
这些标记库包括:aui.tld,liferay-portlet.tld,liferay-portlet-ext.tld,liferay-security.tld,liferay-theme.tld,liferay-ui.tld,liferay-util.tld
回想我们上次研究的内容,这些对应的tld文件都会从portalImpl.jar中复制到对应的/WEB-INF/tld文件下,所以使用这些标记库中的标记都没问题。
(4)加了1个filter(PortalClassLoaderFilter),并且它会利用CompoundSessionIdFilter过滤器,它的url-pattern是任意请求 /*
然后第127行-135行是对于webSphere服务器,加一段服务器特有的配置到StringBundler中,因为我们服务器用的是tomcat,所以不考虑这段代码。
然后第142行会调用updatePortletXML方法,入参也是从我们部署时的portlet war包中解压出来的portlet.xml文件。
从上面看出,它其实就是读取portlet.xml内容,然后把其中的JSPPortlet转为MVCPortlet,因为我们的portlet.xml没有,所以原样返回。
然后在第144行调用sb.append(getServletContent(portletXML,webXML))来附加上和Servlet配置相关的内容到StringBundler中。我们来看下getServletContent的实现(核心):
可以发现,它先会读取portlet.xml文件,然后对于其中的每个<portlet> 进行迭代,创建1段servlet定义。具体看就是定义一个servlet叫PortletServlet, 这个servlet有个初始参数portlet-class为我们当前Portlet的类名,并且load-on-startup为1,而且它的url-mapping是对于任意请求。然后web.xml文件,然后对于其中的<servlet>进行迭代,因为我们文件中没有。<servlet>定义,所以直接跳过。所以最后从getServletContent(portletXML,webXML)中返回到getExtraContent()后的信息就只有一段PortletServlet的定义。
然后从第146行它会去处理jsf文件,因为我们没有faces-config.xml,所以不做任何处理。
然后在第148行会判断,如果是sun的jsfPortlet,那么会加上一段特殊配置到StringBundler中,因为我们不是用的SunJSFPortlet, 所以跳过:
然后第162-166行加上一段PortletContextListener监听器到StringBundler中:
然后第170行会会加上一组ignore过滤器到StringBundler中,它们用于当Portal类加载时候吧一些指定扩展名的文件过滤掉。这组过滤器的定义通过方法getIgnoreFiltersContent(srcFile)获取:
事实上,通过调试,他们都来自于portal-impl.jar文件:
然后第174行会加上一组用于加速请求处理的过滤器到StringBundler中,它们用于当Portal类加载时候做一些加速处理,比如缓存,比如压缩。这组过滤器的定义通过方法getSpeedFiltersContent(srcFile)获取:
事实上,通过调试,他们也都来自于portal-impl.jar文件:
最后,在第178行加上一组ServletContextInclude的过滤器到StringBundler中:
这方法调用后加上了以下内容:
所以最后StringBundler中的内容返回后就是上述各种内容的总和。
精华疑点解答2:
在updateLiferayWebXml方法中到底更新了什么内容给liferay-web.xml,并且返回的剩余内容是什么呢?为此我们也进行调试.
我们发现,首先,它利用WebXMLBuilder吧我们传入的由getExtraContent参合进去的总的web.xml内容文件先格式化,包括元素缩进以及各个元素的排列顺序。
然后,它获取这段内容中从第一个<filter>到最后一个<filter-mapping>这一整段关于过滤器的定义的字符串,把坐标上下界分别存放到变量x,y中,然后把这段过滤器内容存放到filterContent字符串变量中:
接下来,它会先创建一个<web-app>元素,然后把刚才的关于过滤器的全部定义插入到这个<web-app>元素内,利用WebXMLBuilder对格式重新组织下,最后将其内容写入部署缓存目录的/WEB-INF/liferay-web.xml中。
我们对比下文件系统的文件时间戳,果然这个部署缓存目录下的liferay-web.xml是刚更新的。
接下来就是对liferay-web.xml之外的部分进行处理了:
可以看出,它先把web.xml中所有关于过滤器的部分去掉,然后中间填上InvokerFilter过滤器的定义部分,这部分是利用API getInvokerFilterContent()方法调用获得的。这样就形成了最终的web.xml,其实也很好理解,因为避免重复定义,所以web.xml中只保留了InvokerFilter,而其他的filter都放在了liferay-web.xml中。
这里从右边调试信息可以很明显的看出,现在的<filter>部分只有Invoker Filter而没有其他部分了。
我们从文件系统看,也可以看出这个web.xml的确是刚才生成的。
总结:
以上这个过程太复杂了,我们有太多的发现和太多的知识需要总结了。
(1)updateWebXml不仅包含更新web.xml,还包括创建更新liferay-web.xml
(2)在创建更新web.xml时,它总有一个原始xml字符串供我们处理,这原始的web.xml字符串是由从我们war包中获取的web.xml内容的全部加上一些额外内容形成的。
(3)这些额外的内容由多个渠道获取的内容拼凑而成,并且封装在getExtraContent()方法中,具体来说,它包含下述内容:
a.加了1个listener(SerializableSessionAttributeListener),
b.加了1个Servlet(SetPortletClassLoaderServlet),并且这个Servlet的load-on-startup为0
c.加了若干标记库定义,如果高于web.xml版本高于2.3,则放在<jsp-config>元素下,如果版本低于2.3,那么直接放在根元素下。
这些标记库包括:aui.tld,liferay-portlet.tld,liferay-portlet-ext.tld,liferay-security.tld,liferay-theme.tld,liferay-ui.tld,liferay-util.tld
d.加了1个filter(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下的相应应用的部署目录下。