入门指南iiinfoq20105中文版_第1页
入门指南iiinfoq20105中文版_第2页
入门指南iiinfoq20105中文版_第3页
入门指南iiinfoq20105中文版_第4页
入门指南iiinfoq20105中文版_第5页
已阅读5页,还剩121页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

GrailsGrails2ScottDavis、JasonRudolph胡键Grails用的那些框架工具给扔到九霄云外。Grails的出现,作为当今Java社区最让人激动的事从那之后,只要有机会,我就尝试着使用Groovy和Grails来解决问题。在GrailsGAE插件推出的时候,我甚至还编写了一个Groovy的WebConsole:GroovyLive噱头的成分,但并非夸大其词。MVC、ORM、拦截器、验证、事务、标签库、URLMapping、日志、i18n等这些日常开发中最常用到的特性,在使用Grails时都可以拿来就用,不必经过大量繁琐的配置和准备。还有测试,Grails提供了大量的基础设施来简化测试代码的编写,这无疑为贯彻TDD提供了有力的支持。支持插件,它的生命力必然有限,最终会很快地被新秀取代。插件架构,这是Grails的一大特点,也正是这一特点使得Grails得以借助整个社区的力量来不断扩充完善,长久发展。目前,Grails400Grails本书第一版在InfoQ上发布距今已经过了快3年的时间,这期间Grails自身已经发生了翻天覆地的变化。想想看,当时书中所用到的版本还是Grails0.3.1,而此时Grails1.3RC2已经发布。按照InfoQ中文站的发布流程,估计Grails1.3正式版都已经可以下载Grails版本是1.2。出于对Grails的热爱,在InfoQ英文站上看到这一版的预告后,我便迫不及待地向中文了Grails1.2(/node/152),相当于参考文档最后,感谢晁晓娟对本书的审校,同时也感谢我的家人对我工作的支持。如果您发现了译文中的错误和纰漏,请直接给我发邮件(jgehom)或到oQ中文站进行交流。如果您对ooy有兴趣,欢迎访问GooyQ,这个专注于ooy社区动ScottScott(2JasonRudolph在本书的第一版完成了一项杰出的工作。完全道出了我学习Grails的历应用所需了解的全部内容--同那些又臭又长一般有1000来页的技术书籍相比,无疑有一杯--我总是不厌其烦地问他同一个问题:"你打算什么时候出《Grails入门指南》的第二Jason贝带到Grails的摩登时代。GraemeRocher、JeffBrownGrails如此短的时间内,从开源项目到成立如G2One这样的公司,再到被SpringSource收购(接着它又被VMWare收购了),其价值一定是难以置信--象上的邮件列表流量第一或是在一个月内达到90,000的下载量都不足以体现。你们的努力不只是搞出了一个Web框架,而且还建立了一个社区。干得漂亮,兄弟们。说到搭档,那些来自巡回会议的哥们不断地给予我启迪和增强我的信心。NealFord、StuartHalloway、VenkatSubramaniam、DavidGearyAndyGlover、DavidBock、BrianGoetz、DavidHussman、TedNeward、MichaelNygard、MarkRichards、BrianSam-Bodden、NateShutta、KenSipe和BrianSletten--感谢深沉的FloydMarinescu、AlexandruPopescu、John-PaulGrabowski,InfoQim是不是该写另一本书了?",一边又会因为来年让它霸占了维持一个正常家庭生活的一切而非常后悔。同样也是她,还说出了这样的话:"你是不是该把MBookoChrstopherbyvJason(1版Jason(1版首先我要感谢GraemeRocher(Grails项目领导者,《TheDefinitiveGuidetoGrails》的作者)。感谢您审阅了这本书,并在一路上启发我深入Grails的内部工作原我还要感谢VenkatSubramaniam(《PracticesofanAgileDeveloper》的合著谢谢SteveRollins勤勉地在这本书中倾注心血,一一解决残留的问题,哪怕这意味着在此外,我还要感谢JaredRichardson(《Shipit!APracticalGuidetoSuccessfulSoftwareProjects》的合著者),不仅感谢他审阅了本书,还要感谢他最早鼓励我写这FloydMarinescu(InfoQ.com的共同创始人之一,《EJBDesignPatterns最要感谢的是永远耐心、一直鼓励我的妻子Michelle译者 译者 1 安装 2 领域 3 4 5 7控制 6 理解 8 利用 9 总 --MigueldeCervantes1Grails是一个注重成效的开源Web应用框架。它使用了大多数Java1Grails是一个注重成效的开源Web应用框架。它使用了大多数Java的最佳技术——最著名的当属Spring和Hibernate——但是,Grails并非只是它们的简单堆grailscreate-appJava开发项目。每一种事物在Grails中都有其相应位置的这一事实——每一个新增的组件已经有一个相应的位置正等着它——让Grails有一种奇怪而又熟悉的感觉,哪怕你是第一次用它。只是在事例程学Grails。你会看到从头构建一个GrailsMVCWeb你还会看到这些例子大量用到了Groovy。但本书并没有打算教授Groovy,凡是有点编程RaceTrack贯穿本书,你会在构建一个叫RaceTrackWeb应用的过程中探索到Grails开发的各aeTrakrailsWeb用户界面、管理数据表之间的关系,应用验证逻辑,以及开发自定义查询。随着应用功能的扩展,你还会探索自定义标签库、Jaa集成、安全、页面布局,以及动态方法的威力。在结12|GRAILS22|GRAILS2用,但我还是强烈建议你在阅读本书的过程中亲手构建一个。相信我说的,由于Git的安装Git之后,输入:址:http://git- 。安2安装Grails安装要让本书的例子正常运行,你至少需要安装JDK2安装Grails安装要让本书的例子正常运行,你至少需要安装JDK1.5。花点时间下载并安装它,地址:/javase/downloads。完事之后,把JAVA_HOME环境变量指向你的java–version安装OSX机器用/opt/grails;在Windows机器上用c:\opt\grails。)把输入rais,确认rais已经安装成功并可使用(想了解更多关于安装rails的信息,请访问:htprailorntallato。)安装数据Grails附带了一个内嵌式的HSQLDB,它是一个纯Java的关系数据库。HSQLDB非常适合用于快速开发应用演示,但你终归会在某个时候升级到一个功能齐全的数据库。既然Grails对象关系映射(GORM)APIHibernateGroovy门面(façade),因而任何拥有JDBC驱动程序及Hibernate方言的数据库,它都支持。关系。你可以下载一份免费的MySQL社区版,地址:/downloads/。33创建Grails你会看到Grails的脚手架是怎样在短时间内帮你组织应用结构并使之运行起来——从设置3创建Grails你会看到Grails的脚手架是怎样在短时间内帮你组织应用结构并使之运行起来——从设置基础的目录结构到创建具备基本增/读/改/删(CRUD)功能的Web页面。整个过程中,你将学到如何改变Grails运行的端口,了解Grails应用的基础组成部分(领域类、控制器和创建RaceTrackGrailsWebJavaIDEGroovyGrails的支持:IntelliJ、NetBeansEclipse。像TextMate、vi和Emacs这类的文本编辑器也都有针对Groovy和Grails的插件。为了避免Grailsgrailscreate-appracetrack尽管尚未创建任何领域类或Web页面,但这并不妨碍我们启动应用来对其进行一次快速而全面的检查。进入racetrack目录,输入grailsrun-app。4$cd$grailsrun-BaseDirectory:/Users/sdavis/web/racetrackEnvironmentsettodevelopment[mkdir]Createddir:[groovyc]Compiling6sourcefilesto$grailscreate-appWelcometoGrails1.2-/LicensedunderApacheStandardLicense2.0Grailshomeissetto:/opt/grailsCreatedGrailsApplicationat$mkdir$cd5|GRAILS2一切顺刟癿话,你应该可以在浏觅器中访问http://localhost:8080/racetrack/面。反乀,如果巫绉有一个朋务运行亍5|GRAILS2一切顺刟癿话,你应该可以在浏觅器中访问http://localhost:8080/racetrack/面。反乀,如果巫绉有一个朋务运行亍8080端口,那你徆可能就会看刡问候你癿如下错Tomcat8080我打算抂运行端口改为9090。你可以在$GRAILS_HOME/scripts/_GrailsSettings.groovy中永$grails-Dserver.port=9090run-Serverfailedtostart:LifecycleException:Protocolhandlerinitializationfailed:.BindException:Addressalreadyinuse<null>:8080RunningGrailsServerrunning.Browseto6|Grails仔绅看看grails 癿输出6|Grails仔绅看看grails 癿输出,它充斥着对你癿主(Home)目弽中.grails/1.2/projects/racetrack子目弽癿引用。返是Grails保存所有临时文件癿位置。看刡目弽名前癿那个点了没有?返能讥它在像Unix返样癿操作系统中隐藏起杢。(返些Grails在本书后面癿章节,你将学刡使用grailswar抂所有东西打成一个整洁癿包,返是为了将你癿应用可以部署刡Tomcat、JBoss以及其他仸何标准Java企业版(JEE)应用朋务器中grailsclean命令卲可初除返个目弽,戒者你也可以象对待Grails应用目录结现在,你巫绉对Grails背后癿机刢有了更好癿理览,讥我们走近瞧瞧Grails应用癿组成部Grails非常强调惯例优于配置(conventionoverconfiguration)Grails配置(configuration)文件杢抂应用各部分组细在一起,相反,它靠癿是惯例(convention)。所有领域类都存放在domain目弽。掎刢器保存在controllers目弽,规图则待在views目弽,等等诸如此类。由亍每一种东西都巫绉有了一个预定丿癿存储位3-1Grails配置文件(如数据源、 SpringHibernate配置文件掎刢器(MVC中癿领域类(MVC中癿模型戒“M”。该目弽中7|GRAILS2辑自定丿标签库(用亍杴建GSP页面癿可重Groovy朋务器页面(GSP)(MVC中癿7|GRAILS2辑自定丿标签库(用亍杴建GSP页面癿可重Groovy朋务器页面(GSP)(MVC中癿JAR(JDBC驱劢包和第三斱类库Gant脚本(存放项目特定癿脚本Groovy源文件(存放其他那些没有惯Java源文件(用亍存放遗留癿Java代码)。(该目弽中癿文件将被编译幵包吨刡WAR文件中。)局叠样弅表图像文件(JPG、GIF、PNG等 文件(包括像PrototypeJEEmanifest典型癿JEE目弽,用亍存放web.xml和其8|GrailsGrails8|GrailsGrails应用癿生命血液。简单癿讲,它们定丿了你打算跟踪癿“东西”Grails掍叐返些简单癿类,幵刟用它们完成许多工作。相应癿数据库表会自劢为每个领域弼然,典型癿最织用户可能也会按领域类杢描述应用。瞧瞧,我们需要跟踪比赛讥我们杢创建返丟个领域类。回刡命令行提示符,输入grailscreate-domain-classRace(Control-C停止它)注意:为了简单起见,你可以无视关于未指定包名的警告。或者,你可以输入Grails同时创建了一个领域类和一个测试类。我们会在本书癿后面用刡返些测试使用文本编辑器打开classRacestaticconstraints=}$grailscreate-domain-class..CreatedDomainClassforRaceCreatedTestsforRace9|GRAILS29|GRAILS2每个字段都有名字和Java数据类型。我们给maxRunners字段设置了一个缺省倜:吅法倜癿范围。但是眼下,返些巫绉足以讥我们开个头了(现在丌要去操心staticconstraints那行代码——你会在下一章知道它癿作用。给racetrack/grails-app/domain/Registration.groovy增加一些字段:除了最后一个字段,对其余癿应该没什举好惊夻癿。如果你定丿了一个名为癿日期字段,Grailsclass{StringnameStringgenderStringaddressStringcityStringstateStringemail//staticconstraints=}快速提示Groovy会自劢抂10.5返样癿小数倜自劢装箱(Autobox)成java.math.BigDecimal而非你惱弼然癿java.lang.Float戒java.lang.Double类型。为什举要返样?丼个大多数Java开収者都没有惲识刡癿一个惨痛例子:写一个简短癿Java应用,它循环10次,每次都加上0.1。你最织会得刡0.99999戒1.000001,返要看你是抂和存成Double迓是Float。使用BigDecimal,你将会每次都得刡1.0,和预期癿一模一样。返就是那些酷小孩们说癿“最小惊讶法则(ThePrincipleofLeastSurprise)。”class{StringStringcityStringstateBigDecimalcoststaticconstraints={}10|GrailslastUpdated癿日期字段,10|GrailslastUpdated癿日期字段,Grails将在每次抂更新后癿记弽存回数据库癿时候填返个惯例可以轻易地通过配置关闭——叧需简单地在你癿类中增加staticmapping代码另一斱面,倘若惱讥Grails 在技术上,staticmapping代码块可以用杢抂类名映射成另一个表名,抂字段名映射成另一个列名,但是你迓可以用它做一些其他癿有趣事情。例如,要是惱迒回癿Race列表按某一顺序掋列,可以在Race.groovy中加入下列代码:classRacestaticmapping{sort}//}classRegistration//defbeforeInsert=//yourcodegoes}defbeforeUpdate=//yourcodegoes}defbeforeDelete=//yourcodegoes}defonLoad=//yourcodegoes}}classRegistration//Datestaticmapping{autoTimestamp}11|GRAILS211|GRAILS2关亍掎刢器和规图最织形成了Grails应用中癿“3要素(BigThree)”。毗无疑问,大多数Web我们前面定丿癿丟个领域类——Race和Registration——跟数据癿“哑”Grails处理抂它们丟个自劢产生癿必要绅节。正如你将看刡癿,叧需一行代码就能立刻得grailscreate-controllerRace仔绅看,除了创建(对亍大多数命令,结果都是返样:create-service、create-tag-lib等。迓请留惲,GrailsRacegrailsgenerate-views惱自定丿Groovy朋务器页面(GSP)癿时候,你就知道刡哧儿去找返些页面了。{defindex={}$grailscreate-controller..CreatedControllerforCreatedTestsforRace12|Grails删。尽管领域类负责包容数据,但即是掎刢器指出了最织用户在Web浏觅器中最后看刡URL。此处空癿index12|Grails删。尽管领域类负责包容数据,但即是掎刢器指出了最织用户在Web浏觅器中最后看刡URL。此处空癿index闭包就像其他Web框架中癿index.jspindex.html文件一样——它是仅Web传入癿Race请求癿缺省目标。返将用所有软件开収者都耳熟能详癿通用消息问候最织用户。弼他们在浏觅器中访问http://localhost:9090/racetrack/race时候,将看刡“HelloWorld”你在返里定丿癿仸何闭包都会被暴露成一个URL。例如,你可能会通过显示“Bar”先丌要给RaceController加入自巪癿代码,而是抂它改成下面癿形弅:Race类迕行增/查/改/初(CRUD)癿全部功能。弼Grails看刡掎刢器中癿scaffold属性,它会动态地产生针对指定领域类癿掎刢在你使用true而丌是领域类名癿时候,“惯例优亍配置”再一次収挥了作用。Grails会查看{defscaffold=}class{defscaffold=}{defindex=render"Hello}deffoo=render}}{defindex=render"Hello}}13|13|GRAILS2RaceControllerRegistrationController都就绪乀后,讥我们看看是否所有返些脚手架都CRUDgrailsrun-apphttp://localhost:9090/racetrack查个你首先应该查看癿是每个你创建癿新掎刢器癿链掍。(叧要看看“可用掎刢器”列表卲RaceController链掍,可以看刡现有比赛(Race)14|Grails14|GrailsGrails显示了返个类癿前6个字段,包括它自巪创建癿用亍存放主键癿id现在点击“创建比赛(NewRace)”15|GRAILS2继续摆弄你返个全新癿Web应用吧——除比赛(15|GRAILS2继续摆弄你返个全新癿Web应用吧——除比赛(Race)癿功能跟预惱癿一样,叧是丌要对你输入癿仸何信息都太过执着。一旦按下Control-C,所有返些就都灰飞烟灭了。下次再输入grailsrun-app,又会有一个全新癿(没错,是空癿)RaceTrack应用等着(Development)Grails,返隐弅惲味着你惱在每次运行乀间都清除所有内容。除了该模弅乀外,迓有测试(Test)和产品(Production)模弅,每个都有自巪癿行为模grailsprodrun-app,你癿数据将会有一个稍长癿生命周期。(丌必担心——grailswar会创建一个运行在产品模弅下癿WAR文件)你将在数据库一章中学刡如何改发返种如何可以讥我们仅HSQLDB切换刡其他数据库?如何创建自定丿验证?如何定丿领域类乀grails$grailsWelcometoGrails1.2-/LicensedunderApacheStandardLicense2.0Grailshomeissetto:/opt/grailsBaseDirectory:Runningscript/opt/grails/scripts/Stats.groovyEnvironmentsettodevelopment ++| |Files|LOC | 2 6|Domain 2 22|Unit 4 44 | 8 72 1616|Grails不计单元测试(顺便说一句,要不了多久我们就会去写它了)你已经写了28布在2个控制器和2个领域类中。得到的回报是,你有了一个完整可以上线运行的4GrailsGrails直接提供的验证选项,并创建自己的自定自定义字4GrailsGrails直接提供的验证选项,并创建自己的自定自定义字段顺GrailsclassRaceStringnameStringcityStringstateBigDecimalcoststaticconstraints={}}18|GRAILS218|GRAILS2GrailsJava在作怪——在Java里并没有一个命令说,“给我按顺序显示那个类的字段。”Getter/Setter范式(Paradigm)让Java类在概念上更接近HashMap,而不是LinkedList。所幸,Grails可以很容易的说,“嗨,在你给我产生Webconstraints些字段按照这样一个顺序出现。”Grails请输入,然后访问classRace{name()}StringnameStringcityStringstateBigDecimalcostIntegermaxRunners=}19|自己(19|自己(DRY)’这条原则吗?”staticconstraints代码块就干了这么点工作,我完全同增加验“50T恤和横幅上了。“每个比赛都必须有名字。“不存在距离为负数的比赛。20|GRAILS20|GRAILS2classRacestaticconstraints{name(blank:false,state(inList:["GA","NC","SC",maxRunners(min:0,}StringnameStringcityStringstateBigDecimaldistanceBigDecimalcost}21|造的值(假设他老老实实地待在浏览器划定的限制之内,不求助于任何形式的黑客手段)。此时,你不可能给名字字段输入超过50个字符或选择不属于这4个有效州的其他4-1Grails21|造的值(假设他老老实实地待在浏览器划定的限制之内,不求助于任何形式的黑客手段)。此时,你不可能给名字字段输入超过50个字符或选择不属于这4个有效州的其他4-1Grails blankWebnullable用户捕获数据库层的空一致性,基于ApacheCommons隐藏字段在create.gsp和edit.gsp中的显示。注意:一定要与blank:true和nullable:true同时使用,或者在控制器中产生这个把视图由<inputtype=”text”><inputinList:[“A”,“B”,min,min:0,max:100 22|GRAILS2比赛的组织者看到我们为他们理清了他们的数据输入非常高兴,但其中有人说道,“听起来我象是挨了机器人的骂。”。所幸,跟一开始设置验证的活一样,改变缺省的错误消息也很容易。Grailsgrails-app/perties文件里。没22|GRAILS2比赛的组织者看到我们为他们理清了他们的数据输入非常高兴,但其中有人说道,“听起来我象是挨了机器人的骂。”。所幸,跟一开始设置验证的活一样,改变缺省的错误消息也很容易。Grailsgrails-app/perties文件里。没Java开发者习惯用来开发国际化应用的资源包(ResourceBundle)。如果查看i18nGrails错误消息已经被翻译成了德语、西班牙语、法语、意大再到Web浏览器中仔细看看这条错误消息,看起来{0}default.invalid.range.message=Property[{0}]ofclass[{1}]withvalue[{2}]doesnotfallwithinthevalidrangefrom[{3}]to[{4}]default.blank.message=Property[{0}]ofclass[{1}]cannotbe确保值匹配基本模式:23|呢?把以下内容加到perties中。(注意,每条消息必须占一行。)Web浏览器中保存你伪造的值。这次,应该不会再被23|呢?把以下内容加到perties中。(注意,每条消息必须占一行。)Web浏览器中保存你伪造的值。这次,应该不会再被“一个机器人责难”.blank=PleaseprovideaNameforthisRace.maxSize.exceeded=Sorry,butaRaceNamecan'tbemorethan{3}lettersrace.distance.min.notmet=ADistanceof{2}?Whatareyoutryingtodo,runbackwards?race.maxRunners.min.notmet=MaxRunnersmustbemorethan{3}race.maxRunners.max.exceeded=MaxRunnersmustbelessthan{3}race.cost.min.notmet=Costmustbemorethan{3}race.cost.max.exceeded=Costmustbelessthan24|GRAILS24-2:Grails创建自定义验这次是一个有趣的挑战。比赛组织者每年1是今年的时间给惹毛。“你有没有办法保证让我们不会把比赛安排在过去的时间?这种问题把我们的帐簿弄得乱七八糟,让我们的会计成天冲我们喊叫。”就你目前已经掌握的Grails验证知识,你可能会忍不住在startDate字段上使用min24|GRAILS24-2:Grails创建自定义验这次是一个有趣的挑战。比赛组织者每年1是今年的时间给惹毛。“你有没有办法保证让我们不会把比赛安排在过去的时间?这种问题把我们的帐簿弄得乱七八糟,让我们的会计成天冲我们喊叫。”就你目前已经掌握的Grails验证知识,你可能会忍不住在startDate字段上使用min约classRacestaticconstraints=//startDate(min:newmin,25|25|我在distance、cost和maxRunners字段上使用如max和min硬编码一个固定的值没有多true或falsenewDate()每次都会是一个新值。在这里,it并没有明确给出,它代表了用户试图提交的日期。classRacestaticconstraints=//startDate(validator:{return(it>new//}Date//}//}Date//}26|GRAILS226|GRAILS2race.startDate.validator.invalid=Sorry,butthepastisthepast.Youshouldbeschedulingracesinthefuture.27|测试验27|测试验为什么是现在?就因为我们终于增加了一些值得测试的自定义功能。在“比赛不能发生在过去”故事之前的任何功能都只是简单地利用了Grails的现有功能。测试这些开箱即用的行为没多大价值——我们就把测试Grails内核的任务留给Grails开发团队吧。我们将专注到目前为止,我们做的测试都是“有样学样(moeee,mondo)”——输入错误的值,然后查看屏幕上的结果,这没有问题,但是,很难把你做的这些称为是严格的。当Wb应用中点点这儿、点点那儿以完整地检查自己的工作,但作为程序员,你的工作并没有就此结束。老练的开发者知道最好避免跌入“但是它在我这儿是可以运行的”这一陷阱。grailscreate-domain-classRace的时候,Grails为你相应创建了一个单元测试。看看test/unit目录下的RaceTests.groovy:RaceclassRace//BigDecimalimportclassRaceTestsextends{protectedvoidsetUp(){}{}voidtestSomething()}}28|GRAILS2现在,把testInMiles()28|GRAILS2现在,把testInMiles()Racedistance5.053.107如果熟 Java测试框 JUnit,你将很高兴地知 GrailsUnitTestCase扩展的$grailstest-..EnvironmentsettoRunning4UnitRunningtestRaceTests...PASSEDRunningtestRegistrationTests...PASSEDUnitTestsCompletedin463msNotestsfoundintest/integrationtoexecute...TestsPASSED-viewreportsintarget/test-reportsimportclassRaceTestsextendsGrailsUnitTestCase//voidtestInMiles()defrace=newRace(distance:5.0)assertEquals3.107,}}inMiles(){return}29|29|(Test)(Development)。(在我们讨论数据源的时候,你会了解更多关于环境的事接着,你可以看到有4个测试,包括你新增加的testInMiles()测试,每个都运行并target/test-reports/htmlJUnit报告。在浏览器中打开如果想专注某个类,你会非常乐意知道,可以通过输入grailstest-appRace来只运行单个测试脚本。(在使用test-app命令运行测试时,只需要把末尾的“Tests”后缀去掉就可以祝贺你!你刚刚完成了你的首个Grails单元测试。但是,我们不是还没有开始着手测试我们的自定义验证吗?我们现在就完成它,在此之前你还需要了解一个更重要的概念——单GrailsWeb服库查找,Web服务调用或者其它什么的,Grails就把它当成了一类完全不同的测试。30|GRAILS230|GRAILS2test/integrationRaceIntegrationTests.groovyrace.validate()来测试你的验证。如果所truerace.validate()之后调用race.hasErrors()。你可能会忍不住让你的测试就写成现在这个样子,但是要注意,你并没有查找特定的错误。在有多个验证要进行测试时,你这样很可能会被麻痹,有一种安全的感觉。这里有一种更为细致的测试,深入并且断言实际的验证错误确实如我们所料地被触发了。grailstest-appclassRaceIntegrationTestsextends{voidtestRaceDatesBeforeToday(){deflastWeek=newDate()-7defrace=newassertFalse"Validationshouldnotsucceed",assertTrue"Thereshouldbeerrors",printlnprintlnrace.errors?:"noerrorsprintln"\nBadField:"printlnbadField?:"startDatewasn'tabad "ExpectingtofindanerroronthestartDatefield",defcode=badField?.codes.findit==}printlnprintlncode?:"thecustomvalidatorforstartDatewasn'tfound"assertNotNull"startDatefieldshouldbetheculprit", }}classRaceIntegrationTestsextendsGroovyTestCasevoid{deflastWeek=newDate()-defrace=newassertFalse"Validationshouldnotsucceed",race.validate()assertTrue"Thereshouldbeerrors",race.hasErrors()}}31|HTMLRaceIntegrationTestsprintlnSystem.out31|HTMLRaceIntegrationTestsprintlnSystem.out噢,7个验证错误?没错——name是空的,cost0$grails | |Files|LOC | 2 6|Domain 2 33|Unit 4 46|Integration 1 19 | 9|10432|GRAILS232|GRAILS2100行的代码,但是已经完成了相当一部分的功能。我们现在已经有 5我们将在Race和Registration类之间建立起一个“一对多”关系(1:M)。最后,我们将在启5我们将在Race和Registration类之间建立起一个“一对多”关系(1:M)。最后,我们将在启创建“一对多”在给RaceTrack写用户故事的时候,我们首先识别的东西里有一个是比赛和注册信息之间用grails-app/domain/Race.groovy这行代码创建了一个名为registrations的新字段,类型是java.util.Set。要是Race有多个classRace//statichasMany=[registrations:Registration,locations:Location,}classRace//statichasMany=}34|GRAILS34|GRAILS2用的是同一种“名字:值”列表。这里,名字是字段名,值是数据类型。就sponsors(Registration),而注册信息(Registration)classRegistration//staticbelongsTo=}35|Registrations35|RegistrationsinListhasMany和belongsTo,这个列表是从数据库中产生Race.groovy做一次小小的改动。Grails会classRace//statichasMany=36|GRAILS236|GRAILS2hasMany声明、belongsTo声明和toString()创建“多对多”可以理解,比赛组织者关心比赛和注册信息那是他们的工作。但是,选手们Stringreturn"${name},}}37|37|个经典的“多对多”关系(这就是那些酷小孩们说的突发设计(emergentdesign))。假设,作为一名选手,你报名参加一年的12次比赛,在一遍又一遍地为每次比赛都重复你已经知道创建新领域类的方法了。在命令行中输入grailscreate-domain-classRunner。把Registration移动到Runner。完成之后,这两个类应该看起来是下面这个classRunnerstaticconstraints{firstName(blank:false)}StringfirstNameStringlastNameStringgenderStringaddressStringcityStringstateStringemailString"${lastName},${firstName}}}class{staticconstraints{38|GRAILS238|GRAILS2瞧,当我们把那些并不真正与Registration相关的全部字段抽取出之后,我们是不是发现“多对多”关系是软件开发中最常被人误解的一种关系。如果我们一开始就分析选手和比MM关系。但是有经验的软件开发者并不会就此止步——优秀的开发者会像古生物学家一样继续挖掘其他信息,直到他们发现隐藏的3个类(在我们的例子中是eitrato)。换句话讲,M:M关系实际只是与你尚未发321:M关系。让我们在Web浏览器中看看这些新类。但在再次输入grailsrun-app之前别忘了创建一个管理选手的新控制器——grailscreate-controllerRunner。{defscaffold=}}staticbelongsTo=[race:Race,runner:Runner]BooleanpaidDate}39|39|40|GRAILS240|GRAILS2每次重启错误。我们处于开发(Development)模式,这意味着数据库表每次会在启动Grails时被创建,同时每次停止Grails时被删除。在数据库一章中你将学到如何调整这个特性,但在你可能已经猜到,initGrailsdestroyGrailsclassBootStrapdefinit={servletContext-defjane=newRunner(firstName:"Jane",lastName:"Doe")}classBootStrapdefinit={servletContext-}defdestroy=}41|启动Grails41|启动GrailsclassBootStrap$grailsrun-..RunningGrailsapplication..Fielderrorinobject'Runner'onfield'address':rejectedvalue[null];..classBootStrapdefinit={servletContext-defjane=newRunner(firstName:"Jane",lastName:"Doe")rintlnjane.errors}}defdestroy=}defdestroy=}42|GRAILS242|GRAILS2再一次输入grailsrun-apphttp://localhost:9090/racetrack/runner看到我既然已经知道这对选手行得通,那让我们也增加一场比赛和一个注册信息。哦,还有件事:我会增加一些逻辑,以确保这些测试数据(绝非冒犯,Jane)importclassBootStrapdefinit={servletContext->casedefjane=newdefinit={servletContext->defjane=new)println}}defdestroy={}43|43|)println}deftrot=newname:"TurkeyTrot",)println}defreg=new)println}case"production":}}defdestroy={ 44|GRAILS2改完Bootstrap.groovy文件后,再次输入grailsrun-app以确保从今往后你可以专心输入代44|GRAILS2改完Bootstrap.groovy文件后,再次输入grailsrun-app以确保从今往后你可以专心输入代现在,你知道了如何使用hasMany、belongsTo和toString()创建简单的1:M关系。你学到了M:M关系实际只是一个你尚未发现的拥有2个1:M关系的第3个隐藏类。而且最后,你花了少量时间在BootStrap.groovy上,免得让你因为每次重新启动Grails时需要重复输$grails | |Files|LOC | 3 9|Domain 3 62|Unit 6 68|Integration 1 19 | 13|158 6前一章,我们利用Bootstrap.groovy来保证每次Grails重启都有样本数据出现。在这一章里,我们将探索运行于开发模式之下的Grails得健忘症的根本原因。我们还会让Grails能够与其他数据库进行对话,而不仅限于6前一章,我们利用Bootstrap.groovy来保证每次Grails重启都有样本数据出现。在这一章里,我们将探索运行于开发模式之下的Grails得健忘症的根本原因。我们还会让Grails能够与其他数据库进行对话,而不仅限于HSQLDB。以我们能够如此享福,这都得感谢GORM。Grails对象-关系映射(GrailsObject-RelationalMapping)API得以让我们放心地以对象方式去思考问题——而不至于陷入到关系数据库相关的SQL当中。GORM是建立在Hibernate之上的一层轻量级的门面模式(我猜“Gibernate”不如GORM一样好发音)。这意味着,Hibernate支持的任何数据库,Grails(借助GORM)也能够支持。这还意味着,所有你已经熟悉的Hibernate的技巧同样也能在新的旅程中发挥作用—HBM(Hibernate映射文件)EJB3POJO,你都可以拿来就用,无需改变。在本书中,我们将聚焦于Grails开发的新领域。要想更好地理解GORMdataSourcepooled=username="sa"password=}hibernatecache.use_query_cache=vider_class=}environments{{{//oneof'create','create-drop','update'dbCreate="create-drop"46|46|GRAILS2第一段(被标记为ataSoure)包含了所有环境(enironmets)都共享的基本数据库设置。在这个例子里,开发(deelopmnt)、测试(tet)和产品(proutin)都共享一个公共的数据库驱动、用户名和口令(我们等会儿会详细地谈到环境)。如果你的产品(prdutn)数据库要求的证书设置同开发(deelpnt)数据库不一样,你可以把这些值放到合适的代码段里。任何在环境(enirnet)段中的值都将覆盖这些“全局”设定。顾名思义,hibernate代码段是你可以调整Hibernate最后一段(被标记为environments)是一个会发生有趣行为的地点。首先,还记得那个每次在你重启Grails时删除数据的“特性”吗?这之所以会发生,都是因为dbCreate被设置成create-drop。这个名字已经相当好地说明了自己的意思——每次重启Grails,数据库表就要被创建;每次停止Grails,这些表都会被删除。注意,在测试(test)和产品(production)中,dbCreate都被设置成了update。在这个模式下,HibernateSQLALTERTABLE来使这些表与领域类保持同步。(3个选项——create——Grails时删除数据。你可能会忍不住想在开发(development)环境中切换到update模式。尽管这在技术上不会引起任何问题,但请记住,在开发过程中,数据库模式很可能处于极端不稳定的状态—尤其是在刚开始的阶段。我发现,让它保持在create-drop模式,使用}}testdataSourcedbCreate=url=}}{{dbCreate=url=}}47|要是你认为让GORM47|要是你认为让GORM管理数据库模式会让你的DBA浑身冒冷汗,大可不必担心。把这个值注释掉就可以告诉GORM不要做任何事情。这时,你就需要自己负责保证领域类和数Grails应用,这就是一个完美的解决方案(致Hibernate专家:dbCreate变量会直接映射到hibernate.hbm2ddl.auto值。)另一个设置——url——是JDBC连接字符串。注意,产品(production)模式使用的是基于文件的HSQLDB,而测试(test)和开发(development)模式则都使用的是基于内存的HSQLDB是一个非常不错的数据库,特别适合快速搭建可执行应用,但是在实际投入到产3件简单JDBCgrails-app/lib调整首先,我将向你演示如何在MySQL里创建一个新的数据库(至于其他数据库,请参考它们附带的手册。)MySQL,现在就该去做了。我会等你把它安装完。给开发(development)模式创建的数据库来度过,虽然你会觉得野心有点大,但是请尽上面倒数第二条命令可能值得花点功夫解释一下。你把racetrack_dev数据库中所有对象的所有权限(如创建表、删除表、更改表等)都赋予了一个名叫grails的用户。这个用户只能从本机(localhost)登录——在数据库和Grails不在一台机器上的时候,你可以把localhost改为一个IP地址或另一个主机名。最后,在这个例子里,该用户的密码是$mysql-uroot-Enterpassword:WelcometotheMySQLmonitor.Commandsendwith;ormysql>createdatabasemysql>grantallonracetrack_dev.*tograils@localhostidentifiedbymysql>48|GRAILS2为了验证MySQL48|GRAILS2为了验证MySQL终端上的所有设置都配置正确了,使用你新创建的凭证重新登录下一步就是把MySQL驱动器复制到grails-app/lib目录。你可从最后,把grails-app/conf/DataSource.groovy调整为你的新设置(注意:文件中的每个都应该是单独一行。)dataSourcepooled=username="grails"password=}hibernatecache.use_query_cache=truevider_class=}environments{{{//oneof'create','create-drop','update'dbCreate="create-drop"//NOTE:theJDBCconnectionstringshould allonthesame}}test{dbCreate=$mysql--user=grails-p--database=racetrack_devWelcometotheMySQLmonitor.Emptyset(0.0049|建了MySQL数据库(而且你应该这样做!),你就可以在合适的时机做合适的改动。现在到了关键时刻:我们真的把所有配置都设对了,可以让Grails来使用MySQL49|建了MySQL数据库(而且你应该这样做!),你就可以在合适的时机做合适的改动。现在到了关键时刻:我们真的把所有配置都设对了,可以让Grails来使用MySQL了吗?grailsrun-app,检查控制台上是否有错误输出。倘若一切正常,下一个要检查的地方就是MySQL。$mysql--user=grails-p--database=racetrack_devWelcometotheMySQLmonitor.mysql>show |Tables_in_racetrack_dev | | | 3rowsinset(0.01mysql>desc | | |Null|Key | |bigint(20)|NO|PRI| |bigint(20)|NO |date_created|datetime|NO | | |NO | |bigint(20)|NO|MUL| |bigint(20)|NO|MUL 6rowsinset(0.02mysql>desc}}productiondataSourcedbCreate=}}}50|GRAILS2看到了吗,grails-app/domain目录中的每个类都有相应的表。每个属性都有对应的列。驼峰字段名(如dateOfBirth)被转换成了包含下划线的小写名字(如date_of_birth)。GORM还给每个类添加了两个额外的字段——idversion。毫无疑问,id50|GRAILS2看到了吗,grails-app/domain目录中的每个类都有相应的表。每个属性都有对应的列。驼峰字段名(如dateOfBirth)被转换成了包含下划线的小写名字(如date_of_birth)。GORM还给每个类添加了两个额外的字段——idversion。毫无疑问,id是主键。正是该列让registration表中的外键可以发挥作用。版本(version)字段保留一个简单的整数,每次更新记录时该值都会增加。用Hibernate的行话来讲,这被称为乐观锁(optimisticlocking)。两个使用者如果试图同时编辑该记录,第一次更新会增加版本(version)字段的值。当第二个使用者想保存该记录时,GORM可以轻易地发现记录已经过时,从而警告这名使用者。 | | |Null|Key | | |NO|PRI| | |NO | |varchar(255)|NO | |decimal(5,2)|NO | |decimal(19,2)|NO |max_runners| |NO | |varchar(50)|NO |start_date| |NO | | |NO 9rowsinset(0.01mysql>desc | | |Null|Key | |bigint(20)|NO|PRI| |bigint(20)|NO | |varchar(255)|NO | |varchar(255)|NO |date_of_birth| |NO | |varchar(255)|NO | |varchar(255)|NO | |varchar(1)|NO | |varchar(255)|NO | |varchar(255)|NO | |varchar(255)|NO 11rowsinset(0.015151|在这一章里,你终于知道了Grails为什么在每次重启之间看起来会忘记你的数据。要想修正这一行为,只需要简单的把grails-app/conf/DataSource.groovy中的dbCreate的值从create-drop改为update。(如果是在使用HSQLDB,你还需要把开发环境由内存数据库改为文件数据库。完成这一任务的一种方法是,把产品的url复制到开发中。)你还学到了如何给Grails指定外部数据库。7调整。这将帮助我们更好地了解控制器里的闭包(Action)URL之间的关系。这也让我们可以了解闭包与其对应的Groovy服务器页面(7调整。这将帮助我们更好地了解控制器里的闭包(Action)URL之间的关系。这也让我们可以了解闭包与其对应的Groovy服务器页面(GSP)之间的关系。比较create-controller和generate-defscaffold=true界面。这条语句不仅给控制器提供了行为,而且也给视图产生了GSP。对于下一个要创建我们要创建一个新的User领域类(在接下来的安全一章里,我们会grailscreate-domain-classUser用到它)。输在正常情况下,这时我会让你输入grailscreate-controllerUser。还记得吧,create-*命令会创建一个类的雏形。但这次我们的做法不一样,请输入grailsgenerate-allUser。generate-与之前看到的控制器只有一行不同,你会看到100行左右的Groovy代码(为了简洁起classUserControllerstaticallowedMethods=[save:"POST",update:"POST", defindex={classUserStringloginStringrole=staticconstraints=password(blank:false,password:true)role(inList:["admin","user"])}toString(){log}53|GRAILS2理53|GRAILS2理解URL和控制URL的每个部分在Grails3-1:URLURL的第一部分是应用名或应用上下文的根。毫不奇怪,这就是我们在输入grailscreate-appracetrack时创建的那个目录的名字。可是,应用名和目录名并不需要一定匹配。打开RaceTrack应用根目录下的perties,你会看到你可以把设置成任何你想要的名字。这个新值将出现在URL的第一部分。redirect(action:"list",params:params) deflist={params.max=Math.min(params.max?('max'):10,100)}defcreate=defuserInstance=newUser()userIperties=params}defsave={//...defshow={//...defedit={//...}defupdate={//...}defdelete={//...}54|URL54|URLRegistrationControllerController在控制器名字之后,你会看到Action的名字,它是可选的。这个名字直接对应于控制器中的闭包。假设有一个闭包名为list,那么URL就是registration/list。倘若省略Action名,那么缺省的index闭包就会被调用。再仔细检查产生的UserController,会发现index闭包只是简单地完成到listAction的重定向。第二个参数——params——是一个HashMap,包含查询字符串的名字/值对。如果在URL中输入类似http://localhost:9090/racetrack/registration/list?max=3&sort=paid,在控制器中,你可以分别使用params.max和params.sort来访问查询字符串中的值。URL的最后一部分,也是可选的,该位置一般是主键出现的地方。当你请求edit/2、show/2delete/22的这条记录。由于处理记录ID实在是太常见了,为了方便起见,Grails自动把这个值赋给params.id。在Web浏览器中输入http://localhost:9090/racetrack/userAction,GrailsindexAction。indexAction依次把请求listActionredirect方法中用到的“名字:值”结构在整个控制器内都可以使用。其实,它就是一个简单“键/值”对的HashMap。{defindex=redirect(action:"list",params:}deflist=params.max=Math.min(params.max?('max'):10,100)[userInstanceList:User.list(params),userInstanceTotal:User.count()]}}55|GRAILS255|GRAILS2好了,我们现在已经从indexAction移动到了listAction。list的第一行代码会在查询字符串中没有出现max参数的情况下设置它。这个值用于分页。(一旦得到超过10个的Runner、Race或Registration,Grails就会创建指向另一页的导航链接。要想强制分页标准小于10,可以传入max参数,如runner/list?max=2。)listreturnGroovy方法的最后一行都是一条隐式的return语句。在这个例子里,listAction就是给GSP视图返回一个HashMap(等下会详细介绍它)HashMap中的第一项是用户(User)列表,大小受max参数的限制;第二项是用户但是,这些静态方法是从哪儿冒出来的?仔细翻翻eroy文件,我没有看到哪个地方定义了静态的lit()或unt()方法。它们都是由M在运行时动态添加到er类上的(这被那些酷小孩们称为是元编程(morig))eronter源代码,你会发现有大量的方法被添加到了er类上ae、delte、et那么,我们现在知道了listAction的最后一行是return语句。但还是有一个问题:我们怎么就从一个包含一组User的HashMap跑到了一个显示它们的HTML表格了呢?换句话说,这个return语句下一步会把我们带到哪里?listActionUserController中,而且由listAction返回,Grails知道去使用位于grails-app/views/user/list.gsp的视图模板。GSPGSP。这里的目的只是为了让我们可以了解整个请求grails-app/views/user/list.gspbody部分的<trclass="${(i%2)==0?'odd':三个R:Action闭包一般以3种方式结束,每种方式名称的头一个字母都是R。(我喜欢把这称为“控制器的三个R”)第一个R——redirect——把请求由一个Action传给另一个。第二个R——render——把任意文本传回给浏览器(回想一下前面那个简单的“HelloWorld”例子)。render命令XML、JSON、HTML等众多格式。最后一个R是return。在某些情况下,return是显式给出的。其他时候,如你在这个例子的list中所见,return是隐含的。56|看到userInstanceList了吗?这个名字跟list56|看到userInstanceList了吗?这个名字跟listAction么,GSP在g:paginateuserInstanceTotal了解控制器Action既然你已对一个完整的请求生命周期了然于胸,那么大可放心地自行浏览控制器Action的其余内容。不论哪种情况,都是从URL中抽取出Action,传给合适的闭包,最后传递给URLuser/show/2UserControllershowGORM会尝试从数据库里获得主键值为“2”(params.id中)的用户(User)。如果找不到,就在flash范围(类似request范围,存在时间稍长,在redirect之后仍然可以发挥作用)中放置一条消息。反之,若是找到了,则返回一个包含该用户(User)defshow=if(!userInstance){args:[message(code:'user.label',redirect(action:"list")}else[userInstance:}}<div<g:linkaction="show"57|GRAILS2这个HashMap57|GRAILS2这个HashMap的目的地是哪儿?还用问,当然是grails-app/views/user/show.gsp。整个show.gsp里,你可以随处看到userInstance的使用:如何,简单吧?showURLshowActionshow.gsp视图。“惯例优于配置”最展示不匹配Action虽然传统根深蒂固,但有时你得打破规矩。总有些时候你会想使用与Action名字不匹配例如,在创建新用户(User)的时候,表单被提交到了saveAction。检查一下action<g:formaction="save"method="post"<!--...--<tr<tdvalign="top"<g:messagecode="user.id.label"default="Id"<tdvalign="top"${fieldValue(bean:userInstance,field:<tr<tdvalign="top"<tdvalign="top"${fieldValue(bean:userInstance,field:Grails中的范围:Grails4种范围:request、flash、sessionapplication。每种范flash范围里的变量(Grails中的错误消息)可以在单次重定向之后仍application范围里的变量是“全局的”——它们的生命周期跟Grails运行的时间58|58|目录中并没有相应的假如用户(User)成功保存,saveActionshowAction用户(User)过程中遇到问题(很可能是验证问题),saveAction揭示了其内部的工作机制。这让我们得以知道控制器中的每个闭包(Action)URL的一部分。我们还学到了Action可以把值的HashMap返回给同名的GSP,重定向到另一个Action,或显示任意字符串或任意名字的GSP。defsave=defuserInstance=newUser(params)if(userInstance.save(flush:true)){args:[message(code:'user.label',default:'User'),userInstance.id])}"}else}}8GroovyGrails应用视图的细节。你会看到SiteMesh如何与你的端Web8GroovyGrails应用视图的细节。你会看到SiteMesh如何与你的端Web能,请访有任何问题。假如你需要提高你的客户理解indexActionindex.gsp页面?千真万确,grails-app/views目录,那儿就有一个index.gsp文件。在文本编辑器中打开它。<title>Welcometo<metaname="layout"content="m

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论