这篇文章我们来共同探讨一下Apache OFBiz的MVC模型的实现机制。
对于Model-View-Controller的概念此处就不做过多介绍了。如果有想了解的请移步:维基百科。这里我只引用维基百科上关于该item的一张图片,来简单展示一下,这三个component之间的交互机制:
OFBiz实现MVC是通过XML来串联这三者之间的依赖关系。这里牵扯到几个关键的XML元素:<request-map />,<view-map />,<handler />。这三个XML元素的定义都位于applications/XXX/webapp/WEB-INF/controller.xml中。
从这个文件的命名来看,给人的第一感觉这似乎只是对controller的定义。但准确得说,它定义的是他们之间的映射关系,不仅包含了请求的映射关系,同时还包含了视图的映射关系,以及一系列的处理器比如视图解析处理器,事件处理器等。
我们先来分析每个元素,然后再将它们串联起来,看看它们联合起来是如何工作的。
你可以将其理解为controller的配置,如果你了解或使用过struts的配置或springmvc的annotation,就会发现这个定义跟它们是很相似的:
<request-map uri="createCreditCardAndPostalAddress"> <security https="true" auth="true"/> <event type="service" path="" invoke="createCreditCardAndAddress"/> <response name="success" type="request" value="finalizeOrder"/> <response name="error" type="view" value="billsetting"/> </request-map>
<view-map name="billsetting" type="screen" page="component://order/widget/ordermgr/OrderEntryOrderScreens.xml#BillSettings"/>
<!-- event handlers --> <handler name="java" type="request" class="org.ofbiz.webapp.event.JavaEventHandler"/> <handler name="soap" type="request" class="org.ofbiz.webapp.event.SOAPEventHandler"/> <handler name="xmlrpc" type="request" class="org.ofbiz.webapp.event.XmlRpcEventHandler"/> <handler name="service" type="request" class="org.ofbiz.webapp.event.ServiceEventHandler"/> <handler name="service-multi" type="request" class="org.ofbiz.webapp.event.ServiceMultiEventHandler"/> <handler name="service-stream" type="request" class="org.ofbiz.webapp.event.ServiceStreamHandler"/> <handler name="simple" type="request" class="org.ofbiz.webapp.event.SimpleEventHandler"/> <handler name="groovy" type="request" class="org.ofbiz.webapp.event.GroovyEventHandler"/> <handler name="rome" type="request" class="org.ofbiz.webapp.event.RomeEventHandler"/> <handler name="script" type="request" class="org.ofbiz.webapp.event.ScriptEventHandler"/> <!-- view handlers --> <handler name="screen" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/> <handler name="screenxml" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/> <handler name="screentext" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/> <handler name="screencsv" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/> <handler name="screenfop" type="view" class="org.ofbiz.widget.screen.ScreenFopViewHandler"/> <handler name="jsp" type="view" class="org.ofbiz.webapp.view.JspViewHandler"/> <handler name="ftl" type="view" class="org.ofbiz.webapp.ftl.FreeMarkerViewHandler"/> <handler name="http" type="view" class="org.ofbiz.webapp.view.HttpViewHandler"/> <handler name="birt" type="view" class="org.ofbiz.birt.webapp.view.BirtViewHandler"/>
<request-map uri="createCreditCardAndPostalAddress">
<security https="true" auth="true"/>
<event type="service" path="" invoke="createCreditCardAndAddress"/>
调用完该service后,根据service执行的结果,匹配不同的响应视图:
<response name="success" type="request" value="finalizeOrder"/> <response name="error" type="view" value="billsetting"/>
如果event触发调用createCreditCardAndAddress服务的返回结果为success,那么将触发一个请求(type为request表示再次触发一个请求,但这个请求是服务端的请求,有点像servlet里的forward动作),uri为finalizeOrder(它是另一个request-map的定义):
<request-map uri="finalizeOrder">
如果event触发调用createCreditCardAndAddress服务的返回结果为error,那么它将会向浏览器展示一个视图(type为view),而该视图的名称为:billsetting。那接下来ofbiz就去查找名为:billsetting的view-map,查找到如下的结果:
<view-map name="billsetting" type="screen" page="component://order/widget/ordermgr/OrderEntryOrderScreens.xml#BillSettings"/>
<handler name="screen" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>
上面提到ofbiz在渲染视图的时候,采用了一个元素名为screen的配置:
<screen name="BillSettings"> <section> <actions> <set field="stepTitleId" value="OrderOrderEntryPaymentSettings"/> <set field="stepLabelId" value="AccountingPayment"/> <script location="component://order/webapp/ordermgr/WEB-INF/actions/entry/BillSettings.groovy"/> </actions> <widgets> <decorator-screen name="CommonOrderCheckoutDecorator"> <decorator-section name="body"> <platform-specific> <html><html-template location="component://order/webapp/ordermgr/entry/billsettings.ftl"/></html> </platform-specific> </decorator-section> </decorator-screen> </widgets> </section> </screen>
它是screen的子元素,一个screen可以包含n个section。而它可以又会由actions以及widgets元素组成。
在actions元素下,你可以定义若干个不同种类的action:
这里首先定义了一个名为:CommonOrderCheckoutDecorator的decorator-screen。所谓的decorator-screen你可以将其理解为页面的模板或者占位。比如,就一个页面而言,部分内容与空间是固定的,主要变化的是某个特定的区域。此时布局一个新页面的时候就没必要为其每个区域都重复编写html,对于公共区域直接引用已经定义好的模板即可。
比如此处的CommonOrderCheckoutDecorator装饰器screen,其定义中,它又引用了该app的一个main-decorator:
<decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">
一个通常的应用,其mainDecoratorLoaction参数可以在其web.xml中的context-param配置中找到:
<context-param> <param-name>mainDecoratorLocation</param-name> <param-value>component://order/widget/ordermgr/CommonScreens.xml</param-value> <description>The location of the main-decorator screen to use for this webapp; referred to as a context variable in screen def XML files.</description> </context-param>
widget内部拥有一个platform-specific子元素,它可以看做是一种switch-case语句。OFBiz widget 工具集没用对render html UI的方式进行限制。理论上,你可以采用任何技术来render浏览器能显示内容。在这里UI被render成HTML,而且还使用了html模板,该模板的路径通过location属性指定。此处该模板使用的是freemarker(这也是OFBiz中用得最多的一种模板技术)。
就前端展现而言,除了需要有由html标签组成的模板,还需要绑定数据才能形成完整的页面。
OFBiz提供了两种绑定数据的方式: