引入:

大家在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,

发现它不再是原来的那个web.xml了,它有PortletServlet的定义(这里出于security考虑,我吧包名最前面部分去掉了):

所以,这个Portlet相当于一个桥接,它把本来隶属于Portal的一个个的Portlet地位提升上去,提升到一个一个Servlet,这样他们就可以独立的负责响应各种请求了。

而这个Servlet的mapping是:



现在,当我们页面上有个Search按钮。点击会触发如下的<portlet:resourceURL>:

我们可以看到,这个<portlet:resourceURL>标记会被liferay-portlet.tld所识别:

所以最终处理这个标记的类是ResourceURLTag和ResourceURLTei.

它会最终被处理类转为一个请求url:这个请求 url是:

撇开细节,最后因为它的请求url满足/logearchportlet/*这个url模式,

(你肯定会问,这个http://172.29.175.236:8080/web/guest/log-search?......这个url明显不匹配PortletServlet的模式/logsearchportlet/*嘛,因为PortletServlet的模式如下图所示:

那么,我们的请求url是如何进的这个portlet呢?关于这点,我想了整整2天,才想明白,我在后面的精华疑点解答中会提到)

所以它最终会走到PortletServlet方法中。

首先在第64行中从HttpServletRequest获得portletId:

然后在第66-70行分别从HttpServletRequest/Response中获取 PortletRequest和 PortletResponse对象,然后在第72行获取当前请求对应的请求的生命周期阶段LIFECYCLE_PHASE:

(疑点2:这里为什么portletRequest,portletResponse,lifecycle信息都在HttpServletRequest中,何时设置上去的,关于这个问题,参见精华疑点解答)

然后从第78-90行从portletRequest中PortletSession对象,并把PortalSession关联到PortletSession中。

然后第40行调用PortletUtilFilter.doFilter()方法:

它会根据当前lifecycle的值来判断吧PortletRequest转为何种请求类型:

因为我们从调试信息中看出,当前lifecycle是“RESOURCE_PHASE",所以它会吧PortletRequest转为ResourceRequest.然后在第71行继续调用filterChain的doFilter方法。

这次,它会去先把我们的portlet转为ResourceServingPortlet,这里是我们的LogSearchPortlet,再调用我们的LogSearchPortlet的serveResource方法:

而所有的我们在构造<portlet:resourceURL>时候附带的参数都会被封装在ResourceRequest

从以下截图中可以看出,<portlet:resourceURL>中的所有参数都会被添加到ResourceRequest中,一个不少:

而我们在portlet中的代码已经实现了serveResource方法,所以就可以正确的调用执行了。

精华疑点解答1:

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

这个问题很复杂,但是我们可以猜想,肯定在请求送达PortletServlet之前进行了若干预处理。我们知道,过滤器总是在Servlet之前执行的,而我们的请求,刚好可以符合 Invoker Filter的url-mapping .

而这个InvokerFilter,如果熟悉它的代码,会发现它其实会去按照每个Filter链上Filter的定义,依次去调用各个Filter的doFilter方法,当然了,这些Filter根据我们以前的研究内容,都是定义在liferay-web.xml中。

看到第一次,请求是/web/guest/logsearch,果然和我们的匹配,然后它会走一些filter,最后调用invokerFilterChain.doFilter(servletRequest,servletResponse).

第二次,我们进入这个方法时候,请求就变了,变为/c/portal/layout.

这里省略很多不重要步骤,因为我们在struts-config.xml中定义了/c/portal/layout的action-mapping,如下:

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

它会在第244行调用重载的processLayout()方法:

然后在第663-665行它会去调用processPortletRequest方法:

跳过漫长的一段代码(和我们研究重点无关的代码),最终它在porcessPortletRequest的第899行,判断lifecycle是”RESOURCE_PHASE",所以进入这个分支:

再跳过N多不相关行,看到最后它会通过ServletRequest,ServletResponse 构造ResourceRequestImpl和ResourceResponseImpl对象,并且第936行通过ResourceRequestImpl创建并且封装一个ServiceContext对象,看右边的调试信息可以看到我们的请求url是封装在这个ServiceContext对象的。(_currentURL属性),然后我们把这个ServiceContext对象加到ThreadLocal列表中。

最后,在第941行调用InvokerPortlet的serveResource方法,它会最终调用InvokerPortletImpl的invoke()方法:

而我们访问invoke()方法时候,谜底终于揭开了,都给我睁大眼睛看清楚了:

原来,它会在第610行通过PortletConfigImpl获取Portlet的名字,我们获得是"logsearchportlet",然后把它拼接到后面的/invoke字符串就得到了这个path 为"/logsearchportlet/invoke",然后它创建一个RequestDispatcher对象用于转发请求,最后,如下图所示:

它会吧请求转发到path指定的/logsearchportlet/invoke, 而这个请求url显然是匹配/logsearchportlet/*的,所以就可以正确的进入到 PortletServlet了,于是这个问题得到圆满解决。


精华疑点解答2:

在PortletServlet服务于当前请求中的service方法中,为什么portletRequest,portletResponse,lifecycle信息都在HttpServletRequest中?

当我们分析完刚才整个过程中时,这个问题也迎刃而解了,参见InvokerPortletImpl的invoke()方法的第623行到第626行,

在创建好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了。

转载于:https://blog.51cto.com/supercharles888/1287188

Liferay中使用portlet:resourceURL触发serveResource()方法调用的细节相关推荐

  1. Android中使用ContentProvider进行跨进程方法调用

    原文同一时候发表在我的博客 点我进入还能看到很多其它 需求背景 近期接到这样一个需求,须要和别的 App 进行联动交互,比方下载器 App 和桌面 App 进行联动.桌面的 App 能直接显示下载器 ...

  2. android studio 跨进程,Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用...

    本文首发于微信公众号「后厂技术官」 在上一篇文章Android IPC机制(二)用Messenger进行进程间通信中我们介绍了使用Messenger来进行进程间通信的方法,但是我们能发现Messeng ...

  3. java调用怎么调用方法区_Java中的方法调用有多昂贵

    java调用怎么调用方法区 我们都去过那儿. 在查看设计不良的代码的同时,听听作者对人们永远不应该牺牲性能而不是设计的解释. 而且,您不能说服作者摆脱其500行方法,因为链接方法调用会破坏性能. 好吧 ...

  4. Java中的方法调用有多昂贵

    我们都去过那儿. 在查看设计不良的代码的同时,听听作者对人们永远不应该牺牲性能而不是设计的解释. 而且,您不能说服作者摆脱其500行方法,因为链接方法调用会破坏性能. 好吧,这可能在1996年左右是正 ...

  5. 基础强化:深入理解JVM中的方法调用

    作者:xiaolyuh my.oschina.net/xiaolyuh/blog/3168216 方法调用并不等同于方法中的代码被执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法 ...

  6. 2021年大数据常用语言Scala(十二):基础语法学习 方法调用方式

    目录 方法调用方式 后缀调用法 中缀调用法 操作符即方法 花括号调用法 无括号调用法 方法调用方式 在scala中,有以下几种方法调用方式, 后缀调用法 中缀调用法 花括号调用法 无括号调用法 在后续 ...

  7. JVM学习笔记之-运行时数据区概述及线程概述,程序计数器(PC寄存器),虚拟机栈(栈,局部变量表,操作数栈,动态连接,方法调用,方法返回地址等),本地方法接口,本地方法栈

    运行时数据区概述及线程概述 内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行.JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JV ...

  8. 多态方法调用的解析和分派

    方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程.在程序运行时,进行方法调用是最普遍.最频繁的操作,Class文件的编 ...

  9. java 多态实现的jvm调用过程_多态:JVM是如何进行方法调用的

    在我们平时的工作学习中写java代码时,如果我们在同一个类中定义了两个方法名和参数类型都相同的方法时,编译器会直接报错给我们.还有在代码运行的时候,如果子类定义了一个与父类完全相同的方法的时候,父类的 ...

最新文章

  1. MATLAB神经网络样本数量太大,MATLAB搭建bp神经网络的误差特别大,但R2的值也大,求解帮我看看我的网络哪里出问题了...
  2. 智能市场变革,独辟蹊径的机器人营销
  3. js控制文本框中的字符数
  4. 颜宁问4对科研夫妻:男女搭配,如何不累?
  5. 《RabbitMQ 实战指南》第三章 客户端开发向导
  6. php++数据库备份,php实现数据库备份
  7. python有趣小程序-Python里的17个quot;骚操作”好玩有趣
  8. rest_framework 权限功能
  9. linux下将多个文件去除文件头合并_使用 PDF Mix Tool 执行常见的 PDF 编辑任务 | Linux 中国...
  10. c语言流程图形状什么意思,Visio中的基本流程图的各个形状的含义是什么?
  11. esp32+超声波传感器测距
  12. 东欧小哥打造超全Python速查表登上GitHub热榜,标星4600+
  13. 单片机 MSP430 串口 计算 波特率
  14. xp计算机图标,xp我的计算机图标不见了
  15. c# 图片裁剪并画中线
  16. 2021年起重机械指挥考试题及起重机械指挥模拟考试
  17. 修改mysql密码级别_mysql修改密码策略
  18. 删除Github中已有仓库或文件
  19. html将div放到页面最底部,关于html:我如何将div放在容器的底部?
  20. telnet远程登录服务器端口,telnet端口号-TELNET服务的端口号是多少?

热门文章

  1. Github(1)-概览,初始化仓库
  2. kaggle(01)-泰坦尼克号问题
  3. linux版车机安装步骤,RedHat Linux 9.0的安装(详细图解安装过程)
  4. 代码重构 防火墙 相关知识
  5. 中科大 计算机网络9 互联网历史
  6. 研究:多感官教学增强记忆 学习效率事半功倍
  7. 聊一聊 cookie
  8. SSH、SSL与HTTPS
  9. @RequiresPermissions 注解说明
  10. es6之数据结构 set,WeakSet,mapWeakMap