`
biomedinfo
  • 浏览: 25129 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

《The Definitive Guide to Grails》 学习笔记六 (对应第9章)

阅读更多

1. 截止到目前,我们讨论的都是无状态的客户端-服务器交互,grails认为应用程
序中的action都是简单的单步处理,不需要知道用户当前操作的状态和前后步骤。
但是在某些情况下,应用程序的处理是和前后步骤相关的,比如典型的购物车,用
户必须确认购买的物品和价格、输入送货地址、验证信用卡支付才能确认订单并显
示收据,用户直接跳到确认订单的步骤是不合法的。
在无状态的应用框架下,程序员需要自己保存当前状态(比如利用session或者是
cookie),然后在controller里通过大量编码来确保步骤的顺序进行,并且自己提
供各种边界条件的判断和处理逻辑。在度过大量不眠之夜终于完成这个功能后,你
偶然在一个某大师的桌上发现了这本书,随意翻到第9章 “Creating Web Flows”,
你会突然发现人生是如此灰暗,天底下最悲惨的事情莫过于此。。。
当然,也有另一种无状态框架的做法,就是采用Ajax把所有的状态放在客户端来管
理(正如我们上一章里的例子),在流程里的每个步骤对应了HTML里根据当前状态
变成“出现“或“隐藏“的元素,这样就不需要每次刷新浏览器了。但是大部分grails
达人,还是会坚持认为流程控制应该放在服务器端而不是客户端来进行。


2. 来看看grails提供的插件能做什么?利用grails的Spring Web Flow插件,定义
一系列的状态,从启示状态到终结状态,并且根据流程的定义控制用户在不同状态
之间的转移。Spring Web Flow实质上是一个状态机,在客户端和服务器之间传递
flowExecuteKey和eventID(通常是作为request参数,),从而实现用户在不同状
态之间的转移。前面一句话是不是很难看懂?其实,我也没看懂,书上在写了这样
一段话以后,又接着说了一句:“您不需要花太多精力去研究这些机制,grails已
经帮您处理好了大部分的客户端-服务器通讯。“ 既然如此,那就先跳过吧。


3. Web Flow的定义:只需在controller中定义以"Flow"结尾的action,就定义了
一个Web Flow,如:
class StoreController {
    def shoppingCartFlow = {
    ....
    }
}
每个流程应该有一个唯一的id,就是def后面流程名去掉“Flow“,例如这里是
shoppingCart。这样好像看上去和一般的action没啥不同,但是实际上是大不相
同。首先,在流程的closure里并没有任何逻辑语句,而是顺序定义了一组流程状
态,这些流程状态表达为以一个闭包作为参数的方法调用,如:
def shoppingCartFlow = {
    showCart {
        on("checkout").to "enterAddress"
        on("continueShopping").to "displayCatalogue"
    }
}
on("checkout").to "enterAddress" 是Spring Web Flow提供的流程定义DSL,这
样的流程定义非常简单易读。


4. 状态定义:一个流程的起始状态永远是流程闭包里的第一个状态定义,结束状
态有两种情况:一是无参数的(也就是闭包内为空),一是redirect到流程外的
action或者另一个流程的,如:
def shoppingCartFlow = {
    showCart { //起始状态
        .....

    }
    displayInvoice() //第一种终结状态
    cancelTransaction{ //第二种终结状态
        redirect(controller: “store")
    }
}


5. 对应的view:根据grails的convention,在每个含有流程定义的controller的
view目录下,需要为每个定义的流程建立一个与流程同名的子目录,然后在子目录
下根据流程内部状态的定义,为起始、中间和无参数结束的状态建立一个与状态名
同名的.gsp。对应上面的例子,应该在 grails-app/views/store下建立
shoppingCart目录,在其中添加 showCart.gsp,displayInvoice.gsp等,而
cancelTransaction不需要对应的.gsp,因为它作为一个终结状态,并且会
redirect到其他controller。


6. action状态和view状态:在起始和终结状态之间,一般还会存在若干其他的状
态,这些状态可以分为action状态和view状态。简而言之,view状态就是能暂停流
程并渲染一个view的状态,它不定义action或redirect。如前面提到,view名等同
于状态名,但是也可以通过render方法显示指定为其他的.gsp。例如:
showCart{
    render(view:"basket")
}
就让showCart状态对应到basket.gsp进行渲染。
action状态不停下来等待用户的输入,二是直接执行一段代码来确定流程应该转移
到哪个状态。例如:
listAlbums {
    action {
        [albumList:Album.list(max:10, sort:'dateCreated', order:'desc')]
    }
    on("success").to "showCatalogue"
    on(Exception).to "handleError"
}
listAlbums状态定义了一个action,取得最新的10个唱片记录,把这个list包装成
一个map,map的key是albumList,然后它作为model被返回并自动保存到flow
scope里,可以在整个流程中被访问,如果action执行没有产生错误,则在完成后
触发success事件,使流程自动转移到 showCatalogue状态;否则转到handleError
状态,也可以更加具体地描述Exception并转到更清晰的状态,如:
on(StoreNotAvailableException).to "maintenancePage"
action状态也可以从action触发客户化的事件,配合状态的转移DSL,进行动态的
判断和转移。例如:
isGift {
    action {
        params.isGift ? yes() : no()
    }
    on("yes").to "wrappingOptions"
    on("no").to "enterShippingAddress"
}


7. Flow Scope:除了常规的request, session这些scope,我们前面还提到了
flash(controller中的),此外还有flow和conversation。Scope本质上就是容
器,和map一样,flash、flow、和conversation的区别在于:
flash:保存的对象只对当前和下一个request有效,和controller中的flash非常
相似,主要区别是在要保存在flow的flash scope中的对象必须实现
java.io.Serializable接口;
flow:保存的对象在整个流程里有效,当流程到达终结状态后被清除。这是在流程
中最常用的scope;
conversation:保存的对象在对话中有效,包括根流程和所有嵌套的子流程。


8. Flow,串行化和Flow存储:
在前面的例子里可以看到,从action状态中返回的model会被自动保存到flow
scope里。使用flow有一个很重要的问题需要注意:存放在flow scope里的对象必
须实现java.io.Serializable接口。这又是为什么呢?很简单,因为flow不像
session和request这些常规scope,它的状态是在服务器以串行化的压缩表形式保
存的。有好学的同志可能会提出一个问题:为啥一定要保存在服务器端呢?
嗯,grails也允许你把它保存在客户端,具体的做法是在Config.groovy里设置
grails.webflow.flow.storage属性:
grails.webflow.flow.storage = “client"
这样服务器端就是无状态的,Web Flow通过接收从客户端传递到服务器的
flowExecutionKey来获取状态。看到这里我不禁产生了一个疑问:只有
flowExecutionKey,那eventID哪里去了?想来想去,想必是因为状态被保存在客
户端,eventID就不需要给无状态的服务器了?有的同志又要说了:这样不是也很
好吗?可能在性能上还会有明显的改进呢!但是同志们,采用客户端存储的状态有
两个需要注意的问题:
只能使用HTTP POST request来触发事件,因为flowExecutionKey太大,无法包含
在URL里;
该方法是不安全的,因为这种方式需要把敏感数据以串行化的方式在服务器和客户
端之间传输,不过如果你的应用不需要考虑安全,或者是已经在HTTPS下运行,则
是可行的;
不管是否采用这种方法,实现java.io.Serializable是必须的。就如在Java中,如
果你有任何不愿意串行化的属性,你必须标记它为 transient。这包括你定义的所
有闭包,因为Groovy的闭包没有实现Serializable接口,如:

transient onLoad = { }


9. 从view中触发事件:
前面我们提到view状态里会暂停流程,渲染一个view来接收用户输入。那么用户输
入又是怎么使流程继续执行下去的呢?主要有两种方式:链接或表单提交。
链接是通过<g:link>标签实现,在第4章提到了用链接来关联到特定的controller
和action,既然流程也是通过 controller和action来实现,链接触发流程继续执
行也就是很正常的了。例如:
<g:link controller="store" action="shoppingCart">My Cart</g:link>
有仔细的同学看到这里可能会发现上面的例子有问题:”开始说的是通过链接触发
在view状态里被暂停的流程继续执行,可是这个例子说的是通过链接来启动一个流
程,shoppingCart是一个action,明明是对应了一个流程的名字嘛!如果要继续执
行流程,必须要对应到 on("aEventName").to "newStateName"才是正确的。“

咳咳,这位同学说的很对,这里再举一个符合初始问题的例子:
<g:link controller="store" action="shoppingCart"
event="checkout">Checkout</g:link>
这样就使Checkout链接在被点击的时候产生checkout事件传递到
store.shoppingCart这个流程里,对应的代码是:
showCart{
    on("checkout").to "enterPersonalDetails"
    ......
}
于是,在Checkout链接被点击后,流程被转移到enterPersonalDetails状态,可能
是通过 enterPersonalDetails.gsp来让用户输入个人资料。

表单提交略有不同,主要是grails通过提交按钮<g:submitButton>标签里的name属
性来确定产生的事件。例如:
<g:form name="shoppingForm" url="[controller:'store', action:'shoppingCart']"
....
<g:submitButton name="checkout" value="Checkout“/>
......
</g:form>


10. 转移action和表单验证:表单提交的数据验证如何进行?一种方式是提交到一
个action状态,在改状态内部可以执行特定的一块代码,这非常有用。不过用
transistion action进行表单验证是更好的实践。那么transition action是什么
呢?基本上它是一个在特定事件被触发的情况下执行的action。有意思的是,如果
transition action由于错误而失败,transition就会被中止,当前状态会被回滚
到原始状态。例如:
on("submit") {
    flow.person = new Person(params)
    flow.person.validate() ? success() : error()
}.to "enterShipping"

on("return").to "showCart"
在on("submit")后面跟的一个闭包就是transition action,通过validate()方
法,返回的是自动根据validation结果产生的success和error事件。


11. 子流程和对话(conversation)scope:子流程就是流程内部定义的嵌套流
程,通过在流程内部的一个状态下用 subflow(referenceToSubFlowDefinition)方
法产生,如:
先定义subflow:
def chooseGiftWrapFlow = {
    ....
    confirmSelection {
        on('confirm') {
            def giftWrap = new GiftWrap(params)
            if(!giftWrap.validate()) return error()
            else {
                conversation.giftWrap = giftWrap //把结果保存到conversation scope,使上级flow可以访问到
            }
        }. to 'giftWrapChosen'
        on('cancel').to 'cancelGiftWrap'
    }
    cancelGiftWrap() //subflow的终结状态
    giftWrapChosen() //subflow的终结状态
}
然后在主流程中引用subflow:
def shoppingCartFlow = {
    .......
    wrappingOptions {
        subflw(chooseGiftWrapFlow)
        on('giftWarpChosen') { //event 对应subflow里的终结状态
            flow.giftWrap = conversation.giftWrap //从conversation中取出结果,保存到flow scope里
        }
        on('cancelGiftWrap'). to 'enterShippingAddress' //event 对应subflow里的终结状态
    }
}


12. 在一个view里混用Ajax和普通request:request对象中有一个xhr属性,如果
该request是一个Ajax请求则是true,否则为false,可以用于判断处理。例如:
if (request.xhr) {
    render(template:"album",model:[artist:artist, album:album])
}
else {
    render(view:"show",model:[artist:artist, album:album])
}
else {
    response.sendError 404
}


 


13. siteMesh layout的g:layoutBody和g:applyLayout标签:利用这两个标签,可
以实现layout的复用和嵌套,并通过 pageScope.variables表达式把当前页面的
model传递到被渲染的template页面。


14. Groovy表达式动态解析:看这个例子:

if(!flow.albumPayments.album.find{it?id == album.id} )
flow.albumPayments实际上是一个java.util.List对象,怎么它会有一个属性叫
album(一个Album类的实例)呢?这就是GPath的魔力,Groovy会自动解析其中含
有flow.albumPayments这个List中每个元素中的album属性,并把这些属性包装成
一个List返回。还有find{it?id == album.id}部分,是Groovy Truth的体现,在
Java中,只有布尔值可以用来表示true和false,而在Groovy中包含了更完整的表
示法,例如null在if语句中为 false。


15. 条件查询和基于字符串的查询:基于字符串的查询如SQL或HQL容易出现错误,
因为在书写查询语句的时候没有IDE或parser的自动检查,而且这种方式丢失了被
查询对象的大部分类型信息;而条件查询则受益于Groovy的运行时间查询构建器,
能够安全和优雅地实现查询。例如使用 withCriteria方法:
flow.genreRecommandations = Album.withCriteria {
    inList 'genre', genres
    not {
        inList 'id', albums.id
    }
    maxResults 4
    order 'dateCreated', 'desc'
}
还可以通过在条件的闭包内引用以关联表名命名的方法,实现对关联表的查询,如:
def otherAlbumPayments = AlbumPayment.withCriteria {
    user { //关联到AlbumPayment表的user属性
        purchasedAlbums { //关联到User表的purchasedAlbums属性
            inList 'id', albums.id
        }
    }
}


16. 复用closure:如果有一段代码经常被重复使用,出于DRY的原则,并且提高代
码的一致性和可维护性,应该把这部分代码放在一个closure里,赋值给一个
private 对象进行保存,在该段代码反复出现的地方可以直接引用该对象,达到完
全相同的效果。这是为什么呢?因为everything in Groovy is an object,在
Groovy里一切都是对象,所以一个closure作为一个对象,也可以保存在一个对象
里,引用这个对象的时候,就能访问到这个 closure里的代码块。如:
private addAlbumToCartAction = {
    if(!flow.albumPayments) flow.albumPayments = [ ]
    def album = Album.get(params.id)
    if(!flow.albumPayments.album.find {it?.id == album.id}) {
        flow.lastAlbum = new AlbumPayment(album.album)
        flow.albumPayments << flow.lastAlbum
    }
}
然后在流程中引用就可以非常简化:
def buyFlow = {
    start {
    ....
    }
    ....
    showRecommendations {
        on('addAlbum', addAlbumToCartAction). to 'requireHardCopy'
        .......
    }
}
否则showRecommendations状态内部要写成:
on('addAlbum') {
    if(!flow.albumPayments) flow.albumPayments = [ ]
    def album = Album.get(params.id)
    if(!flow.albumPayments.album.find {it?.id == album.id}) {
        flow.lastAlbum = new AlbumPayment(album.album)
        flow.albumPayments << flow.lastAlbum
    }
}.to 'requreHardCopy'


17. 动态的状态转移:如果需要在view状态中动态地确定转移到哪个状态,可以
在.to方法的参数中放置一个closure来动态返回一个目标状态名称。如:
on('back').to {
    def view
    if(flow.genreRecommendations || flow.userRecommendations)
        view = "showRecommendations"
    else if(flow.lastAlbum.shippingAddress) {
        view = 'enterShipping'
    }
    else {
        view = 'requireHardCopy'
    }
    return view
}
简单的例子也可以这么看:
on('back').to ‘enterShipping' //static String
on('back').to {'enterShipping'} //closure执行,隐含返回
on('back').to { return 'enterShipping'} //closure执行,显式返回
三个语句的执行结果是一样的。


18. 在转移到关键性的状态之前,可以通过一系列的assert关键字进行状态的
validate,如:
p.addToAlbumPayments(ap)
assert p.save()


19. 测试flow:利用grails.test.WebFlowTestCase类,在集成测试环境中进行测
试。有人问,为什么不做flow的单元测试呢?这个问题就问的没有水平了,flow涉
及到那么多的domain类、controller、template GSP、甚至还有command对象什么
的,单元测试就不适用了。言归正传,在测试类中需要实现抽象方法getFlow(),
返回一个代表被测试flow 的closure,在测试中可以使用的方法如下:
startFlow():启动被测试的flow
assertFlowExecutionEnded():断言流程执行已终结
assertFlowExecutionOutcomeEquals(): 断言流程执行的结果
assertFlowExecutionActive():断言流程未终结
assertCurrentStateEquals():断言当前的状态
signalEvent():触发一个事件
setCurrentState():设定流程的当前状态


0
0
分享到:
评论

相关推荐

    The definitive Guide To Grails学习笔记

    《The definitive Guide To Grails学习笔记》是一份深入探讨Grails框架的重要资源,它源于经典书籍《The Definitive Guide to Grails》的精华总结。Grails是一种基于Groovy语言的开源Web应用框架,旨在提高开发效率...

    The definitive guide to grails 2 英文版 书 代码

    《The Definitive Guide to Grails 2》是Grails框架深入学习的重要参考资料,由业界专家撰写,旨在为开发者提供全面、详尽的Grails 2技术指导。这本书结合了理论与实践,不仅介绍了Grails的基本概念,还涵盖了高级...

    the definitive guide to grails 2

    《Grails 2 的终极指南》是一本深入探讨Grails框架精髓的专业书籍,该书以英文撰写,旨在为读者提供全面、深入的Grails框架学习资料。Grails框架基于Groovy语言,是一种高度动态、敏捷的Java应用开发框架,它简化了...

    The Definitive Guide to Grails 2nd Edition

    The Definitive Guide to Grails 2nd Edition.pdf

    The definitive guide to grails_2 源代码

    《The Definitive Guide to Grails 2》是关于Grails框架的一本权威指南,它为读者提供了深入理解和使用Grails 2开发Web应用程序所需的知识。Grails是一种基于Groovy语言的开源全栈式Web应用框架,它借鉴了Ruby on ...

    The Definitive Guide to Django 2nd Edition

    《The Definitive Guide to Django 2nd Edition》是一本深度解析Django框架的权威指南,旨在帮助初学者和有经验的开发者全面掌握Django的使用。这本书分为两个主要部分,确保读者能够从基础到高级逐步提升自己的技能...

    The Definitive Guide to Spring Batch, 2nd Edition.epub

    The Definitive Guide to Spring Batch takes you from the “Hello, World!” of batch processing to complex scenarios demonstrating cloud native techniques for developing batch applications to be run on...

    The Definitive Guide to SQLite

    And because SQLite's databases are completely file based, privileges are granted at the operating system level, allowing for easy and fast user management., The Definitive Guide to SQLite is the ...

    <The Definitive Guide to MySQL 5>

    9. **备份与恢复**:学习如何备份数据库,以及在数据丢失时进行恢复,确保数据安全。 10. **安全性**:讨论用户权限管理,如何设置和管理用户账户,以及最佳的安全实践。 11. **性能优化**:涵盖查询分析、性能...

    The Definitive Guide to Grails Second Edition (Apress 2009)

    ### Grails第二版终极指南(Apress 2009)关键知识点解析 #### 一、Grails概述 - **Grails**是一款基于Groovy语言的高性能、全栈式的Java Web应用开发框架,它极大地简化了Java Web开发过程,使得开发者能够更高效...

    The Definitive Guide to Java Swing Third Edition

    ### 《Java Swing 终极指南》第三版关键知识点概览 #### 一、书籍基本信息与版权信息 - **书名**:《Java Swing 终极指南》第三版 - **作者**:John Zukowski - **出版社**:本书由Springer-Verlag New York, Inc....

    The Definitive Guide to Windows Installer

    The Definitive Guide to Windows Installer Introduction Chapter 1 - Installations Past, Present, and Future Chapter 2 - Building an Msi File: Visual Studio and Orca Chapter 3 - COM in the ...

    The Definitive Guide to Django - Web Development Done Right(2nd) 无水印pdf

    The Definitive Guide to Django - Web Development Done Right(2nd) 英文无水印pdf 第2版 pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 ...

    The Definitive Guide to GCC, Second Edition

    The Definitive Guide to GCC, Second Edition has been revised to reflect the changes made in the most recent major GCC release, version 4. Providing in-depth information on GCC’s enormous array of ...

    The Definitive Guide to Jython-Python for the Java Platform

    ### 关于《The Definitive Guide to Jython—Python for the Java Platform》的知识点解析 #### 一、Jython简介 Jython 是一种开放源代码的实现方式,它将 Python 这种高级、动态且面向对象的脚本语言无缝集成到 ...

    The Definitive Guide to HTML5 epub

    The Definitive Guide to HTML5 英文epub 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除

    802.11详解 802.11 Wireless Networks- The Definitive Guide

    Wireless Networks: The Definitive Guide 这个比直接看802.11 协议要舒服一些。理解更方便。 802.11® Wireless Networks: The Definitive Guide By Matthew Gast Publisher : O'Reilly Pub Date : April 2002 ISBN...

Global site tag (gtag.js) - Google Analytics