Liferay中使用portlet:resourceURL触发serveResource()方法调用的细节
引入:
大家在Portlet 開(kāi)發(fā)中經(jīng)常用到<portlet:resourceURL>,而大體上都會(huì)去調(diào)用相應(yīng)的serveResource()方法,這個(gè)過(guò)程雖然大家都清楚,但是能弄明白這個(gè)過(guò)程細(xì)節(jié)的,我相信全世界不超過(guò)100人,至少我去年就這個(gè)疑惑問(wèn)了我們客戶的liferay專家,她不能解釋。后來(lái)去年團(tuán)隊(duì)里Danny問(wèn)過(guò)我這個(gè)問(wèn)題,我當(dāng)時(shí)研究了一陣也走不通,所以一直擱置了。而現(xiàn)在,當(dāng)我花了前面幾天時(shí)間去研究了下liferay部署war包的細(xì)節(jié)后,我突然發(fā)現(xiàn),這個(gè)問(wèn)題我完全明白了。
調(diào)試分析:
其實(shí),根據(jù)上文http://supercharles888.blog.51cto.com/609344/1286976的結(jié)論,在我們部署war包應(yīng)用時(shí)候,對(duì)應(yīng)的我們?cè)趙ar包中的xml文件并不是機(jī)械的復(fù)制到了webapps下面應(yīng)用的部署目錄,而是對(duì)于其中的xml文件進(jìn)行了拆分和加內(nèi)容。從上述文字結(jié)論我們知道,web.xml被添加了很多額外內(nèi)容,然后被拆分為2個(gè)文件,1個(gè)是portal-web.xml文件,它包含了所有的除(Invoker Filter)以外的過(guò)濾器的定義,另外一個(gè)是web.xml,它添加了不少內(nèi)容,而最重要的是,它會(huì)在web.xml中添加一段PortletServlet的定義。
所以,我們到服務(wù)器的webapps上面看下我們的應(yīng)用部署目錄下的web.xml,
發(fā)現(xiàn)它不再是原來(lái)的那個(gè)web.xml了,它有PortletServlet的定義(這里出于security考慮,我吧包名最前面部分去掉了):
所以,這個(gè)Portlet相當(dāng)于一個(gè)橋接,它把本來(lái)隸屬于Portal的一個(gè)個(gè)的Portlet地位提升上去,提升到一個(gè)一個(gè)Servlet,這樣他們就可以獨(dú)立的負(fù)責(zé)響應(yīng)各種請(qǐng)求了。
而這個(gè)Servlet的mapping是:
現(xiàn)在,當(dāng)我們頁(yè)面上有個(gè)Search按鈕。點(diǎn)擊會(huì)觸發(fā)如下的<portlet:resourceURL>:
我們可以看到,這個(gè)<portlet:resourceURL>標(biāo)記會(huì)被liferay-portlet.tld所識(shí)別:
所以最終處理這個(gè)標(biāo)記的類是ResourceURLTag和ResourceURLTei.
它會(huì)最終被處理類轉(zhuǎn)為一個(gè)請(qǐng)求url:這個(gè)請(qǐng)求 url是:
撇開(kāi)細(xì)節(jié),最后因?yàn)樗恼?qǐng)求url滿足/logearchportlet/*這個(gè)url模式,
(你肯定會(huì)問(wèn),這個(gè)http://172.29.175.236:8080/web/guest/log-search?......這個(gè)url明顯不匹配PortletServlet的模式/logsearchportlet/*嘛,因?yàn)镻ortletServlet的模式如下圖所示:
那么,我們的請(qǐng)求url是如何進(jìn)的這個(gè)portlet呢?關(guān)于這點(diǎn),我想了整整2天,才想明白,我在后面的精華疑點(diǎn)解答中會(huì)提到)
所以它最終會(huì)走到PortletServlet方法中。
首先在第64行中從HttpServletRequest獲得portletId:
然后在第66-70行分別從HttpServletRequest/Response中獲取 PortletRequest和 PortletResponse對(duì)象,然后在第72行獲取當(dāng)前請(qǐng)求對(duì)應(yīng)的請(qǐng)求的生命周期階段LIFECYCLE_PHASE:
(疑點(diǎn)2:這里為什么portletRequest,portletResponse,lifecycle信息都在HttpServletRequest中,何時(shí)設(shè)置上去的,關(guān)于這個(gè)問(wèn)題,參見(jiàn)精華疑點(diǎn)解答)
然后從第78-90行從portletRequest中PortletSession對(duì)象,并把PortalSession關(guān)聯(lián)到PortletSession中。
然后第40行調(diào)用PortletUtilFilter.doFilter()方法:
它會(huì)根據(jù)當(dāng)前l(fā)ifecycle的值來(lái)判斷吧PortletRequest轉(zhuǎn)為何種請(qǐng)求類型:
因?yàn)槲覀儚恼{(diào)試信息中看出,當(dāng)前l(fā)ifecycle是“RESOURCE_PHASE",所以它會(huì)吧PortletRequest轉(zhuǎn)為ResourceRequest.然后在第71行繼續(xù)調(diào)用filterChain的doFilter方法。
這次,它會(huì)去先把我們的portlet轉(zhuǎn)為ResourceServingPortlet,這里是我們的LogSearchPortlet,再調(diào)用我們的LogSearchPortlet的serveResource方法:
而所有的我們?cè)跇?gòu)造<portlet:resourceURL>時(shí)候附帶的參數(shù)都會(huì)被封裝在ResourceRequest
從以下截圖中可以看出,<portlet:resourceURL>中的所有參數(shù)都會(huì)被添加到ResourceRequest中,一個(gè)不少:
而我們?cè)?/span>portlet中的代碼已經(jīng)實(shí)現(xiàn)了serveResource方法,所以就可以正確的調(diào)用執(zhí)行了。
精華疑點(diǎn)解答1:
我們的請(qǐng)求url:http://172.29.175.236:8080/web-guest/logsearch?.....是如何進(jìn)入我們PortletServlet的url-pattern /logsearchportlet/*的。
這個(gè)問(wèn)題很復(fù)雜,但是我們可以猜想,肯定在請(qǐng)求送達(dá)PortletServlet之前進(jìn)行了若干預(yù)處理。我們知道,過(guò)濾器總是在Servlet之前執(zhí)行的,而我們的請(qǐng)求,剛好可以符合 Invoker Filter的url-mapping .
而這個(gè)InvokerFilter,如果熟悉它的代碼,會(huì)發(fā)現(xiàn)它其實(shí)會(huì)去按照每個(gè)Filter鏈上Filter的定義,依次去調(diào)用各個(gè)Filter的doFilter方法,當(dāng)然了,這些Filter根據(jù)我們以前的研究?jī)?nèi)容,都是定義在liferay-web.xml中。
看到第一次,請(qǐng)求是/web/guest/logsearch,果然和我們的匹配,然后它會(huì)走一些filter,最后調(diào)用invokerFilterChain.doFilter(servletRequest,servletResponse).
第二次,我們進(jìn)入這個(gè)方法時(shí)候,請(qǐng)求就變了,變?yōu)?c/portal/layout.
這里省略很多不重要步驟,因?yàn)槲覀冊(cè)趕truts-config.xml中定義了/c/portal/layout的action-mapping,如下:
所以,會(huì)走到LayoutAction的execute()方法中:
它會(huì)在第244行調(diào)用重載的processLayout()方法:
然后在第663-665行它會(huì)去調(diào)用processPortletRequest方法:
跳過(guò)漫長(zhǎng)的一段代碼(和我們研究重點(diǎn)無(wú)關(guān)的代碼),最終它在porcessPortletRequest的第899行,判斷l(xiāng)ifecycle是”RESOURCE_PHASE",所以進(jìn)入這個(gè)分支:
再跳過(guò)N多不相關(guān)行,看到最后它會(huì)通過(guò)ServletRequest,ServletResponse 構(gòu)造ResourceRequestImpl和ResourceResponseImpl對(duì)象,并且第936行通過(guò)ResourceRequestImpl創(chuàng)建并且封裝一個(gè)ServiceContext對(duì)象,看右邊的調(diào)試信息可以看到我們的請(qǐng)求url是封裝在這個(gè)ServiceContext對(duì)象的。(_currentURL屬性),然后我們把這個(gè)ServiceContext對(duì)象加到ThreadLocal列表中。
最后,在第941行調(diào)用InvokerPortlet的serveResource方法,它會(huì)最終調(diào)用InvokerPortletImpl的invoke()方法:
而我們?cè)L問(wèn)invoke()方法時(shí)候,謎底終于揭開(kāi)了,都給我睜大眼睛看清楚了:
原來(lái),它會(huì)在第610行通過(guò)PortletConfigImpl獲取Portlet的名字,我們獲得是"logsearchportlet",然后把它拼接到后面的/invoke字符串就得到了這個(gè)path 為"/logsearchportlet/invoke",然后它創(chuàng)建一個(gè)RequestDispatcher對(duì)象用于轉(zhuǎn)發(fā)請(qǐng)求,最后,如下圖所示:
它會(huì)吧請(qǐng)求轉(zhuǎn)發(fā)到path指定的/logsearchportlet/invoke, 而這個(gè)請(qǐng)求url顯然是匹配/logsearchportlet/*的,所以就可以正確的進(jìn)入到 PortletServlet了,于是這個(gè)問(wèn)題得到圓滿解決。
精華疑點(diǎn)解答2:
在PortletServlet服務(wù)于當(dāng)前請(qǐng)求中的service方法中,為什么portletRequest,portletResponse,lifecycle信息都在HttpServletRequest中?
當(dāng)我們分析完剛才整個(gè)過(guò)程中時(shí),這個(gè)問(wèn)題也迎刃而解了,參見(jiàn)InvokerPortletImpl的invoke()方法的第623行到第626行,
在創(chuàng)建好RequestDipatcher對(duì)象后,但是還沒(méi)轉(zhuǎn)發(fā)請(qǐng)求到/logsearchportlet/invoke 之前,它會(huì)去先獲得HttpServletRequest對(duì)象,并且依次吧JAVAX_PORTLET_PORTLET,LIFECYCLE_PHASE,PORTLET_SERVLET_FILTER_CHAIN存入,這樣在PortletServlet的service()方法中就可以正確取出這些信息并且處理了。
總結(jié):
結(jié)束這文章時(shí)候,我真是非常開(kāi)心,其實(shí)這個(gè)問(wèn)題我已經(jīng)想了半年沒(méi)想通,不過(guò)今天終于想通了。本來(lái)這個(gè)問(wèn)題是我們團(tuán)隊(duì)一個(gè)叫Danny的問(wèn)我了,我當(dāng)時(shí)研究了沒(méi)解決,后來(lái)我在Liferay官網(wǎng)上掛了幾個(gè)月沒(méi)人能解答,真開(kāi)心我還是靠自己的實(shí)力解決了。
(1)頁(yè)面上用<portlet:resourceURL>對(duì)應(yīng)的請(qǐng)求url最終會(huì)被映射到PortletServlet中進(jìn)行處理,這個(gè)目的是吧 Portlet的對(duì)于請(qǐng)求處理能力的地位提升到Servlet級(jí)別,因?yàn)樗F(xiàn)在可以接受 HttpServletRequest類型的請(qǐng)求了,這個(gè)PortletServlet會(huì)先從HttpServletRequest中獲得portletId,portletRequest,portletResponse和lifecycle信息,然后根據(jù)lifecycle階段信息,相應(yīng)的吧PortletRequest轉(zhuǎn)為何種請(qǐng)求類型,比如如果lifecycle是RESOURCE_PHASE,那么它會(huì)吧portletRequest轉(zhuǎn)為ResourceRequest,它包含了<portlet:resourceURL>中所有附帶參數(shù)。然后在doFilter方法中,它吧我們的portlet轉(zhuǎn)為ResourceServingPortlet 并且調(diào)用serveResource()方法,于是可以就可以正確的調(diào)用我們?cè)趐ortlet應(yīng)用層面定義的serveResource()方法了。
(2)這個(gè)PortletServlet并不是開(kāi)始就定義在我們的項(xiàng)目打的war包中的,而是在部署war包到liferay部署目錄后,liferay框架自己添加的一段代碼,具體細(xì)節(jié)見(jiàn)上一篇文章:http://supercharles888.blog.51cto.com/609344/1286976
(3)但是最重要的一點(diǎn)是,我們的頁(yè)面的<portlet:actionURL>并不直接對(duì)應(yīng)到PortletServlet的url-mapping中,這也是困擾我半年多的問(wèn)題。其實(shí),它是先走到InvokerFilter中,然后在執(zhí)行/c/portal/layout時(shí)候,它會(huì)走到struts框架,然后經(jīng)過(guò)一系列漫長(zhǎng)的調(diào)用,最終在InvokerPortletImpl的invoke()方法中得到解決了,它會(huì)生成一個(gè)新path類似 ?/<portlet-name>/invoke, 然后把所有portlet相關(guān)信息(包括portlet,lifecycle,filterchain)添加到HttpServletRequest對(duì)象中,并且新建一個(gè)RequestDispatcher吧當(dāng)前請(qǐng)求轉(zhuǎn)發(fā)到剛才的新path中,這樣就可以讓請(qǐng)求去匹配PortletServlet的url-pattern并且進(jìn)入PortletSevlet了。
轉(zhuǎn)載于:https://blog.51cto.com/supercharles888/1287188
總結(jié)
以上是生活随笔為你收集整理的Liferay中使用portlet:resourceURL触发serveResource()方法调用的细节的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android中的BitMap(二)从网
- 下一篇: Magento安装后无法访问