Liferay中使用<portlet:resourceURL>觸發serveResource

來源:互聯網
上載者:User

引入:

大家在Portlet 開發中經常用到<portlet:resourceURL>,而大體上都會去調用相應的serveResource()方法,這個過程雖然大家都清楚,但是能弄明白這個過程細節的,我相信全世界不超過100人,至少我去年就這個疑惑問了我們客戶的liferay專家,她不能解釋。後來去年團隊裡Danny問過我這個問題,我當時研究了一陣也走不通,所以一直擱置了。而現在,當我花了前面幾天時間去研究了下liferay部署war包的細節後,我突然發現,這個問題我完全明白了。



調試分析:

其實,根據上文http://supercharles888.blog.51cto.com/609344/1286976的結論,在我們部署war包應用時候,對應的我們在war包中的xml檔案並不是機械的複製到了webapps下面應用的部署目錄,而是對於其中的xml檔案進行了拆分和加內容。從上述文字結論我們知道,web.xml被添加了很多額外內容,然後被拆分為2個檔案,1個是portal-web.xml檔案,它包含了所有的除(Invoker Filter)以外的過濾器的定義,另外一個是web.xml,它添加了不少內容,而最重要的是,它會在web.xml中添加一段PortletServlet的定義。


所以,我們到伺服器的webapps上面看下我們的應用部署目錄下的web.xml,

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222U393-0.png" title="31.png" />

發現它不再是原來的那個web.xml了,它有PortletServlet的定義(這裡出於security考慮,我吧包名最前面部分去掉了):

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222T011-1.png" title="33.png" />

所以,這個Portlet相當於一個橋接,它把本來隸屬於Portal的一個個的Portlet地位提升上去,提升到一個一個Servlet,這樣他們就可以獨立的負責響應各種請求了。

而這個Servlet的mapping是:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222Q620-2.png" title="34.png" />



現在,當我們頁面上有個Search按鈕。點擊會觸發如下的<portlet:resourceURL>:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222V1B-3.png" title="35.png" />


我們可以看到,這個<portlet:resourceURL>標記會被liferay-portlet.tld所識別:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222VN4-4.png" title="36.png" />

所以最終處理這個標記的類是ResourceURLTag和ResourceURLTei.


它會最終被處理類轉為一個請求url:這個請求 url是:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222S160-5.png" title="45.png" />


撇開細節,最後因為它的請求url滿足/logearchportlet/*這個url模式,

(你肯定會問,這個http://172.29.175.236:8080/web/guest/log-search?......這個url明顯不匹配PortletServlet的模式/logsearchportlet/*嘛,因為PortletServlet的模式如所示:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222U005-6.png" title="46.png" />

那麼,我們的請求url是如何進的這個portlet呢?關於這點,我想了整整2天,才想明白,我在後面的精華疑點解答中會提到)


所以它最終會走到PortletServlet方法中。

首先在第64行中從HttpServletRequest獲得portletId:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222UQ3-7.png" title="37.png" />


然後在第66-70行分別從HttpServletRequest/Response中擷取 PortletRequest和 PortletResponse對象,然後在第72行擷取當前請求對應的請求的生命週期階段LIFECYCLE_PHASE:

(疑點2:這裡為什麼portletRequest,portletResponse,lifecycle資訊都在HttpServletRequest中,何時設定上去的,關於這個問題,參見精華疑點解答)

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222S058-8.png" title="38.png" />


然後從第78-90行從portletRequest中PortletSession對象,並把PortalSession關聯到PortletSession中。

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222V359-9.png" title="39.png" />


然後第40行調用PortletUtilFilter.doFilter()方法:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222R164-10.png" title="40.png" />


它會根據當前lifecycle的值來判斷吧PortletRequest轉為何種請求類型:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222U132-11.png" title="41.png" />

因為我們從調試資訊中看出,當前lifecycle是“RESOURCE_PHASE",所以它會吧PortletRequest轉為ResourceRequest.然後在第71行繼續調用filterChain的doFilter方法。


這次,它會去先把我們的portlet轉為ResourceServingPortlet,這裡是我們的LogSearchPortlet,再調用我們的LogSearchPortlet的serveResource方法:

而所有的我們在構造<portlet:resourceURL>時候附帶的參數都會被封裝在ResourceRequest

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222V242-12.png" title="42.png" />


從以下中可以看出,<portlet:resourceURL>中的所有參數都會被添加到ResourceRequest中,一個不少:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222S441-13.png" title="43.png" />


而我們在portlet中的代碼已經實現了serveResource方法,所以就可以正確的調用執行了。

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222R5L-14.png" title="44.png" />



精華疑點解答1:

我們的請求url:http://172.29.175.236:8080/web-guest/logsearch?.....是如何進入我們PortletServlet的url-pattern /logsearchportlet/*的。

這個問題很複雜,但是我們可以猜想,肯定在請求送達PortletServlet之前進行了若干預先處理。我們知道,過濾器總是在Servlet之前執行的,而我們的請求,剛好可以符合 Invoker Filter的url-mapping .

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222VP1-15.png" title="47.png" />


而這個InvokerFilter,如果熟悉它的代碼,會發現它其實會去按照每個Filter鏈上Filter的定義,依次去調用各個Filter的doFilter方法,當然了,這些Filter根據我們以前的研究內容,都是定義在liferay-web.xml中。

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222Q101-16.png" title="48.png" />


看到第一次,請求是/web/guest/logsearch,果然和我們的匹配,然後它會走一些filter,最後調用invokerFilterChain.doFilter(servletRequest,servletResponse).

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222V012-17.png" title="49.png" />


第二次,我們進入這個方法時候,請求就變了,變為/c/portal/layout.

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222W391-18.png" title="50.png" />


這裡省略很多不重要步驟,因為我們在struts-config.xml中定義了/c/portal/layout的action-mapping,如下:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222U420-19.png" title="51.png" />


所以,會走到LayoutAction的execute()方法中:

它會在第244行調用重載的processLayout()方法:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222W0K-20.png" title="52.png" />


然後在第663-665行它會去調用processPortletRequest方法:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222U958-21.png" title="54.png" />


跳過漫長的一段代碼和我們研究重點無關的代碼),最終它在porcessPortletRequest的第899行,判斷lifecycle是”RESOURCE_PHASE",所以進入這個分支:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222QJ9-22.png" title="55.png" />


再跳過N多不相關行,看到最後它會通過ServletRequest,ServletResponse 構造ResourceRequestImpl和ResourceResponseImpl對象,並且第936行通過ResourceRequestImpl建立並且封裝一個ServiceContext對象,看右邊的調試資訊可以看到我們的請求url是封裝在這個ServiceContext對象的。(_currentURL屬性),然後我們把這個ServiceContext對象加到ThreadLocal列表中。

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222W636-23.png" title="56.png" />


最後,在第941行調用InvokerPortlet的serveResource方法,它會最終調用InvokerPortletImpl的invoke()方法:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222V642-24.png" title="57.png" />


而我們訪問invoke()方法時候,謎底終於揭開了,都給我睜大眼睛看清楚了:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222SE1-25.png" title="58.png" />

原來,它會在第610行通過PortletConfigImpl擷取Portlet的名字,我們獲得是"logsearchportlet",然後把它拼接到後面的/invoke字串就得到了這個path 為"/logsearchportlet/invoke",然後它建立一個RequestDispatcher對象用於轉寄請求,最後,如所示:

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222V516-26.png" title="59.png" />

它會吧請求轉寄到path指定的/logsearchportlet/invoke, 而這個請求url顯然是匹配/logsearchportlet/*的,所以就可以正確的進入到 PortletServlet了,於是這個問題得到圓滿解決。


精華疑點解答2:

在PortletServlet服務於當前請求中的service方法中,為什麼portletRequest,portletResponse,lifecycle資訊都在HttpServletRequest中?

當我們分析完剛才整個過程中時,這個問題也迎刃而解了,參見InvokerPortletImpl的invoke()方法的第623行到第626行,

650) this.width=650;" src="http://www.bkjia.com/uploads/allimg/131228/11222Q610-27.png" title="60.png" />

在建立好RequestDipatcher對象後,但是還沒轉寄請求到/logsearchportlet/invoke 之前,它會去先獲得HttpServletRequest對象,並且依次吧JAVAX_PORTLET_PORTLET,LIFECYCLE_PHASE,PORTLET_SERVLET_FILTER_CHAIN存入,這樣在PortletServlet的service()方法中就可以正確取出這些資訊並且處理了。



總結:

結束這文章時候,我真是非常開心,其實這個問題我已經想了半年沒想通,不過今天終於想通了。本來這個問題是我們團隊一個叫Danny的問我了,我當時研究了沒解決,後來我在Liferay官網上掛了幾個月沒人能解答,真開心我還是靠自己的實力解決了。

(1)頁面上用<portlet:resourceURL>對應的請求url最終會被映射到PortletServlet中進行處理,這個目的是吧 Portlet的對於請求處理能力的地位提升到Servlet層級,因為它現在可以接受 HttpServletRequest類型的請求了,這個PortletServlet會先從HttpServletRequest中獲得portletId,portletRequest,portletResponse和lifecycle資訊,然後根據lifecycle階段資訊,相應的吧PortletRequest轉為何種請求類型,比如如果lifecycle是RESOURCE_PHASE,那麼它會吧portletRequest轉為ResourceRequest,它包含了<portlet:resourceURL>中所有附帶參數。然後在doFilter方法中,它吧我們的portlet轉為ResourceServingPortlet 並且調用serveResource()方法,於是可以就可以正確的調用我們在portlet應用程式層面定義的serveResource()方法了。

(2)這個PortletServlet並不是開始就定義在我們的項目打的war包中的,而是在部署war包到liferay部署目錄後,liferay架構自己添加的一段代碼,具體細節見上一篇文章:http://supercharles888.blog.51cto.com/609344/1286976

(3)但是最重要的一點是,我們的頁面的<portlet:actionURL>並不直接對應到PortletServlet的url-mapping中,這也是困擾我半年多的問題。其實,它是先走到InvokerFilter中,然後在執行/c/portal/layout時候,它會走到struts架構,然後經過一系列漫長的調用,最終在InvokerPortletImpl的invoke()方法中得到解決了,它會產生一個新path類似  /<portlet-name>/invoke, 然後把所有portlet相關資訊包括portlet,lifecycle,filterchain)添加到HttpServletRequest對象中,並且建立一個RequestDispatcher吧當前請求轉寄到剛才的新path中,這樣就可以讓請求去匹配PortletServlet的url-pattern並且進入PortletSevlet了。

本文出自 “平行線的凝聚” 部落格,請務必保留此出處http://supercharles888.blog.51cto.com/609344/1287188

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.