Apache OFBiz源码解读之MVC模型

节点解析

request-map

你可以将其理解为controller的配置,如果你了解或使用过struts的配置或springmvc的annotation,就会发现这个定义跟它们是很相似的:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<request-mapuri="createCreditCardAndPostalAddress">

<securityhttps="true"auth="true"/>

<eventtype="service"path=""invoke="createCreditCardAndAddress"/>

<responsename="success"type="request"value="finalizeOrder"/>

<responsename="error"type="view"value="billsetting"/>

</request-map>

该元素定义了请求的映射关系。它使用名为uri的属性,表述该uri将要映射的请求。内部包含三个常用的子元素,分别是:security,event,response。

security:表示该uri应该对应的安全级别(是否应该是https的,是否要进行权限检查)

event:该请求触发的事件,这个后面在讲解handler的时候再谈

response:指定响应的配置

view-map

一个常见的view-map配置:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<view-mapname="billsetting"type="screen"page="component://order/widget/ordermgr/OrderEntryOrderScreens.xml#BillSettings"/>

包含的属性:

name:当前view-map的名称,通常被<request-map>子元素<response>的value属性引用

type:其表示用什么技术展示视图,通常为screen,该值其实引用的是后面要讲解的handler

page:指定真实用于前端展示的视图布局文件

handler

在OFBiz中大致会划分两种类型的handler:event和screen。其实个人认为此处将handler理解为engine更为贴切一点,因为叫handler很容易跟业务联系到一起,而如果称之为engine则可以完全跟业务隔离开来,它只是纯技术组件而已。看看handler的定义就很容易理解了:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<!--eventhandlers-->

<handlername="java"type="request"class="org.ofbiz.webapp.event.JavaEventHandler"/>

<handlername="soap"type="request"class="org.ofbiz.webapp.event.SOAPEventHandler"/>

<handlername="xmlrpc"type="request"class="org.ofbiz.webapp.event.XmlRpcEventHandler"/>

<handlername="service"type="request"class="org.ofbiz.webapp.event.ServiceEventHandler"/>

<handlername="service-multi"type="request"class="org.ofbiz.webapp.event.ServiceMultiEventHandler"/>

<handlername="service-stream"type="request"class="org.ofbiz.webapp.event.ServiceStreamHandler"/>

<handlername="simple"type="request"class="org.ofbiz.webapp.event.SimpleEventHandler"/>

<handlername="groovy"type="request"class="org.ofbiz.webapp.event.GroovyEventHandler"/>

<handlername="rome"type="request"class="org.ofbiz.webapp.event.RomeEventHandler"/>

<handlername="script"type="request"class="org.ofbiz.webapp.event.ScriptEventHandler"/>

<!--viewhandlers-->

<handlername="screen"type="view"class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>

<handlername="screenxml"type="view"class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>

<handlername="screentext"type="view"class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>

<handlername="screencsv"type="view"class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>

<handlername="screenfop"type="view"class="org.ofbiz.widget.screen.ScreenFopViewHandler"/>

<handlername="jsp"type="view"class="org.ofbiz.webapp.view.JspViewHandler"/>

<handlername="ftl"type="view"class="org.ofbiz.webapp.ftl.FreeMarkerViewHandler"/>

<handlername="http"type="view"class="org.ofbiz.webapp.view.HttpViewHandler"/>

<handlername="birt"type="view"class="org.ofbiz.birt.webapp.view.BirtViewHandler"/>

handler包含的属性:

name:指定handler的名称,通常会被<request-map>子元素的event的type属性以及<view-map>的type属性所引用

type:有两种取值:request和view。request应对的是<request-map>中的event的处理器;view对应的是<view-map>的处理器

class:指定当前处理器实现类的完全限定名

mvc串接

下面我们以OFBiz收到一个请求为示例,展示其利用MVC模型处理请求的完整过程:

首先我们假设OFBizwebcontainer收到请求:createCreditCardAndPostalAddress。然后OFBiz会根据每个app下面的controller配置文件中request-map定义,查找并匹配uri为createCreditCardAndPostalAddress的映射节点(就是上文中讲解request-map使用的节点)。

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<request-mapuri="createCreditCardAndPostalAddress">

然后根据其子元素security的配置,对其进行安全检查:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<securityhttps="true"auth="true"/>

因为有event元素,那么此处会触发一个“事件”(注意,不一定会有event元素)。这里是通过ofbiz的ServiceEngine来调用一个service:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<eventtype="service"path=""invoke="createCreditCardAndAddress"/>

调用完该service后,根据service执行的结果,匹配不同的响应视图:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<responsename="success"type="request"value="finalizeOrder"/>

<responsename="error"type="view"value="billsetting"/>

这里(也是通常情况下)有两种不同的响应配置:success,error。而且他们的响应方式不同,我们分开来看:

如果event触发调用createCreditCardAndAddress服务的返回结果为success,那么将触发一个请求(type为request表示再次触发一个请求,但这个请求是服务端的请求,有点像servlet里的forward动作),uri为finalizeOrder(它是另一个request-map的定义):

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<request-mapuri="finalizeOrder">

其语义为:完成订单创建。

如果event触发调用createCreditCardAndAddress服务的返回结果为error,那么它将会向浏览器展示一个视图(type为view),而该视图的名称为:billsetting。那接下来ofbiz就去查找名为:billsetting的view-map,查找到如下的结果:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<view-mapname="billsetting"type="screen"page="component://order/widget/ordermgr/OrderEntryOrderScreens.xml#BillSettings"/>

发现它是一个widget配置(type为screen表示OFBiz中采用的一种xml的widget),而该配置的路径为:component://order/widget/ordermgr/OrderEntryOrderScreens.xml文件中名称为BillSettings的screen。然后就利用名为screen的handler,来解析该screen配置:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<handlername="screen"type="view"class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>

screen

上面提到ofbiz在渲染视图的时候,采用了一个元素名为screen的配置:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<screenname="BillSettings">

<section>

<actions>

<setfield="stepTitleId"value="OrderOrderEntryPaymentSettings"/>

<setfield="stepLabelId"value="AccountingPayment"/>

<scriptlocation="component://order/webapp/ordermgr/WEB-INF/actions/entry/BillSettings.groovy"/>

</actions>

<widgets>

<decorator-screenname="CommonOrderCheckoutDecorator">

<decorator-sectionname="body">

<platform-specific>

<html><html-templatelocation="component://order/webapp/ordermgr/entry/billsettings.ftl"/></html>

</platform-specific>

</decorator-section>

</decorator-screen>

</widgets>

</section>

</screen>

这牵扯到OFBiz前端screen以及form的widget布局设计。

section

它是screen的子元素,一个screen可以包含n个section。而它可以又会由actions以及widgets元素组成。

action

在actions元素下,你可以定义若干个不同种类的action:

label:将值绑定到label

entityaction:利用entityengine进行action操作

setaction:简单的赋值以及groovy表达式语法

scriptaction:调用一个groovy脚本

serviceaction:调用一个服务

widgets

widgets是OFBiz布局的特点之一,它可以将一个完整的html页面拆分为一个个小块的widget,最终的页面是通过widget组合而成。

这里首先定义了一个名为:CommonOrderCheckoutDecorator的decorator-screen。所谓的decorator-screen你可以将其理解为页面的模板或者占位。比如,就一个页面而言,部分内容与空间是固定的,主要变化的是某个特定的区域。此时布局一个新页面的时候就没必要为其每个区域都重复编写html,对于公共区域直接引用已经定义好的模板即可。

比如此处的CommonOrderCheckoutDecorator装饰器screen,其定义中,它又引用了该app的一个main-decorator:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<decorator-screenname="main-decorator"location="${parameters.mainDecoratorLocation}">

这个是当前app最外层的装饰器模板。这样就形成了widget的两层嵌套关系:BillSettings引用了CommonOrderCheckoutDecorator,而CommonOrderCheckoutDecorator又引用了main-decorator,这种嵌套关系,也同时建立了页面显示的联系。

一个通常的应用,其mainDecoratorLoaction参数可以在其web.xml中的context-param配置中找到:

[html]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

<context-param>

<param-name>mainDecoratorLocation</param-name>

<param-value>component://order/widget/ordermgr/CommonScreens.xml</param-value>

<description>Thelocationofthemain-decoratorscreentouseforthiswebapp;referredtoasacontextvariableinscreendefXMLfiles.</description>

</context-param>

回到正题,在BillSettings的第一个decorator-screen:CommonOrderCheckoutDecorator,还有一个decorator-section:body,它是对内容区域的模板占位。

widget内部拥有一个platform-specific子元素,它可以看做是一种switch-case语句。OFBizwidget工具集没用对renderhtmlUI的方式进行限制。理论上,你可以采用任何技术来render浏览器能显示内容。在这里UI被render成HTML,而且还使用了html模板,该模板的路径通过location属性指定。此处该模板使用的是freemarker(这也是OFBiz中用得最多的一种模板技术)。

数据绑定

就前端展现而言,除了需要有由html标签组成的模板,还需要绑定数据才能形成完整的页面。

OFBiz提供了两种绑定数据的方式:

form-action:如果你使用formwidget,绑定数据的最好方式是采用action子元素。这可以有效得帮助你增强该form的复用性。

groovyscriptaction:如果你使用的是freemarker模板,此时form便是由html原生标签表示,那么推荐的方式是采用groovy脚本来获取数据并绑定至模板来显示,本文所举的示例就是这种模式。

相关推荐