(实战)Spring Portlet MVC处理请求分析以及如何快速架构一个Portlet项目

  1. 云栖社区>
  2. 博客>
  3. 正文

(实战)Spring Portlet MVC处理请求分析以及如何快速架构一个Portlet项目

余二五 2017-11-15 19:35:00 浏览738
展开阅读全文

引入:

很多人不是很熟悉springportlet mvc ,如果项目组要他们去搭建一个portlet应用时候,往往是在网上依葫芦画瓢搭建一个例子,但是网上的配置往往错综复杂,自己去照着弄一个,十有八九会错,而且这种搭建架子多数是靠配置文件的堆叠,而配置文件很难调试,所以出了错也很难去解决。我们这里就给出一个搭建springportlet mvc应用的实战例子。因为所有portlet都有view模式,所以我们就以发送一个render请求为例,看下Spring Portlet MVC内部工作原理,它需要什么文件和配置,我们就创建相应文件或者配置。


调试分析和实践:

(1)创建DispatcherPortlet的子类作为Portlet类,它充当前端控制器

为了搭建SpringPortlet MVC,我们必须要知道在springportlet mvc生态圈中最重要的类:DispatcherPortlet.我们都知道,DispatcherPortlet继承自GenericPortlet,它是sprint portlet mvc的前端控制器。(为什么这么说,可以参见http://supercharles888.blog.51cto.com/609344/1287188,所以一旦我们创建了一个Portlet 类,那么这个Portlet类就自动转为PortletServlet,并且它的并且它的url mapping/<portletdisplay name>/*所以它可以作为portlet中各种请求的前端,所以我们创建一个Portlet类让其继承自DispatcherPortlet就可以了,如下:

145415220.png

你肯定会问,难道这个Portlet什么都不要么?你说对了,这个Portlet仅仅是DispatcherPortlet的直接子类,不需要额外任何细节,因为它只充当前端控制器用于接收任意请求,后续处理都在子类DispatcherPortlet的相应方法中了。

实践步骤总结:

  • 创建一个空的Portlet让其继承自DispatcherPortlet。


(2)创建HandlerMappingbean和二级控制器Handler

因为我们知道Portlet一旦接收到请求(假定是render请求)之后,它自己是不会处理请求的,它只是前端控制器,所以它会交给具体的处理器Handler来执行,它会通过getHandler()方法来找到对应的Handler.

145722574.png从这里可以看出来,它会从当前Portlet应用配置的handlerMappings进行遍历,直到找到所需要的Handler. 从上述970行我们看到,this.handlerMappings是一个数组,也就是说,我们一个spring 应用可以定义多个HandlerMapping.(因为现在spring注解很流行,所以我们需要一个基于注解的HandlerMapping, 也就是我们调试信息中的DefaultAnnotationHandlerMapping. 并且把它定义为一个beanportletspring应用上下文中。并且在下面定义多个<component-scan>来表示对那些包,类进行扫描以获取被标注的类。)

150016412.png

但是从第977行可以看到,一旦找到了匹配的某个Handler,就直接从循环中跳出了,所以概括就是虽然可以定义多个HandlerMapping,但是最多只会有一个Handler被用来处理当前请求,一旦找到这个Handler,就不会再去试着寻找其他Handler了。


那么它是如何根据HandlerMapping找到合适的handler呢?其过程是封装在上述第975行的getHandler(PortletRequest)方法中,我们看下实现。

150143496.png


从上面不难看出,它会调用getHandlerInternal(PortletRequest)方法来找寻Handler,如果找不到,就返回defaultHandler,运气再不好,只能返回null了。所以通常情况下应该在getHandlerInternal(PortletRequest)方法中就可以找到相应的Handler了。

那么这个通用的找Handler的方法是根据什么原则查找呢?我们看下getHandlerInternal(PortletRequest)的实现:

150248867.png

很清楚,在71行,它会先获取查询Key,从调试信息可以看到是PortletMode ,然后在72行它会在handlerMap中根据PortletMode来查找相应的Handler。这就启发我们必须定义一个Handler类,并且对应上相应的PortletMode,因为我们用基于注解的spring开发,所以一切都用注解来实现,并且spring 中,Controller会充当Handler的作用。所以我们创建了一个Controller类,并用@Controller标注它为Handler, @RequestMapping(“模式”)来标注这个控制器用于何种Portlet模式。

150333857.png

实践步骤总结:

  • 需要一个或者多个HandlerMappingbean,因为我们用基于注解的Spring开发,所以我们必须在portlet spring上下文中定义DefaultAnnotationHandlerMapping并且在下面定义多个<component-scan>来表示对那些包,类进行扫描以获取被标注的类。

  • 为了让各种请求有相应的Handler处理,我们必须定义自己的Controller类来充当Handler。因为前面已经配置了DefaultAnnotationHandlerMappingcomponent-scan,所以我们必须在扫描能覆盖的包中定义一个Controller类,并且用@Controller 标注和@RequestMapping(“模式”)标注给出



(3)细化Handler

一旦找到了Handler类,我们就需要调用Handler类的相应方法来处理请求了。我们回到DispatcherPortlet,因为我们开始在步骤2中假定了它是render请求,所以我们看下doRenderService()方法.


首先,在找到了mappedHandler之后,它会先获取这个handler的拦截器(HandlerInterceptor),并且对其中的每个拦截器都调用preHandle()方法。

150702642.png

因为不是每个Handler都必须有拦截器配置的,所以这不属于我们搭建基础框架的范畴,只能作为一个高级特性的扩展,所以我们略去(关于这个特性,我会在后续博客中分析),也略去postHandle()部分


然后,它会具体调用Handler上的handleXXX方法:

150757746.png所以对于我们的Handler,必须定义一组handleXX方法 ,因为我们的请求类型不仅仅有render,还有resource, action等,所以我们不得不为每种请求类型都加一个handleXXX方法,并且都用 @RenderMapping,@ResourceMapping,@ActionMapping等注解标注在这些handleXXX方法上。

150841497.png实践步骤总结:

  •  Handler类中定义一组handleXXX方法,然后用@RenderMapping,@ResourceMapping,@ActionMapping等注解标注对应的方法上。


(4)处理Handler的返回结果ModelAndView-解析视图名字和渲染视图。

假定现在Handler已经处理完了当前请求,那么如何处理返回结果呢?

我们回到DispatcherPortlet中的doRenderService()方法中,可以看到,在步骤(3)调用handleRender进而委托Handler处理完事件并封装结果到ModelAndView之后,它会对这个ModelAndView中的内容进行处理:

151135833.png

处理的逻辑在render(ModelAndView,PortletRequest,MimeResponse)方法中,我们看下实现:


第一步:解析视图名字

151220812.png


而解析的方式是封装在resolveViewName()方法中:

151317700.png


这里实现很容易,它就是遍历当前PortletviewResolver数组,然后让其根据视图的逻辑名字(viewName)和当前请求的locale来获取真正的视图。所以我们必须为当前项目定义一个或者多个viewResolverbean. 根据Spring的官方说明,最简单的ViewResolverInternalResourceViewResolver,在采用这个ViewResolver之前,我们来看下这个ViewResolver类是如何解析视图的,它最终会调用AbstractUrlBasedViewbuildView()方法来创建View对象:

151407746.png


从这里可以看出,它会把viewurl设为前缀+viewName+后缀的形式,所以如果是一般的应用,你不想隐藏jsp页面,并且想吧通常的view模式对应的页面定义在html目录下的view.jsp,那么你的viewResolver设置就应该如下:

151457272.png


所以你需要在HandlerhandleRendleRequest方法返回的ModelAndView中,吧逻辑名字设为 html/view,这样/ +“html/view” + “.jsp就可以拼接为正确的页面url(/html/view.jsp)

然后你要在项目的应用根目录下新建html目录,并且在其下面创建一个view.jsp子页面作为view模式的对应视图页面。

Note:当然了,你如果需要隐藏这些视图,那么可能需要把他们放在WEB-INF下面,那么通用的做法是吧prefix设为 /WEB-INF/jsp, 并且创建对应的目录。


第二步:渲染视图

我们回到DispatcherPortletrender(ModelAndView,PortletRequest,MimeResponse)方法:

在利用完ViewResolver解析出正确的目标视图之后,现在框架就考虑如何吧ModelAndView中的数据部分填充到目标视图中并且显示,也就是渲染视图。


在正式渲染视图之前,它会设置相应类型,具体做法是:它会从view中获取contentType,并且设置为response的内容类型,因为只有给出contentType,才能告诉框架以何种方式来渲染内容。

151627162.png

因为我们的view定义了contentTypetext/html

151718553.png

所以这让框架最后以一个html文本页面的形式来展示内容,这和我们设想的一样。


然后它基于第一步已经得到的View对象,以及存储视图需要数据的Model对象。调用doRender方法来正式渲染view中的页面。

151801126.png

它的实现如下:

151834640.png


可以看出,很简单,它最终会用RequestDispatcher来转发请求到this.viewRendererUrl对应的url上用于渲染。而这个viewRendererUrl是定义如下:

151905704.png


所以我们的目标明确了,我们需要定义一个ViewRendererServlet,并且吧它的url-pattern设为 /WEB-INF/servlet/view.因为servlet不可以定义在portlet spring 上下文中,所以它只能定义在web.xml中,如下:

151944722.png


我们测试,果然,最终负责render视图的方法是ViewRenderServletrenderView()方法:

152012100.png


实践步骤总结:

  • Handler方法返回ModelAndView中给出逻辑视图的名字(“html/view”) ,也就是 return new ModelAndView(“html/view”);

  • 在项目的根目录下创建相应的文件结构,比如创建html目录,在其下创建view.jsp文件作为视图的页面。

  • portlet spring上下文文件中给出InternalResourceViewResolverbean定义,特别注意到prefix,suffix必须和你创建的目录结构和文件扩展名吻合。

  • web.xml中给出ViewRenderServlet<servlet>定义,并且它的<url-pattern>设为/WEB-INF/servlet/view


总结:

我们这个例子是一个实战例子,它不仅分析了Spring Portlet MVC响应Portlet请求的全过程,而且指导读者如何快速搭建一个基于Spring Portlet MVC的应用。我们分2部分来总结:

a. Spring Portlet MVC响应Portlet 请求的全过程知识点:

(1)我们的自定义Portlet类会直接继承自DispatcherPortlet,它会作为所有Portlet请求的前端控制器。

(2)DispatcherPortlet一旦接收到Portlet 请求,它自己不会去处理请求,而是会交给具体的二级控制器Handler来处理具体请求。

(3)如何找到具体的二级控制器 Handler呢?这个实现封装在DispatcherPortlet的getHandler()方法中。它会借助HandlerMapping来完成。一个Portlet项目可以配置多个HandlerMapping, getHandler()中会遍历循环所有HandlerMapping,只要找到一个匹配的Handler,就不会再往下找了。

(4)HandlerMapping如何去判断一个Handler是否是满足当前需要的Handler呢?这通过当前的PortletMode来查询Handler映射。我们鼓励采用基于注解的Spring,所以一个可选的HandlerMapping的bean是DefaultAnnotationHandlerMapping,如果用这个HandlerMapping的时候我们必须开启组件扫描<component-scan>,并且吧相应的Handler类用注解标识上。

(5)一旦找到了目标Handler后,则会调用该Handler的handleXXX方法来根据请求类型来进行处理。在Spring Portlet MVC中,Controller会充当Handler. 调用handleXXX处理的前后,如果为这个Handler配置了拦截器,则会调用handleXXX前后分别调用拦截器Interceptor的preHandle()和postHandle()方法。

handleXXX处理的结果会填充到ModelAndView对象中。

(6)在得到了ModelAndView对象后,DispatcherPortlet会解析视图名和渲染视图。解析视图名是利用的ViewResolver类的resolveViewName()方法,一个项目可以配置多个ViewResolver。最简单的是InternalResourceViewResolver类,它会用 “前缀”+“视图逻辑名”+"后缀“拼接的方法来根据视图逻辑名来找到对应的视图文件。因为视图逻辑名的信息会封装在ModelAndView中,所以我们必须在文件系统中创建相应的页面文件来匹配逻辑视图名。如果要隐藏这些资源文件,那么吧前缀设为/WEB-INF下的某个自定义目录

(7)在解析完视图后,DispatcherPortlet就会尝试渲染视图了,它会用RequestDispatcher forward到ViewRenderServlet来完成这件事情。为了调用ViewRenderServlet,我们必须在web.xml中声明这个Servlet,并且url-pattern设置为/WEB-INF/servlet/view,这样我们的请求信息(包括视图和模型)就会全部包含在HttpServletRequest中,并且交给ViewRenderServlet的renderView()方法来完成具体的视图渲染工作。


b.快速架构一个Spring Portlet 项目的实践步骤:

其实如果联系所有部分的”实践步骤总结“,我们就可以很快的得到快速架构Spring Portlet项目的实践步骤了。

a.在java包中定义我们的Portlet 类,让其继承自DispatcherPortlet.

b.定义若干个Handler类,每个Handler类都用@Controller 和@RequestMapping(”Portlet模式“)标注。

c.在每个Handler类中定义多个handleXXX方法,并且都相应标注上@RenderMapping,@ResourceMapping,@ActionMapping

d.在Portlet的Spring 配置文件中,定义一个或者多个bean 作为HandlerMapping,如果要启用注解,那么可以定义DefaultAnnotationHandlerMapping ,并且下面用<component-scan>元素来配置对那些组件进行扫描注解。

e.在Portlet的Spring配置为文件中,定义一个或者多个viewResolver用来解析视图,如果用InternalResourceViewResolver,那么必须给出prefix和suffix属性用于匹配充当视图的资源文件的前缀和后缀,并且要和真实文件结构匹配。

f.在web.xml中,定义一个Servlet叫ViewRenderServlet,并且配置它的url-pattern为 /WEB-INF/servlet/view.

g.吧其他的平台无关的配置文件(比如portlet.xml) 和平台相关的配置文件(比如基于liferay的portlet开发则为liferay-plugin-package.xml,liferay-display.xml和liferay-portlet.xml)都复制到WEB-INF目录下.

h.测试运行检验之。





本文转自 charles_wang888 51CTO博客,原文链接:http://blog.51cto.com/supercharles888/1298217,如需转载请自行联系原作者

网友评论

登录后评论
0/500
评论
余二五
+ 关注