`
海之恋鱼
  • 浏览: 16395 次
  • 来自: 北京
社区版块
存档分类
最新评论

5、Grails 事件模型

阅读更多
原文地址:http://www.ibm.com/developerworks/cn/java/j-grails08128.html

构建事件
开发 Grails 的第一步是输入 grails create-app。最后输入 grails run-app 或 grails war。这期间输入的所有命令和内容都会在过程的关键点抛出事件。
查看 $GRAILS_HOME/scripts 目录。此目录中的文件是 Gant 脚本,对应输入的命令。例如,输入 grails clean 时,调用 Clean.groovy。
Gant 的 groovy 特性
您在 第一篇文章 中第一次看到了 Grant 脚本。注意,Gant 是针对 Apache Ant 设计的瘦 Groovy。Gant 没有重新实现 Ant 任务 — 它实际上调用底层 Ant 代码来实现最大的兼容性。在 Ant 中能做的一切事情也可以在 Grant 中完成。惟一的区别在于 Gant 脚本是 Groovy 脚本,而不是 XML 文件(有关 Gant 的更多信息,请参阅 参考资料)。
在文本编辑器中打开 Clean.groovy。首先看到的目标是 default 目标,如清单 1 所示:
清单 1. Clean.groovy 中的 default 目标
target ('default': "Cleans a Grails project") {
   clean()
   cleanTestReports()
}

可见,它的内容并不多。首先运行 clean 目标,然后运行 cleanTestReports 目标。调用堆栈后,看一下 clean 目标,如清单 2 所示:
清单 2. Clean.groovy 中的 clean 目标
target ( clean: "Implementation of clean") {
    event("CleanStart", [])
    depends(cleanCompiledSources, cleanGrailsApp, cleanWarFile)
    event("CleanEnd", [])
}

如果需要自定义 clean 命令的行为,可以在此添加自己的代码。不过,使用此方法的问题是:每次升级 Grails 时都必须迁移自定义内容。而且从一台计算机移动到另一台计算机时,您的构建会更容易出错。(Grails 安装文件很少签入版本控制 — 只检签入用程序代码)。为了避免可怕的 “but it works on my box” 综合症,我倾向于将这些类型的自定义内容放在项目中。这确保来自源控件的所有新签出都包含成功构建所需的自定义内容。如果使用持续集成服务器(比如 CruiseControl),也有助于保持一致性。
注意,在 clean 目标期间会抛出几个事件。CleanStart 在过程开始之前发生,随后发生 CleanEnd。您可以在项目中引入这些事件,将自定义代码与项目放在一起,不要改动 Grails 安装文件。您只需要创建一个监听器。
在项目的脚本目录中创建一个名为 Events.groovy 的文件。添加清单 3 所示的代码:
清单 3. 向 Events.groovy 添加事件监听器
eventCleanStart = {
  println "### About to clean"
}

eventCleanEnd = {
  println "### Cleaning complete"
}

如果输入 grails clean,应该看到类似于清单 4 的输出:
清单 4. 显示新注释的控制台输出
$ grails clean

Welcome to Grails 1.0.3 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /opt/grails

Base Directory: /src/trip-planner2
Note: No plugin scripts found
Running script /opt/grails/scripts/Clean.groovy
Environment set to development
Found application events script
### About to clean
  [delete] Deleting: /Users/sdavis/.grails/1.0.3/projects/trip-planner2/resources/web.xml
  [delete] Deleting directory /Users/sdavis/.grails/1.0.3/projects/trip-planner2/classes
  [delete] Deleting directory /Users/sdavis/.grails/1.0.3/projects/trip-planner2/resources
### Cleaning complete

当然,您可以不向控制台写入简单的消息,而是进行一些实际工作。可能需要删除一些额外的目录。您可能喜欢通过用新的文件覆盖现有文件来 “重置” XML 文件。任何能在 Groovy(或通过 Java 编程)中完成的工作都可以在这里完成。
CreateFile 事件
以下是另一个可在构建期间引入的事件示例。每次输入 create- 命令之一(create-controller、create-domain-class 等等),都会触发 CreatedFile 事件。看看 scripts/CreateDomainClass.groovy,如清单 5 所示:
清单 5. CreateDomainClass.groovy
Ant.property(environment:"env")
grailsHome = Ant.antProject.properties."env.GRAILS_HOME"

includeTargets << new File ( "${grailsHome}/scripts/Init.groovy" )  
includeTargets << new File( "${grailsHome}/scripts/CreateIntegrationTest.groovy")

target ('default': "Creates a new domain class") {
    depends(checkVersion)

   typeName = ""
   artifactName = "DomainClass"
   artifactPath = "grails-app/domain"
   createArtifact()
   createTestSuite() 
}

在此不能看到 CreatedFile 事件的调用,不过看一下 $GRAILS_HOME/scripts/Init.groovy 中的 createArtifact 目标($GRAILS_HOME/scripts/CreateIntegrationTest.groovy 中的 createTestSuite 目标最终也调用 $GRAILS_HOME/scripts/Init.groovy 中的 createArtifact 目标)。在 createArtifact 目标的倒数第二行,可以看到以下调用 :event("CreatedFile", [artifactFile])。
该事件与 CleanStart 事件的最大差异是:前者会将一个值传回给事件处理程序。在本例中,它是刚才创建的文件的完全路径(随后会看到,第二个参数是一个列表 — 可以需要传递回以逗号分隔的值)。必须设置事件处理程序来捕获传入的值。
假设您想将这些新创建的文件自动添加到源控件。在 Groovy 中,可以将平时在命令行中输入的所有内容包含在引号内并在 String 上调用 execute()。将清单 6 中的事件处理程序添加到 scripts/Events.groovy:
清单 6. 自动向 Subversion 添加工件
eventCreatedFile = {fileName ->
  "svn add ${fileName}".execute()
  println "### ${fileName} was just added to Subversion."  
}

现在输入 grails create-domain-class Hotel 并查看结果。如果没有使用 Subversion,此命令将静默失败。如果使用 Subversion,输入 svn status。此时应该看到添加的文件(域类和对应的集成测试)。
发现调用的构建事件
要发现什么脚本抛出什么事件,最快方式是搜索 Grails 脚本中的 event() 调用。在 UNIX® 系统中,可以使用 grep 搜索 Groovy 脚本中的 event 字符串,如清单 7 所示:
清单 7. 使用 Grep 搜索 Grails 脚本中的事件调用
$ grep "event(" *.groovy
Bootstrap.groovy:       event("AppLoadStart", ["Loading Grails Application"])
Bootstrap.groovy:       event("AppLoadEnd", ["Loading Grails Application"])
Bootstrap.groovy:       event("ConfigureAppStart", [grailsApp, appCtx])
Bootstrap.groovy:       event("ConfigureAppEnd", [grailsApp, appCtx])
BugReport.groovy:    event("StatusFinal", ["Created bug-report ZIP at ${zipName}"])

知道调用的事件后,可以在 scripts/Events.groovy 中创建相应的监听器,并高度自定义构建环境。
回页首
抛出自定义事件
显然,现在已经了解相关的原理,您可以随意添加自己的事件了。如果确实需要自定义 $GRAILS_HOME/scripts 中的脚本(我们随后将进行此操作以抛出自定义事件),我建议将它们复制到项目内的脚本目录中。这意味着自定义脚本会和其他内容一起签入到源控件中。Grails 询问运行哪个版本的脚本 — $GRAILS_HOME 或本地脚本目录中的脚本。
将 $GRAILS_HOME/scripts/Clean.groovy 复制到本地脚本目录,并在 CleanEnd 事件后添加以下事件:
event("TestEvent", [new Date(), "Some Custom Value"])
第一个参数是事件的名称,第二个参数是要返回的项目列表。在本例中,返回一个当前日期戳和一条自定义消息。
将清单 8 中的闭包添加到 scripts/Events.groovy:
清单 8. 捕获自定义事件
eventTestEvent = {timestamp, msg ->
  println "### ${msg} occurred at ${timestamp}" 
}
输入 grails clean 并选择本地脚本版本后,应该看到如下内容:
### Some Custom Value occurred at Wed Jul 09 08:27:04 MDT 2008

回页首
启动
除了构建事件,还可以引入应用程序事件。在每次启动和停止 Grails 时会运行 grails-app/conf/BootStrap.groovy 文件。在文本编辑器中打开 BootStrap.groovy。init 闭包在启动时调用。destroy 闭包在应用程序关闭时调用。
首先,向闭包添加一些简单文本,如清单 9 所示:
清单 9. 以 BootStrap.groovy 开始
def init = {
  println "### Starting up"
}

def destroy = {
  println "### Shutting down"
}

输入 grails run-app 启动应用程序。应该会程序末尾附近看到 ### Starting Up 消息。
现在按 CTRL+C。看到 ### Shutting Down 消息了吗?我没有看到。问题在于 CTRL+C 会突然停止服务器,而不调用 destroy 闭包。Rest 确保在应用服务器关闭时会调用此闭包。但无需输入 grails war 并在 Tomcat 或 IBM®WebSphere® 中加载 WAR 来查看 destroy 事件。
要查看 init 和 destroy 事件触发,输入 grails interactive 以交互模式启动 Grails。现在输入 run-app 启动应用程序,输入 exit 关闭服务器。以交互模式运行会大大加快开发过程,因为 JVM 一直在运行并随时可用。其中一个优点是,与使用 CTRL+C 强硬方法相比,应用程序关闭得更恰当。
在启动期间向数据库添加记录
使用 BootStrap.groovy 脚本除了提供简单的控制台输出,还能做什么呢?通常,人们使用这些挂钩将记录插入数据库中。
首先,向先前创建的 Hotel 类中添加一个名称字段,如清单 10 所示:
清单 10. 向 Hotel 类添加一个字段
class Hotel{
  String name
}

现在构建一个 HotelController,如清单 11 所示:
清单 11. 创建一个 Hotel Controller
class HotelController {
  def scaffold = Hotel
}

注意:如果像 “Grails 与遗留数据库” 中讨论的那样禁用 grails-app/conf/DataSource.groovy 中的 dbCreate 变量,本例则应该重新添加它并设置为 update。当然,还有另一种选择是通过手动方式让 Hotel 表与 Hotel 类的更改保持一致。
现在将清单 12 中的代码添加到 BootStrap.groovy:
清单 12. 保存和删除 BootStrap.groovy 中的记录
def init = { servletContext ->  
  new Hotel(name:"Marriott").save()
  new Hotel(name:"Sheraton").save()  
}


def destroy = {
  Hotel.findByName("Marriott").delete()
  Hotel.findByName("Sheraton").delete()  
}

在接下来的几个示例中,需要一直打开 MySQL 控制台并观察数据库。输入 mysql --user=grails -p --database=trip 登录(记住,密码是 server)。然后执行以下步骤:
如果 Grails 还没有运行就启动它。
输入 show tables; 确认已创建 Hotel 表。
输入 desc hotel; 查看列和数据类型。
输入 select from hotel; 确认记录已插入。
输入 delete from hotel; 删除所有记录。
BootStrap.groovy 中的防故障数据库插入和删除
在 BootStrap.groovy 中执行数据库插入和删除操作时可能需要一定的防故障措施。如果在插入之前没有检查记录是否存在,可能会在数据库中得到重复项。如果试着删除不存在的记录,会看到在控制台上抛出恶意异常。清单 13 说明了如何执行防故障插入和删除:
清单 13. 防故障插入和删除
def init = { servletContext ->  
  def hotel = Hotel.findByName("Marriott")    
  if(!hotel){
    new Hotel(name:"Marriott").save()
  }
  
  hotel = Hotel.findByName("Sheraton")
  if(!hotel){
    new Hotel(name:"Sheraton").save()
  }
}

def destroy = {
  def hotel = Hotel.findByName("Marriott")
  if(hotel){
    Hotel.findByName("Marriott").delete()
  }
  
  hotel = Hotel.findByName("Sheraton")
  if(hotel){
    Hotel.findByName("Sheraton").delete()
  }
}

如果调用 Hotel.findByName("Marriott"),并且 Hotel 不存在表中,就会返回一个 null 对象。下一行 if(!hotel) 只有在值非空时才等于 true。这确保了只在新 Hotel 还不存在时才保存它。在 destroy 闭包中,执行相同的测试,确保不删除不存在的记录。
在 BootStrap.groovy 中执行特定于环境的行为
如果希望行为只在以特定的模式中运行时才发生,可以借助 GrailsUtil 类。在文件顶部导入 grails.util.GrailsUtil。静态 GrailsUtil.getEnvironment() 方法(由于 Groovy 的速记 getter 语法,简写为 GrailsUtil.environment)指明运行的模式。将此与 switch 语句结合起来,如清单 14 所示,可以在 Grails 启动时让特定于环境的行为发生:
Groovy 健壮的 switch
注意,Groovy 的 switch 语句比 Java switch 语句更健壮。在 Java 代码中,只能开启整数值。在 Groovy 中,还可以开启 String 值。
清单 14. BootStrap.groovy 中特定于环境的行为
import grails.util.GrailsUtil

class BootStrap {

     def init = { servletContext ->
       switch(GrailsUtil.environment){
         case "development":
           println "#### Development Mode (Start Up)"
           break
         case "test":
           println "#### Test Mode (Start Up)"
           break
         case "production":
           println "#### Production Mode (Start Up)"
           break
       }
     }

     def destroy = {
       switch(GrailsUtil.environment){
         case "development":
           println "#### Development Mode (Shut Down)"
           break
         case "test":
           println "#### Test Mode (Shut Down)"
           break
         case "production":
           println "#### Production Mode (Shut Down)"
           break
       }
     }
}

现在具备只在测试模式下插入记录的条件。但不要在此停住。我通常在 XML 文件中外部化测试数据。将这里所学到的知识与 “Grails 与遗留数据库” 中的 XML 备份和还原脚本相结合,就会得到了一个功能强大的测试平台(testbed)。
因为 BootStrap.groovy 是一个可执行的脚本,而不是被动配置文件,所以理论上可以在 Groovy 中做任何事情。您可能需要在启动时调用一个 Web 服务,通知中央服务器该实例正在运行。或者需要同步来自公共源的本地查找表。这一切都有可能实现。
回页首
微型事件
了解一些大型事件后,现在看几个微型事件。
为域类添加时间戳
如果您提供几个特别的命名字段,GORM 会自动给它们添加时间戳,如清单 15 所示:
清单 15. 为字段添加时间戳
class Hotel{
  String name
  Date dateCreated 
  Date lastUpdated 
}

顾名思义,dateCreated 字段在数据第一次插入到数据库时被填充。lastUpdated 字段在每次数据库记录更新之后被填充。
要验证这些字段在幕后被填充,需要再做一件事:在创建和编辑视图中禁用它们。为此,可以输入 grails generate-views Hotel 并删除 create.gsp 和 edit.gsp 文件中的字段,但有一种方法使 scaffolded 视图更具动态性。在 “用 Groovy 服务器页面(GSP)改变视图” 中,您输入了 grails install-templates,以便能够调试 scaffolded 视图。查看 scripts/templates/scaffolding 中的 create.gsp 和 edit.gsp。现在向模板中的 excludedProps 列表添加两个时间戳字段,如清单 16 所示:
清单 16. 从默认 scaffolding 中删除时间戳字段
excludedProps = ['dateCreated','lastUpdated',
                 'version',
                 'id',
                   Events.ONLOAD_EVENT,
                   Events.BEFORE_DELETE_EVENT,
                   Events.BEFORE_INSERT_EVENT,
                   Events.BEFORE_UPDATE_EVENT]

这会限制在创建和编辑视图中创建字段,但仍然在列表中保留字段并显示视图。创建一两个 Hotel 并验证字段会自动更新。
如果应用程序已经使用这些字段名称,可以轻松地禁用此功能,如清单 17 所示:
清单 17. 禁用时间戳
static mapping = {
  autoTimestamp false
}
回忆一下 “Grails 与遗留数据库”,在那里还可以指定 version false 来禁用 version 字段的自动创建和更新。
向域类添加事件处理程序
除了给域类添加时间戳,还可以引入 4 个事件挂钩:beforeInsert、befortUpdate、beforeDelete 和 onload。
这些闭包名称反映了它们的含义。beforeInsert 闭包在 save() 方法之前调用。beforeUpdate 闭包在 update() 方法之前调用。beforeDelete 闭包在 delete() 方法之前调用。最后,从数据库加载类后调用 onload。
假设您的公司已经制有给数据库记录加时间戳的策略,而且将这些字段的名称标准化为 cr_time 和 up_time。有几个方案可使 Grails 符合这个企业策略。一个是使用在 “Grails 与遗留数据库” 中学到的静态映射技巧将默认 Grails 字段名称与默认公司列名称关联,如清单 18 所示:
清单 18. 映射时间戳字段
class Hotel{
  Date dateCreated
  Date lastUpdated
  
  static mapping = {
    columns {
      dateCreated column: "cr_time"
      lastUpdated column: "up_time"
    }
  }
}

另一种方案是将域类中的字段命名为与企业列名称匹配的名称,并创建 beforeInsert 和 beforeUpdate 闭包来填充字段,如清单 19 所示(不要忘记将新字段设置为 nullable— 否则 save() 方法会在 BootStrap.groovy 中静默失败)。
清单 19. 添加 beforeInsert 和 beforeUpdate 闭包
class Hotel{
  static constraints = {
    name()
    crTime(nullable:true)
    upTime(nullable:true)
  }

  String name
  Date crTime
  Date upTime

  def beforeInsert = {
    crTime = new Date()
  }

  def beforeUpdate = {
    upTime = new Date()
  }  
}

启动和停止应用程序几次,确保新字段按预期填充。
像到目前为止看到的所有其他事件一样,您可以决定如何使用它们。回忆一下 “Grails 服务和 Google 地图”,您创建了一个 Geocoding 服务来将街道地址转换为纬度/经度坐标,以便可以在地图上标示一个 Airport。在那篇文章中,我让您在 AirportController 中调用 save 和 update 闭包中的服务。我曾试图将此服务调用移动到 Airport 类中的 beforeInsert 和 beforeUpdate,以使它能够透明地自动发生。
如何在所有类中共享这个行为呢?我将这些字段和闭包添加到 src/templates 中的默认 DomainClass 模板中。这样,新创建域类时它们就有适当的字段和事件闭包。
回页首
结束语
Grails 中的事件能帮助您进一步自定义应用程序运行的方式。可以扩展构建过程,而无需通过在脚本目录中创建一个 Events.groovy 文件来修改标准 Grails 脚本。可以通过向 BootStrap.groovy 文件中的 init 和 destroy 闭包添加自己的代码来自定义启动和关闭进程。最后,向域类添加 beforeInsert 和 beforeUpdate 等闭包,这允许您添加时间戳和地理编码等行为。
在下一篇文章中,我将介绍使用 Grails 创建基于数据具象状态传输(Representational State Transfer,REST)的 Web 服务的思想。您将看到 Grails 能轻松支持 HTTP GET、PUT、POST 和 DELETE 操作,而它们是支持下一代 REST 式 Web 服务所需的。到那时,仍然需要精通 Grails。
分享到:
评论

相关推荐

    Grails Grails Grails

    5. **构建工具**:Grails 使用Gradle作为其构建工具,允许自定义构建流程和依赖管理。 **Grails1.1中文文档** 《Grails1.1中文文档》是Grails 1.1版本的官方中文指南,包含了框架的详细介绍、安装指南、基本概念、...

    Grails权威指南 Grails权威指南

    5. **Grails插件系统**:Grails拥有庞大的插件库,涵盖各种功能,如安全、缓存、报表、测试等。通过插件,开发者可以轻松地扩展框架功能,避免重复造轮子。 6. **Grails构建工具**:Grails的构建系统自动化处理许多...

    Eclipse下搭建Grails项目

    创建新项目时,你可以利用配置好的Grails外部工具执行`create-domain-class`等命令,快速生成领域模型类。在Eclipse中,Grails项目的结构和管理将得到很好的支持,包括源代码编辑、构建、测试和调试。 注意,尽管...

    一步一步学grails(5)

    5. `render`方法的使用,将模型数据和视图结合,同时保留请求参数。 6. 创建查询表单的GSP页面,收集用户输入并触发查询操作。 通过以上步骤,我们可以构建出一个高效且用户友好的动态组合查询和分页系统,这是...

    grails-用户手册

    《Grails用户手册》 Grails,作为一个基于Groovy语言的开源Web应用框架,深受开发者喜爱,它简化了Java开发的复杂性,提供了强大的MVC(Model-View-Controller)架构,以及丰富的插件系统。这份用户手册将帮助你...

    grails-2.4.4.zip

    5. **Grails插件系统** Grails 的强大之处在于其丰富的插件库,如Spring Security用于安全控制,Hibernate Search提供全文搜索功能,以及各种用于支付、邮件发送、社交网络集成的插件,极大地扩展了框架的功能。 6...

    grails中文入门简介

    用户可以创建可复用的Grails脚本,并利用事件来扩展脚本功能。Grails还支持Ant和Maven,这意味着可以利用这两种构建工具的生态来管理Grails项目。 对象关系映射(GORM)是Grails框架中的一个重要组成部分,它为...

    grails中文参考手册

    Grails提供了多种命令用于生成模型类、控制器、服务等工件,如`generate-domain-class`用于创建领域模型,`generate-controller`用于生成控制器。 9. **生成Grails应用** 使用Grails的命令行工具,你可以快速生成...

    eclipse插件grails(groovy)

    Groovy是一种动态、面向对象的编程语言,而Grails则是一个基于Groovy的开源Web应用框架,采用MVC(模型-视图-控制器)架构模式。下面我们将详细介绍如何配置Eclipse插件Grails以及Groovy的相关知识点。 首先,安装...

    grails app

    在Grails中,模型类通常是Domain Class,它们直接映射到数据库表。如果我们的应用有数据存储需求,那么可能有一个或多个模型类定义在`src/groovy`目录下。 7. **配置(Configurations)** Grails应用的配置主要在`...

    grails-core源码

    3. **Domain Classes**:Grails的领域模型类,基于Groovy,具有自动持久化的能力,通过`GORM(Grails Object Relational Mapping)`与数据库交互。 4. **Controllers**:控制层负责处理HTTP请求,调用业务逻辑,并...

    Grails从入门指南(第二版)

    12. **Grails进阶**:探索更高级的主题,如异步处理、事件驱动和微服务架构。 13. **案例研究**:通过实际项目示例,综合运用所学知识,解决实际开发问题。 14. **社区与资源**:了解Grails社区,获取最新的Grails...

    Grails入门指南 -- 针对grails1.0.4更新

    - **JDK5**及以上版本。 - **Grails1.0.4**版本。 - 支持的数据库(如MySQL、PostgreSQL等)。 - **开发工具**: - IntelliJ IDEA 8.1用于开发Grails应用,提供集成开发环境支持。 - **入门示例**: - **...

    grails1.3.9

    Grails遵循模型-视图-控制器(Model-View-Controller,MVC)设计模式,将应用程序的不同部分分离,提高了代码的可维护性和可重用性。Grails的Controller处理用户请求,Domain Class代表业务模型,Service提供业务...

    Grails中文参考手册

    GORM 是 Grails 的内置 ORM 框架,它允许开发者使用 Domain Class 来定义数据模型,并自动提供数据库操作。GORM 支持 Hibernate 和 ActiveJDBC,使得数据库操作更加简单。 **Controllers** Controllers 在 Grails ...

    grails login

    Grails是一个基于Java的开源Web应用程序框架,它使用Groovy语言进行开发,提供了高效、简洁的编程模型。在Grails中实现用户登录功能是构建任何Web应用的基础,它确保了数据的安全性和用户权限的管理。本示例将详细...

    the definitive guide to grails 2

    在Grails框架中,域模型(Domain Classes)是用于表示业务实体的对象,它们通常映射到数据库中的表。通过GORM(Grails Object Relational Mapping),Grails提供了自动的ORM支持,使得开发者无需编写复杂的SQL语句...

Global site tag (gtag.js) - Google Analytics