- 浏览: 16395 次
- 来自: 北京
最新评论
原文地址: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 目标
可见,它的内容并不多。首先运行 clean 目标,然后运行 cleanTestReports 目标。调用堆栈后,看一下 clean 目标,如清单 2 所示:
清单 2. Clean.groovy 中的 clean 目标
如果需要自定义 clean 命令的行为,可以在此添加自己的代码。不过,使用此方法的问题是:每次升级 Grails 时都必须迁移自定义内容。而且从一台计算机移动到另一台计算机时,您的构建会更容易出错。(Grails 安装文件很少签入版本控制 — 只检签入用程序代码)。为了避免可怕的 “but it works on my box” 综合症,我倾向于将这些类型的自定义内容放在项目中。这确保来自源控件的所有新签出都包含成功构建所需的自定义内容。如果使用持续集成服务器(比如 CruiseControl),也有助于保持一致性。
注意,在 clean 目标期间会抛出几个事件。CleanStart 在过程开始之前发生,随后发生 CleanEnd。您可以在项目中引入这些事件,将自定义代码与项目放在一起,不要改动 Grails 安装文件。您只需要创建一个监听器。
在项目的脚本目录中创建一个名为 Events.groovy 的文件。添加清单 3 所示的代码:
清单 3. 向 Events.groovy 添加事件监听器
如果输入 grails clean,应该看到类似于清单 4 的输出:
清单 4. 显示新注释的控制台输出
$ grails clean
当然,您可以不向控制台写入简单的消息,而是进行一些实际工作。可能需要删除一些额外的目录。您可能喜欢通过用新的文件覆盖现有文件来 “重置” XML 文件。任何能在 Groovy(或通过 Java 编程)中完成的工作都可以在这里完成。
CreateFile 事件
以下是另一个可在构建期间引入的事件示例。每次输入 create- 命令之一(create-controller、create-domain-class 等等),都会触发 CreatedFile 事件。看看 scripts/CreateDomainClass.groovy,如清单 5 所示:
清单 5. CreateDomainClass.groovy
在此不能看到 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 添加工件
现在输入 grails create-domain-class Hotel 并查看结果。如果没有使用 Subversion,此命令将静默失败。如果使用 Subversion,输入 svn status。此时应该看到添加的文件(域类和对应的集成测试)。
发现调用的构建事件
要发现什么脚本抛出什么事件,最快方式是搜索 Grails 脚本中的 event() 调用。在 UNIX® 系统中,可以使用 grep 搜索 Groovy 脚本中的 event 字符串,如清单 7 所示:
清单 7. 使用 Grep 搜索 Grails 脚本中的事件调用
知道调用的事件后,可以在 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 开始
输入 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 类添加一个字段
现在构建一个 HotelController,如清单 11 所示:
清单 11. 创建一个 Hotel Controller
注意:如果像 “Grails 与遗留数据库” 中讨论的那样禁用 grails-app/conf/DataSource.groovy 中的 dbCreate 变量,本例则应该重新添加它并设置为 update。当然,还有另一种选择是通过手动方式让 Hotel 表与 Hotel 类的更改保持一致。
现在将清单 12 中的代码添加到 BootStrap.groovy:
清单 12. 保存和删除 BootStrap.groovy 中的记录
在接下来的几个示例中,需要一直打开 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. 防故障插入和删除
如果调用 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 中特定于环境的行为
现在具备只在测试模式下插入记录的条件。但不要在此停住。我通常在 XML 文件中外部化测试数据。将这里所学到的知识与 “Grails 与遗留数据库” 中的 XML 备份和还原脚本相结合,就会得到了一个功能强大的测试平台(testbed)。
因为 BootStrap.groovy 是一个可执行的脚本,而不是被动配置文件,所以理论上可以在 Groovy 中做任何事情。您可能需要在启动时调用一个 Web 服务,通知中央服务器该实例正在运行。或者需要同步来自公共源的本地查找表。这一切都有可能实现。
回页首
微型事件
了解一些大型事件后,现在看几个微型事件。
为域类添加时间戳
如果您提供几个特别的命名字段,GORM 会自动给它们添加时间戳,如清单 15 所示:
清单 15. 为字段添加时间戳
顾名思义,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 中删除时间戳字段
这会限制在创建和编辑视图中创建字段,但仍然在列表中保留字段并显示视图。创建一两个 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. 映射时间戳字段
另一种方案是将域类中的字段命名为与企业列名称匹配的名称,并创建 beforeInsert 和 beforeUpdate 闭包来填充字段,如清单 19 所示(不要忘记将新字段设置为 nullable— 否则 save() 方法会在 BootStrap.groovy 中静默失败)。
清单 19. 添加 beforeInsert 和 beforeUpdate 闭包
启动和停止应用程序几次,确保新字段按预期填充。
像到目前为止看到的所有其他事件一样,您可以决定如何使用它们。回忆一下 “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 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。
发表评论
-
grais Communications link failure
2015-11-12 16:15 1493WARNING: Failed to execute: sel ... -
10、groovy grails List 集合 排序方法1
2015-10-09 19:11 2542class Contract implements Compa ... -
8、grails 打war报错 Error Error executing script War: GC overhead limit exceeded
2015-09-17 13:33 1033grails版本:2.4.3 运行grails war包: ... -
6、不生成 version 字段
2015-09-01 15:37 666class UserSystemInfo { i ... -
4、Grails的主键id生成规则
2015-08-31 19:29 1122原文:http://aiwenoyo.blog.163.com ... -
3、grails修改默认首页
2015-08-31 10:32 2075原文地址:http://www.blogjava.net/sh ... -
2、 打war包过滤掉js
2015-08-31 10:29 1181erp系统中遇到的问题,时间控制本地没有问题,打war包压缩后 ... -
1、 grails时间格式化配置
2015-08-31 10:28 717在/grails-app/conf/config.groovy ...
相关推荐
5. **构建工具**:Grails 使用Gradle作为其构建工具,允许自定义构建流程和依赖管理。 **Grails1.1中文文档** 《Grails1.1中文文档》是Grails 1.1版本的官方中文指南,包含了框架的详细介绍、安装指南、基本概念、...
5. **Grails插件系统**:Grails拥有庞大的插件库,涵盖各种功能,如安全、缓存、报表、测试等。通过插件,开发者可以轻松地扩展框架功能,避免重复造轮子。 6. **Grails构建工具**:Grails的构建系统自动化处理许多...
创建新项目时,你可以利用配置好的Grails外部工具执行`create-domain-class`等命令,快速生成领域模型类。在Eclipse中,Grails项目的结构和管理将得到很好的支持,包括源代码编辑、构建、测试和调试。 注意,尽管...
5. `render`方法的使用,将模型数据和视图结合,同时保留请求参数。 6. 创建查询表单的GSP页面,收集用户输入并触发查询操作。 通过以上步骤,我们可以构建出一个高效且用户友好的动态组合查询和分页系统,这是...
《Grails用户手册》 Grails,作为一个基于Groovy语言的开源Web应用框架,深受开发者喜爱,它简化了Java开发的复杂性,提供了强大的MVC(Model-View-Controller)架构,以及丰富的插件系统。这份用户手册将帮助你...
5. **Grails插件系统** Grails 的强大之处在于其丰富的插件库,如Spring Security用于安全控制,Hibernate Search提供全文搜索功能,以及各种用于支付、邮件发送、社交网络集成的插件,极大地扩展了框架的功能。 6...
用户可以创建可复用的Grails脚本,并利用事件来扩展脚本功能。Grails还支持Ant和Maven,这意味着可以利用这两种构建工具的生态来管理Grails项目。 对象关系映射(GORM)是Grails框架中的一个重要组成部分,它为...
Grails提供了多种命令用于生成模型类、控制器、服务等工件,如`generate-domain-class`用于创建领域模型,`generate-controller`用于生成控制器。 9. **生成Grails应用** 使用Grails的命令行工具,你可以快速生成...
Groovy是一种动态、面向对象的编程语言,而Grails则是一个基于Groovy的开源Web应用框架,采用MVC(模型-视图-控制器)架构模式。下面我们将详细介绍如何配置Eclipse插件Grails以及Groovy的相关知识点。 首先,安装...
在Grails中,模型类通常是Domain Class,它们直接映射到数据库表。如果我们的应用有数据存储需求,那么可能有一个或多个模型类定义在`src/groovy`目录下。 7. **配置(Configurations)** Grails应用的配置主要在`...
3. **Domain Classes**:Grails的领域模型类,基于Groovy,具有自动持久化的能力,通过`GORM(Grails Object Relational Mapping)`与数据库交互。 4. **Controllers**:控制层负责处理HTTP请求,调用业务逻辑,并...
12. **Grails进阶**:探索更高级的主题,如异步处理、事件驱动和微服务架构。 13. **案例研究**:通过实际项目示例,综合运用所学知识,解决实际开发问题。 14. **社区与资源**:了解Grails社区,获取最新的Grails...
- **JDK5**及以上版本。 - **Grails1.0.4**版本。 - 支持的数据库(如MySQL、PostgreSQL等)。 - **开发工具**: - IntelliJ IDEA 8.1用于开发Grails应用,提供集成开发环境支持。 - **入门示例**: - **...
Grails遵循模型-视图-控制器(Model-View-Controller,MVC)设计模式,将应用程序的不同部分分离,提高了代码的可维护性和可重用性。Grails的Controller处理用户请求,Domain Class代表业务模型,Service提供业务...
GORM 是 Grails 的内置 ORM 框架,它允许开发者使用 Domain Class 来定义数据模型,并自动提供数据库操作。GORM 支持 Hibernate 和 ActiveJDBC,使得数据库操作更加简单。 **Controllers** Controllers 在 Grails ...
Grails是一个基于Java的开源Web应用程序框架,它使用Groovy语言进行开发,提供了高效、简洁的编程模型。在Grails中实现用户登录功能是构建任何Web应用的基础,它确保了数据的安全性和用户权限的管理。本示例将详细...
在Grails框架中,域模型(Domain Classes)是用于表示业务实体的对象,它们通常映射到数据库中的表。通过GORM(Grails Object Relational Mapping),Grails提供了自动的ORM支持,使得开发者无需编写复杂的SQL语句...