Groovy和Grails简介

来源:百度文库 编辑:神马文学网 时间:2024/04/28 14:06:22
时间:2006-12-26
作者:Harshad Oak
浏览次数:
本文关键字:groovy, grails, scripting language, Dev Toolbox, WebLogic Server, Harshad Oak, 脚本语言

摘要
Java Web应用程序框架是企业Java得以成功的重要原因之一。人们怀疑如果没有Apache Struts框架JavaEE是否能够如此成功。虽然底层编程语言很重要,但通常是框架使编程语言成为引人注目的中心的。如果您经常访问讨论论坛,就会注意到Ruby语言和Ruby On Rails框架之间也是这种情况。Ruby已经出现十多年了,然而只是在Ruby OnRails框架流行之后,开发人员才开始注意到Ruby语言。
诸如Ruby、PHP和Python之类的脚本语言最近几年越来越流行,因此,需要开发一个Java脚本备选语言和类似Rails的针对Java环境的框架。Groovy就是这个脚本语言,而Grails就是这个框架。
在本文中我将讨论Groovy的Web开发功能,然后继续讨论Grails框架。我将开发一个示例Grails Web应用程序,并讨论此框架的各种特性。
Groovy是什么?
Groovy是一种语言,其语法类似于Java,但比Java更简单。它通常被视为脚本/灵活/动态的语言,但是我不喜欢这类形容词,因为我认为它们只会令人困惑。如果说Java是一位明智的中年男子,那么Groovy就是他十几岁的儿子。Groovy具有父亲的许多特点,但是更为狂野且更为有趣。他们也可以很好地合作。
Groovy的规则比Java少得多。例如,要在Java中获得标准的"HelloWorld"输出,您需要编写一个类、一个具有合适参数的主方法,等等。但是在Groovy中,如果不想编写所有样板代码,您可以抛开类定义和主方法,仅编写一行代码即可打印出"Hello World"。
以下是打印Hello World的文件 Hello.groovy 的内容:
println "Hello World"
Java平台仅关心使字节码得到执行。同样,此平台不强迫您使用Java语言。只要提供了字节码,工作就会进行。Groovy代码会被编译为字节码,而对于Java平台来说,字节码是从Java代码还是Groovy代码生成的并没有任何区别。
以下是一个Groovy例子,它显示了Groovy对清单、映射和范围的内置支持,并证明了Groovy的简单性及其利用Java的强大功能的能力:
// Print Date
def mydate = new java.util.Date()
println mydate
//Iterate through a map
def numbersMAP = [‘1‘:‘ONE‘, ‘2‘:‘TWO‘]
for (entry in numbersMAP) {
println "${entry.key} = ${entry.value}"
}
//Introducing the range
def range = ‘a‘..‘d‘
//Lists
def numberlist = [1, 2, 3, 4, 5, 6, 7, 8]
println numberlist;
println "Maximum value: ${numberlist.max()}"
请注意以上代码直接使用java.util.Date ,对收集的内置支持减少了使用清单、映射和范围所需的代码。还有许多其他有趣的Groovy特性,例如闭包和简化的XML处理。您可以在groovy.codehaus.org上找到详细清单。
现在让我们来讨论如何将Groovy用于Web开发。
使用Groovy进行Web开发
大多数Java EE教程都从一个基本servlet例子开始。对于GroovyWeb开发来说,您将从groovlet(在groovy中servlet的对应概念)开始。如果您在servlet中摆脱了类和doXX()方法声明,那么剩下的内容就与groovlet很像了。以下是一个名为 Login.groovy的groovlet例子,您需要将它置于Web应用程序的最高级目录:
def username= request.getParameter("username")
def password= request.getParameter("password")
if (username == "java" && password == "developer") {
response.sendRedirect("home.jsp")
session = request.getSession(true);
session.setAttribute("name", username)
}
else {
println """

Login Invalid


Your IP has been logged > ${request.remoteHost}


"""
paramMap = request.getParameterMap()
println "

You Submitted:

"
for (entry in paramMap) {
println "${entry.key} = ${entry.value}
"
}
}
您可以仅创建一个简单的HTML表单,然后将此表单的行为属性发送到 action="Login.groovy"。然后将以下标签添加到web.xml:

Groovy
groovy.servlet.GroovyServlet


Groovy
*.groovy

现在只需将要求的Groovy jar 文件添加到WEB-INF/lib 目录,您的Groovy Web应用程序就准备好在任意Java EE应用服务器上运行了。
您应该已经注意到代码中没有分号,而且使用了隐式变量如request和response。其他隐式变量有context、application、session、out、sout和 html。
GSP是JSP在groovy中的对应概念。您无需使用println生成HTML;只需将Groovy代码嵌入HTML页面。本文中的例子将在提到Grails时使用GSP。
请注意,因为所有代码最终都要转换为字节码,所以groovlet和GSP能够与servlet和JSP轻松协作。因此您无需区分groovlet和GSP或者servlet和JSP。
现在让我们讨论前途无量的Grails框架。如果成功的话,Grails能够极大地改变开发Java Web应用程序的方式。Ruby on Rails对Ruby的影响,Grails也能够对Groovy实现。
Grails特性和架构
Grails试图使用Ruby On Rails的“规约编程”(coding byconvention)范例来降低对配置文件和其他样板代码的需求。使用“规约编程”,如果文件的名称本身就能说明此文件的用途,那么您就不需要在配置文件中再次声明这些内容了。此框架会查看文件名,并自己弄清文件用途。通过使用“规约编程”,Grails还将自动生成Web应用程序中需要的许多内容。通过使用Grails,您将能够在很短的时间内、以最小的复杂性使Web应用程序就绪。请看以下例子。
Grails基于开源技术,例如Spring、Hibernate和SiteMesh。如果您已经擅长这些技术,那么这是件好事;但是如果您由于某种原因不喜欢这些技术,或者您认为不仅需要学习Grails,还需要学习其他三种框架,那么这就不是件好事了。虽然这些技术能够帮助Grails执行得更好,但是学习四种框架对于大多数人来说是一个很高的门槛。Grails文档目前主要关注它与Spring、Hibernate和其他程序的集成,然而我认为它需要采用相反的方法,将Grails推行为一个简单快速的Web应用程序开发框架。开发人员无需担心或考虑底层发生了什么。
幸运的是,一旦您开始使用Grails,您将发现Grails隐藏了这些框架的大多数底层复杂性。如果您忘掉在底层运行的是Spring、Hibernate和其他程序,那么事情就会变得简单。
Grails应用程序的三个层是:
由视图和控制器组成的Web层
由域类和服务组成的业务逻辑层
由域类和数据源组成的持久层
大多数框架都有数十种特性,其中只有很少几种得到了广泛使用。对于Grails来说,这种关键特性是指“规则编程”(coding by convention)范例和构件的自动生成。
Grails的其他特性包括对Ajax、验证、单元测试和功能测试的内置支持。它使用免费的开源Canoo WebTest项目来实现Web应用程序的功能测试。Grails还提供与Quartz Scheduler的集成。
现在是时候安装Grails框架并且编写您的第一个应用程序了。
Grails安装
安装过程非常简单。以下是Grails下载页面:http://grails.org/Download。您可以从http://dist.codehaus.org/grails/grails-bin-0.2.1.zip下载version 0.2.1。请注意Grails源代码和文档作为单独的下载提供。下载zip文件之后,只需将其内容解压缩到一个目录即可,在我的案例中此目录是 C:\groovy\grails-0.2.1\。
创建一个名为GRAILS_HOME 的新环境变量,并将其值设为C:\groovy\grails-0.2.1\。接下来将GRAILS_HOME\bin 添加到PATH环境变量。这样安装就完成了。通过在命令提示符界面中运行grails 命令您可以检查安装是否成功。您应该获得此命令的使用信息。
既然您有了一个运行中的Grails安装,那么您已经为创建Grails Web应用程序做好了准备。
开发Grails应用程序:应用程序结构
多年来我一直计划开发一个可以帮助我管理衣服的应用程序——这个应用程序应该能够告诉我我最喜欢的T恤衫放在哪里、是否洗过、是否熨过,等等。总有一天我会靠销售这个应用程序挣上几百万,但是现在我将把它用作Grails例子。
第一步是创建一个Grails项目目录结构。在这一步我将在C:\groovy\grailsapps创建一个新目录,并在此级别打开一个命令提示符窗口。在此窗口中,执行命令grails create-app。要求您输入应用程序名称。输入ClothesMgt。Grails将显示它为您创建的全部目录和文件。图1显示了最后得到的命令结构。

图1:Grails项目目录结构
此命令将创建约800KB大小的文件和目录。这里的想法是此框架遵循已经建立的Web应用程序开发惯例,因此它创建的文件和目录在大多数Web应用程序中是有用的。虽然有些人可能不喜欢这种强制使用某种结构的想法,但是这种基于惯例的自动生成正是Grails的RAD特性的基础。
如果更仔细地看一下这些目录,您就会发现存在用于诸如控制器、视图、测试、配置文件和标签库之类东西的目录。您还会发现存在一些基本JavaScript和CSS文件。那么现在应用程序的基本结构已经有了。您只需做些填空,应用程序即可就绪。
请注意自动生成目录和文件的命令是可选的。您可以手动创建全部文件和目录。如果熟悉Apache Ant,那么您甚至可以打开GRAILS_HOME 目录中的\src\grails\build.xml 文件,来仔细查看每个Grails命令的用途。
数据库
在此例中我将使用一个运行于localhost的名为Clothes_Grails的MySQL数据库。Grails内置一个HSQL数据库,这对测试简单的应用程序或仅试用Grails非常有用。如果您使用HSQL DB,那么无需执行以下几步。我将使用MySQL来证明您能够非常轻松地使用HSQL之外的数据库。
从http://www.mysql.com/products/connector/j/下载MySQL驱动器,并将mysql-connector-java--stable-bin.jar文件放置在ClothesMgt\lib 目录中。接下来您需要编辑ClothesMgt\grails-app\conf\ApplicationDataSource.groovy文件。
现在此文件的内容应该类似以下内容:
class ApplicationDataSource {
boolean pooling = true
String dbCreate = "create-drop"
String url = "jdbc:mysql://localhost/Clothes_Grails"
String driverClassName = "com.mysql.jdbc.Driver"
String username = "grails"
String password = "groovy"
}
现在让我们看一下如何使用此数据库和对象关系映射。
域类
Grails的对象关系映射(GORM)功能在内部使用Hibernate3,但是您无需了解或更改任何Hibernate设置。Grails具有称为“域类”的东西,这些域类的对象被映射到数据库。您可以使用关系来链接域类,它们也提供用于CRUD(创建/读取/更新/删除)操作的功能非常强大的动态方法。
在此例中,我们将创建三个域类,其名称分别是Shirt、Trouser和Cabinet。要创建域类,只需运行命令 grailscreate-domain-class。请记住在您的项目目录(而不是它的上级目录)内运行此命令。这是一个常见错误,虽然我已经提醒了您,您还是会犯至少一次这样的错误。
您必须提供给create-domain-class命令的唯一输入是类的名称。运行此命令三次,将Shirt、Trouser和Cabinet作为三个域类的名称。Grails现在将在目录grails-app/domain/中创建这些域类。它们将仅具有两个属性id 和version。我将为这些类添加属性,以便使它们更能代表衬衫、裤子和衣橱。
清单1:Cabinet.groovy
class Cabinet {
Long id
Long version
String name
String location
def relatesToMany = [ shirts : Shirt, trousers : Trouser ]
Set shirts = new HashSet()
Set trousers = new HashSet()
String toString() { "${this.class.name} : $id" }
boolean equals(other) {
if(other?.is(this))return true
if(!(other instanceof Cabinet)) return false
if(!id || !other?.id || id!=other?.id) return false
return true
}
int hashCode() {
int hashCode = 0
hashCode = 29 * (hashCode + ( !id ? 0 : id ^ (id >>> 32)) )
}
}
清单2: Trouser.groovy
class Trouser {
Long id
Long version
String name
String color
Cabinet cabinet
def belongsTo = Cabinet
String toString() { "${this.class.name} : $id" }
boolean equals(other) {
if(other?.is(this))return true
if(!(other instanceof Trouser)) return false
if(!id || !other?.id || id!=other?.id) return false
return true
}
int hashCode() {
int hashCode = 0
hashCode = 29 * (hashCode + ( !id ? 0 : id ^ (id >>> 32) ) )
}
}
清单3: Shirt.groovy
class Shirt {
Long id
Long version
String name
String color
Cabinet cabinet
def belongsTo = Cabinet
String toString() { "${this.class.name} : $id" }
boolean equals(other) {
if(other?.is(this))return true
if(!(other instanceof Shirt)) return false
if(!id || !other?.id || id!=other?.id) return false
return true
}
int hashCode() {
int hashCode = 0
hashCode = 29 * (hashCode + ( !id ? 0 : id ^ (id >>> 32)))
}
}
我添加的仅有的几行声明了字段名称和颜色,然后声明了Cabinet、Shirt和Trouser之间的关系。每个Shirt和Trouser都属于Cabinet,而Cabinet具有shirt和trouser的集合。belongsTo属性在此案例中是可选的,因为在一对多关系中,Grails会将“一”这一方视为所有者。因此您就无需显式声明了。在这里我进行显式声明只是为了使这种关系更明显。
接下来我们将讨论Grails应用程序的控制器和视图部分。
控制器和视图
既然域类已经就绪,让我们使用generate-all命令自动生成基本CRUD Web应用程序。运行grails generate-all 命令三次,当被询问时提供域类名称。generate-all 命令的目的是生成每个域类的控制器和视图,但是由于bug-245,Grails 0.2.1不能生成控制器。您必须手动生成控制器,其方法是对每个域类使用generate-controller 命令。
现在您应该在grails-app\controllers 目录中看到三个控制器。这些控制器负责处理Web应用程序中针对特定域类的请求。因此ShirtController.groovy 将处理Web应用程序中与Shirt域类相关的CRUD请求,等等。现在控制器具有多个闭包,每个闭包映射到一个URI。闭包是Groovy语言很好的一个特性,然而要习惯它还是需要一些时间的。清单4显示了Shirtcontroller.groovy的一段摘录。
清单4:ShirtController.groovy 摘录
class ShirtController {
def index = { redirect(action:list,params:params) }
def list = {
[ shirtList: Shirt.list( params ) ]
}
def show = {
[ shirt : Shirt.get( params.id ) ]
}
def delete = {
def shirt = Shirt.get( params.id )
if(shirt) {
shirt.delete()
flash.message = "Shirt ${params.id} deleted."
redirect(action:list)
}
else {
flash.message = "Shirt not found with id ${params.id}"
redirect(action:list)
}
}
// ...
}
在此例中,ShirtController 中的list闭包将处理URI是/shirt/list的请求,等等。您可在控制器中使用您习惯在Java Web应用程序中使用的东西,例如请求、会话和servletContext。
请注意:闭包也将值作为显式返回语句返回,或者作为闭包体中的最后一个语句的值返回。不要因为Grails生成的代码中没有return 而困惑。
一旦控制器完成了对请求的处理,它必须委托给合适的视图。Grails使用惯例机制实现此操作。因此ShirtController中的list闭包将委托给视图 /grails-app/views/shirt/list.gsp 或/grails-app/views/shirt/list.jsp。尽管您在使用Grails,全部视图可以是JSP文件而不是GSP。我几乎没有编写任何代码,但是我已经准备好了一个Web应用程序。
让我们尝试部署和运行我们的应用程序。
在Java EE Server上部署和运行Grails
Grails具有一个内置Resin服务器,您可使用grails run-app 命令运行应用程序。此命令会将应用程序部署到Resin服务器并启动服务器。因此您现在可以在http://localhost:8080/ClothesMgt 访问此应用程序。您还可以同样轻松地将应用程序部署到任意JavaEE服务器。我尝试将它部署到Tomcat。要实现此操作,我所需要做的是运行grails war 命令,将生成的war文件复制到Tomcat中的webapps目录!
在此案例中生成的war文件的名称为 ClothesMgt.war。一旦部署到Tomcat,您就应该能够在http://localhost:8080/ClothesMgt/ 上访问它,并看到如图2所示的屏幕。

图2:Grails 应用程序
通过此应用程序,能够获得Shirt、Trouser和Cabinet的全部CRUD功能。可以显示衣橱的全部数据、向衣橱添加新衬衫和裤子、编辑它们的值和删除记录——实现这些操作都无需编写任何业务逻辑、视图或数据访问代码。仅在几分钟内您就在JavaEE服务器上部署好了一个合适的Web应用程序。很酷吧?!
让我们更进一步来定制Grails。
创建自定义控制器
我现在将把新功能和页面添加到Web应用程序,同时重用已经存在的域类。shirt/list 和 trouser/list 会分别显示衬衫和裤子的清单,现在让我们添加一个新的显示,来同时显示衬衫和裤子的清单。要创建一个新的显示,您需要一个新的控制器和视图。
使用 generate-controller 和 generate-views命令,可以轻松实现使用域类自动生成视图和控制器。然而,在此案例中我希望创建一个与域类不直接关联的控制器。因此我将使用grailscreate-controller命令。当被提示输入控制器名称时,声明Display。Grails将在grails-app/controllers/ 目录创建一个名为DisplayController.groovy 的控制器,在grails-tests目录创建一个测试套件。如清单5所示编辑控制器。
清单5:DisplayController.groovy
class DisplayController {
def index = {redirect(action:list,params:params)}
def list = {
params[‘max‘] = 10
return [ shirtList: Shirt.list( params ),
trouserList: Trouser.list( params )]
}
}
index 闭包将请求重定向到清单。在list 闭包中我将最大参数设为10,然后使用动态方法Shirt.list 和 Trouser.list。然后返回Groovy Map,它有两个清单——衬衫清单和裤子清单。
作为Java开发人员,当看到Shirt.list()时会自然认为是在Shirt域类中的list方法。然而,如果打开Shirt.groovy,会发现并没有此方法。对于Java开发人员来说,不了解Groovy的特性就使用Grails不仅是令人困惑的,而且是死胡同。动态方法是Grails的特殊特性,它是构建于Groovy语言的一个非常特殊的特性元对象协议 (MOP)之上的。如此证明可以使用动态方法查询域类。因此,在控制器中,您将注意到在域类上调用的方法似乎在域类中不存在。您可以在这里阅读关于使用动态方法查询的更多信息。可以在这里找到对Grails控制器和域类中可用的动态方法的参考资料。
既然控制器能够处理请求、获取清单并转发到视图,我需要创建相应视图。
创建自定义视图
当创建控制器时,Grails还在grails-app\views 目录创建了一个新的显示目录,并将以下映射添加到web.xml 文件中。

grails
/display/*

目前Grails有一个generate-views 命令,此命令能够生成基于域类的视图,然而没有能够自动生成视图的create-view 命令。请看图3中的例子。

图3:一个显示Trousers的默认视图
因为我希望创建一个独立于域类的视图,所以让我们手动创建视图文件。在目录grails-app\views\display\中,创建一个名为 list.gsp的文件,如清单6所示。
清单6:list.gsp



Display Shirt And Trouser List





Shirt List












IdCabinet ColorName
${it.id} ${it.cabinet.name}${it.color} ${it.name}

Trouser List













Id CabinetColor Name
${it.id} ${it.cabinet.name}${it.color} ${it.name}




与我之前使用的方式类似,您现在也可以使用run-app 命令运行应用程序,或者创建一个war文件并将其部署到Tomcat。您应该在http://localhost:8080/ClothesMgt/display/下看到新的视图,如图4所示。

图4:新创建的列出衬衫和裤子清单的视图
现在让我们快速讨论一下Grails服务。
服务
如果您想知道如何分开业务逻辑以及放置业务逻辑的位置,答案在Grails 服务中。服务以SomeNameService.groovy 格式命名,且被置于 /grails-app/services/目录。服务可利用依赖注入特性,您能够轻松地从控制器内部调用这些服务。
让我们来看一个使用服务的例子。首先,使用create-service命令创建新服务。运行此命令并命名服务Order。Grails将创建两个文件——grails-app/services/OrderService.groovy 和 grails-tests/OrderTests.groovy。
现在编辑OrderService.groovy,如清单7所示。当引入新的orderGoods() 方法时会自动生成serviceMethod() 。
清单7:OrderService.groovy
class OrderService {
boolean transactional = true
def serviceMethod() {
// TODO
}
def orderGoods() {
return "Order Placed - New shirts and trousers \
will be sent shortly."
}
}
现在编辑DisplayController,如清单8所示。引入使用OrderService的重排闭包。请注意服务将由Groovy注入。
清单8:DisplayController.groovy
class DisplayController {
OrderService orderService
def index = {redirect(action:list,params:params)}
def list = {
params[‘max‘] = 10
return [ shirtList: Shirt.list( params )
, trouserList: Trouser.list( params )]
}
def reorder = {
render(orderService.orderGoods())
}
}
现在当您访问URLhttp://localhost:8080/ClothesMgt/display/reorder时,重排闭包将调用OrderService,响应会被发回到浏览器。您能够以类似方式将全部业务逻辑移入服务,然后使用Grails的注入功能非常轻松地使用它们。
动态方法和属性
正如之前提到的,域类没有能够从数据库获取数据或更新/删除现有数据的任何方法,例如find()、 findAll() 或 save()。在控制器中您也没有编写诸如 redirect() 或 render()之类的方法。但是域类和控制器有它们的计划目的,且允许所有要求的操作。原因是Grails中动态方法和属性的存在。动态方法被动态添加到类,就好像功能是在程序中编译的一样。
这些是可用的方法和属性,无需编写。这些动态方法涵盖了大多数Web应用程序开发中会碰到的常见情况。对于域类来说,存在诸如find()、findAll()、list()、executeQuery()、save()和delete()之类的动态方法。控制器具有诸如session、request和response之类的动态属性,以及诸如chain()、render()和 redirect()之类的方法。要真正利用Grails的强大功能,您需要了解所有这些动态方法和属性的功能。
顺便介绍一下:自动重载和@Property
Grails的一个重要特性是能够在开发过程中进行了更改时自动重载文件。因此只需编辑和保存gsp文件,就会自动重载新文件。然而这里创建的类似OrderService 的事务服务不会被重载。您会在服务器控制台看到以下消息"[groovy]Cannot reload class [class OrderService] reloading of transactionalservice classes is not currently possible. Set class tonon-transactional first. "。
Grails的自动重载功能会为您节省许多时间,您就无需浪费时间来重启服务器了。我碰到过一些Grails不能自动重载的案例,例如将一个jsp文件重命名到gsp。然而,Grails的这项功能有望在未来版本中得到进一步改进。
在Groovy JSR 06 的之前版本中,您必须使用@Property来定义Groovy中的新属性。因此您会在线看到许多使用@Property的旧的Groovy例子。然而请注意,@Property已经从GroovyJSR 06中移除,在Grails 0.2和之后的版本中也不会再需要它。请参阅@Property 建议来获得更多细节。
结束语
在本文中,我介绍了Grails框架的基本特性,并使用Grails创建了一个应用程序。Groovy和Grails最大的好处是一切都运行在优秀的旧Java和Java EE上——因此您能够使用Groovy和Grails的RAD特性快速开发应用程序,然后将应用程序部署到可靠的JavaEE服务器上。考虑到关于Ruby和Rails的宣传噪音,显然需要一个Java备选方案。Groovy和Grails看起来非常适合这个角色。
下载
下载本文中的代码:
本文中的代码 (zip)。
最新版本的SVN快照。
参考资料
Grails主页
Groovy主页
Grails Wiki
Grails Mailing 清单
Groovy, Java‘s New Scripting Language (ONJava, 2004)
Getting to Know Groovy (Sun, 2005)
Groovy bridges the scripting and the enterprise Java worlds (IndicThreads, 2006)