Apache OFBiz源码解读之MVC模型

节点解析

request-map

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

[html] view plain copy print?

  1. <request-map uri="createCreditCardAndPostalAddress">
  2. <security https="true" auth="true"/>
  3. <event type="service" path="" invoke="createCreditCardAndAddress"/>
  4. <response name="success" type="request" value="finalizeOrder"/>
  5. <response name="error" type="view" value="billsetting"/>
  6. </request-map>

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

  • security:表示该uri应该对应的安全级别(是否应该是https的,是否要进行权限检查)
  • event:该请求触发的事件,这个后面在讲解handler的时候再谈
  • response:指定响应的配置

view-map

一个常见的view-map配置:

[html] view plain copy print?

  1. <view-map name="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] view plain copy print?

  1. <!-- event handlers -->
  2. <handler name="java" type="request" class="org.ofbiz.webapp.event.JavaEventHandler"/>
  3. <handler name="soap" type="request" class="org.ofbiz.webapp.event.SOAPEventHandler"/>
  4. <handler name="xmlrpc" type="request" class="org.ofbiz.webapp.event.XmlRpcEventHandler"/>
  5. <handler name="service" type="request" class="org.ofbiz.webapp.event.ServiceEventHandler"/>
  6. <handler name="service-multi" type="request" class="org.ofbiz.webapp.event.ServiceMultiEventHandler"/>
  7. <handler name="service-stream" type="request" class="org.ofbiz.webapp.event.ServiceStreamHandler"/>
  8. <handler name="simple" type="request" class="org.ofbiz.webapp.event.SimpleEventHandler"/>
  9. <handler name="groovy" type="request" class="org.ofbiz.webapp.event.GroovyEventHandler"/>
  10. <handler name="rome" type="request" class="org.ofbiz.webapp.event.RomeEventHandler"/>
  11. <handler name="script" type="request" class="org.ofbiz.webapp.event.ScriptEventHandler"/>
  12. <!-- view handlers -->
  13. <handler name="screen" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>
  14. <handler name="screenxml" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>
  15. <handler name="screentext" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>
  16. <handler name="screencsv" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>
  17. <handler name="screenfop" type="view" class="org.ofbiz.widget.screen.ScreenFopViewHandler"/>
  18. <handler name="jsp" type="view" class="org.ofbiz.webapp.view.JspViewHandler"/>
  19. <handler name="ftl" type="view" class="org.ofbiz.webapp.ftl.FreeMarkerViewHandler"/>
  20. <handler name="http" type="view" class="org.ofbiz.webapp.view.HttpViewHandler"/>
  21. <handler name="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模型处理请求的完整过程:


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

[html] view plain copy print?

  1. <request-map uri="createCreditCardAndPostalAddress">

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

[html] view plain copy print?

  1. <security https="true" auth="true"/>

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

[html] view plain copy print?

  1. <event type="service" path="" invoke="createCreditCardAndAddress"/>

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

[html] view plain copy print?

  1. <response name="success" type="request" value="finalizeOrder"/>
  2. <response name="error" type="view" value="billsetting"/>

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


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

[html] view plain copy print?

  1. <request-map uri="finalizeOrder">

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


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

[html] view plain copy print?

  1. <view-map name="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] view plain copy print?

  1. <handler name="screen" type="view" class="org.ofbiz.widget.screen.MacroScreenViewHandler"/>

screen

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

[html] view plain copy print?

  1. <screen name="BillSettings">
  2. <section>
  3. <actions>
  4. <set field="stepTitleId" value="OrderOrderEntryPaymentSettings"/>
  5. <set field="stepLabelId" value="AccountingPayment"/>
  6. <script location="component://order/webapp/ordermgr/WEB-INF/actions/entry/BillSettings.groovy"/>
  7. </actions>
  8. <widgets>
  9. <decorator-screen name="CommonOrderCheckoutDecorator">
  10. <decorator-section name="body">
  11. <platform-specific>
  12. <html><html-template location="component://order/webapp/ordermgr/entry/billsettings.ftl"/></html>
  13. </platform-specific>
  14. </decorator-section>
  15. </decorator-screen>
  16. </widgets>
  17. </section>
  18. </screen>

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

section

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

action

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

  • label:将值绑定到label
  • entity action:利用entity engine进行action操作
  • set action:简单的赋值以及groovy表达式语法
  • script action:调用一个groovy脚本
  • service action:调用一个服务

widgets

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


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

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

[html] view plain copy print?

  1. <decorator-screen name="main-decorator" location="${parameters.mainDecoratorLocation}">


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

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

[html] view plain copy print?

  1. <context-param>
  2. <param-name>mainDecoratorLocation</param-name>
  3. <param-value>component://order/widget/ordermgr/CommonScreens.xml</param-value>
  4. <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>
  5. </context-param>

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

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

数据绑定

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

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

    1. form-action:如果你使用form widget,绑定数据的最好方式是采用action子元素。这可以有效得帮助你增强该form的复用性。
    2. groovy script action:如果你使用的是freemarker模板,此时form便是由html原生标签表示,那么推荐的方式是采用groovy脚本来获取数据并绑定至模板来显示,本文所举的示例就是这种模式。
时间: 2024-10-24 01:14:32

Apache OFBiz源码解读之MVC模型的相关文章

Apache Shiro源码解读之SecurityManager的创建

对于Shiro(v1.2+)的SecurityManager的创建,在普通的应用程序中一般可以在main方法中这么创建 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); 该方法读取classpath路径下的shiro.ini文件来构建S

Apache Beam WordCount编程实战及源码解读

概述:Apache Beam WordCount编程实战及源码解读,并通过intellij IDEA和terminal两种方式调试运行WordCount程序,Apache Beam对大数据的批处理和流处理,提供一套先进的统一的编程模型,并可以运行大数据处理引擎上.完整项目Github源码 负责公司大数据处理相关架构,但是具有多样性,极大的增加了开发成本,急需统一编程处理,Apache Beam,一处编程,处处运行,故将折腾成果分享出来. 1.Apache Beam编程实战–前言,Apache B

Spark发行版笔记10:Spark Streaming源码解读之流数据不断接收和全生命周期彻底研究和思考

本节的主要内容: 一.数据接受架构和设计模式 二.接受数据的源码解读 Spark Streaming不断持续的接收数据,具有Receiver的Spark 应用程序的考虑. Receiver和Driver在不同进程,Receiver接收数据后要不断给Deriver汇报. 因为Driver负责调度,Receiver接收的数据如果不汇报给Deriver,Deriver调度时不会把接收的数据计算入调度系统中(如:数据ID,Block分片). 思考Spark Streaming接收数据: 不断有循环器接收

SpringMVC源码解读 - RequestMapping注解实现解读 - RequestCondition体系

一般我们开发时,使用最多的还是@RequestMapping注解方式. @RequestMapping(value = "/", param = "role=guest", consumes = "!application/json") public void myHtmlService() { // ... } 台前的是RequestMapping ,正经干活的却是RequestCondition,根据配置的不同条件匹配request. @Re

15、Spark Streaming源码解读之No Receivers彻底思考

在前几期文章里讲了带Receiver的Spark Streaming 应用的相关源码解读,但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Approach)的方式,No Receiver的方式的优势: 1. 更强的控制自由度 2. 语义一致性 其实No Receivers的方式更符合我们读取数据,操作数据的思路的.因为Spark 本身是一个计算框架,他底层会有数据来源,如果没有Receivers,我们直接操作数据来源,这其实是一种更自然的方式

Jfinal启动源码解读

本文对Jfinal的启动源码做解释说明. PS:Jfinal启动容器可基于Tomcat/Jetty等web容器启动,本文基于Jetty的启动方式做启动源码的解读和分析,tomcat类似. 入口  JFinalConfig的继承类的Main方法为入口,实例代码继承类为:DemoConfig,Main方法如下: public static void main(String[] args) { /** * 特别注意:Eclipse 之下建议的启动方式 */ JFinal.start("WebRoot&

sklearn中LinearRegression关键源码解读

问题的引入 我们知道,线性回归方程的参数,可以用梯度下降法求解,或者用正规方程求解. 那sklearn.linear_model.LinearRegression中,是不是可以指定求解方式呢?能不能从中获取梯度相关信息呢? 下面是线性回归最简单的用法. from sklearn import linear_model # Create linear regression object regr = linear_model.LinearRegression() # Train the model

HttpClient 4.3连接池参数配置及源码解读

目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口.最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB->服务端处理请求,查询数据并返回),发现原本的HttpClient连接池中的一些参数配置可能存在问题,如defaultMaxPerRoute.一些timeout时间的设置等,虽不能确定是由于此连接池导致接口查询慢,但确实存在可优化的地方,故花时间做一些研究.本文主要涉及HttpClient连接池.请求的参数

structs2源码解读(6)之解析package标签

structs2源码解读之解析package标签 上面讨论过,在创建Dispacher对象时,调用dispacher.init()方法完成初始化,在这个方法中先创建各种配置文件的解析器(ConfigurationProvider),然后循环遍历这些解析器的register()方法解析各个配置文件.  for (final ContainerProvider containerProvider : providers)         {             containerProvider