Grails开发全解析:环境、配置与功能实现
1. 应用规模快速查看
在Grails应用开发中,若想快速了解新应用的规模,可使用
grails stats
命令。以下是执行该命令后的示例输出:
$ grails stats
+----------------------+-------+-------+
| Name | Files | LOC |
+----------------------+-------+-------+
| Controllers | 1 | 66 |
| Domain Classes | 1 | 8 |
| Integration Tests | 1 | 4 |
+----------------------+-------+-------+
| Totals | 3 | 78 |
+----------------------+-------+-------+
通过这个输出,我们能清晰看到控制器、领域类和集成测试文件的数量以及代码行数,对应用规模有一个直观的认识。
2. 理解Grails环境
Grails默认提供了三种标准环境:开发(development)、测试(test)和生产(production)。这些环境便于根据运行模式更改数据库连接、Log4j设置等。不同环境有不同的行为特点:
-
开发模式(dev)
:所有文件会自动重新加载,这意味着开发者在修改代码后无需重启服务器,但动态重新加载会在一定程度上影响性能。
-
生产模式(prod)
:更注重速度优化,而非灵活性。
2.1 环境配置文件
要查看每个环境的配置设置,可查看
grails-app/conf/DataSource.groovy
文件。该文件中,环境块外的设置是全局的,而像
development
这样的环境块内的设置可以选择性地覆盖全局设置。
虽然
grails-app/conf/Config.groovy
文件中默认没有环境块,但我们可以自行添加。以下是一个示例:
environments{
production{
println "I'm in production"
}
foo{
println "I'm in foo"
}
}
保存文件后,执行
grails prod run-app
命令,在控制台输出的早期会显示
I'm in production
。
2.2 自定义环境
默认的三种环境在命令行中可直接使用。若要设置自定义环境,则需使用
-D
标志自行设置
grails.env
属性。例如,若在
Config.groovy
中添加了上述代码,执行
grails -Dgrails.env=foo run-app
命令,控制台输出会显示
I'm in foo
。
3. 在不同端口运行Grails
Grails默认在8080端口运行。若要在其他端口运行,有以下两种方法:
3.1 临时指定端口
在命令行中使用
-D
标志显式指定端口,例如:
$ grails -Dserver.port=9090 run-app
这种方法适合临时测试,可在同一物理机上同时运行多个不同端口的服务器。
3.2 设置默认端口
若想始终在特定端口(如9090)运行Grails,可在
$GRAILS_HOME/scripts/Init.groovy
文件中设置默认端口:
serverPort = System.getProperty('server.port') ?
System.getProperty('server.port').toInteger() : 9090
需要注意的是,
Init.groovy
是一个GANT文件,GANT是Groovy实现的Ant,对于喜欢Ant约定但更倾向于动态语言表达的开发者来说是个不错的选择。
4. 生成WAR文件
虽然在嵌入式Jetty容器中运行Grails便于开发,但在生产环境中,很少有公司使用Jetty。不过,Grails可以生成行业标准的WAR文件,可部署到任何生产环境的应用服务器上。只需执行以下命令:
$ grails war
执行该命令后,会生成一个名为
bookstore-0.1.war
的文件,版本号和应用名称来自
application.properties
文件,可根据需要进行修改:
app.version=0.1
app.servlet.version=2.4
app.grails.version=1.0
app.name=bookstore
需要注意的是,WAR文件默认在生产模式下运行。
5. 更改数据库
嵌入式数据库HSQLDB便于快速启动应用,但大多数生产环境的Grails应用最终会依赖外部数据库。若数据库受Hibernate支持,Grails也同样支持,因为Grails对象/关系映射器(GORM)是Hibernate的Groovy封装。以下以将书店应用迁移到MySQL为例,介绍更改数据库的步骤:
5.1 设置数据库和用户
假设MySQL已安装并运行,首先以具有管理权限的用户登录:
$ mysql --user=root
mysql> create database bookstore_dev;
mysql> use bookstore_dev;
mysql> grant all on bookstore_dev.* to grails@localhost identified by 'server';
mysql> flush privileges;
mysql> quit
$ mysql --user=grails -p --database=bookstore_dev
上述命令创建了目标数据库
bookstore_dev
,并创建了一个用户
grails
,密码为
server
,该用户只能从本地登录。创建完成后,可手动测试登录以确保用户账户创建成功。
5.2 复制数据库驱动
将JDBC驱动JAR文件复制到
lib
目录:
$ cp ~/mysql-connector-java-3.1.13-bin.jar bookstore/lib
5.3 调整
DataSource.groovy
配置
在
grails-app/conf/DataSource.groovy
文件中进行以下配置:
dataSource {
pooled = false
driverClassName = "com.mysql.jdbc.Driver"
username = "grails"
password = "server"
}
hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.provider_class='org.hibernate.cache.EhCacheProvider'
}
// 环境特定设置
environments {
development {
dataSource {
// 可选值:'create', 'create-drop', 'update'
dbCreate = "update"
url = "jdbc:mysql://localhost:3306/bookstore_dev?autoreconnect=true"
}
}
test {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:mem:testDb"
}
}
production {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:file:prodDb;shutdown=true"
}
}
}
要让Grails指向新创建的MySQL数据库,需调整以下四个值:
| 参数 | 值 |
| -------------- | -------------------------- |
| driverClassName | com.mysql.jdbc.Driver |
| username | grails |
| password | server |
| url | jdbc:mysql://localhost:3306/bookstore_dev |
由于之前的配置中存在HSQLDB的遗留设置,在测试和生产环境中无法与MySQL配合使用,因此有两种解决方案:
- 将MySQL特定的
driverClassName
、
username
和
password
值移到
development
块中,并为测试和生产环境设置类似的值。
- 在其他环境块中将
url
更改为有效的MySQL连接URL。
5.4 调整
dbCreate
值
dbCreate
变量对应Hibernate中的
hibernate.hbm2ddl.auto
设置,它允许我们微调表生成行为。以下是不同
dbCreate
值的含义:
| 设置 | 启动时操作 | 关闭时操作 |
| ------------ | ---------------------- | -------------------- |
| create-drop | 创建表 | 删除表 |
| create | 创建或修改表 | 仅删除数据 |
| update | 创建或修改表 | 保留数据 |
默认情况下,开发环境的
dbCreate
设置为
create
,测试和生产环境设置为
update
。为避免数据丢失,建议将开发环境的
dbCreate
也设置为
update
。需要注意的是,
create
和
update
操作在修改表时较为保守,只会添加字段、延长字段长度,而不会删除字段或缩短字段长度,任何可能导致数据丢失的操作都需要开发者自行处理。若不想让Hibernate自动管理表结构,可在
DataSource.groovy
文件中注释掉
dbCreate
变量。
6. 更改主页
Grails应用的默认主页文件名为
index.gsp
,由于Jetty是符合标准的Servlet容器,也支持
index.jsp
。若同一目录下同时存在
index.jsp
和
index.gsp
,则
index.jsp
页面优先。
6.1 重定向到现有控制器
常见的做法是将主页重定向到现有的控制器。以下是一个示例,将主页重定向到
book
控制器的
list
视图:
// web-app/index.gsp
<% response.sendRedirect("book/list") %>
6.2 使用URL映射
更灵活的方法是在
grails-app/conf/UrlMappings.groovy
文件中为根URL(
/
)添加自定义映射:
// grails-app/conf/UrlMappings.groovy
class UrlMappings {
static mappings = {
"/" (controller:"book", action:"list")
}
}
UrlMappings.groovy
文件可让我们精细控制Grails应用中URL到控制器的映射,更多信息可查看在线文档。
7. 理解控制器和视图
Grails应用的控制器是连接视图和模型的桥梁。每个URL都对应控制器中的一个动作,在Grails URL中,控制器名称会去掉
Controller
后缀。通常情况下,控制器动作在
grails-app/views
目录中有同名的GSP文件与之对应。
理解Grails控制器主要涉及三个关键操作:重定向(redirect)、返回(return)和渲染(render)。
7.1 重定向(Redirect)
def index = { redirect(action:list,params:params) }
每个控制器都应该有一个
index
动作,它是控制器的默认动作。上述示例中,若没有其他指令,
bookstore/book
请求将重定向到
list
动作,并将查询字符串参数传递给该动作。重,也可以接受一个控制器参数,例如在保存书籍时,若需要用户登录并具有足够的权限,可将请求重定向到
Logon
控制器。
7.2 返回(Return)
def list = {
if(!params.max) params.max = 10
[ bookList: Book.list( params ) ]
}
def show = {
[ book : Book.get( params.id ) ]
}
在Groovy方法中,最后一行是隐式返回语句。Grails动作的最后一行返回一个值的Map给同名的GSP页面。例如,
list
动作若没有提供
max
参数,将从数据库中返回一个包含十本书的ArrayList;
show
动作根据
id
参数从数据库中获取书籍并传递给
show.gsp
视图。
7.3 渲染(Render)
def save = {
def book = new Book(params)
if(!book.hasErrors() && book.save()) {
flash.message = "Book ${book.id} created"
redirect(action:show,id:book.id)
}
else {
render(view:'create',model:[book:book])
}
}
render
方法是Grails控制器中最灵活的操作之一。在
save
动作中,若书籍保存成功且没有错误,将重定向到
show
动作;若保存失败,则渲染
create.gsp
视图。
render
方法不仅可以渲染GSP页面,还可以用于返回XML、Atom和RSS提要,甚至Excel电子表格。
8. 动态脚手架
在学习Grails时,我们可以使用
grails generate-all Book
命令为
Book
模型创建控制器和视图。但Grails真正的强大之处在于动态脚手架功能。
8.1 动态创建控制器和视图
以下是一个示例,展示如何使用动态脚手架为
Publisher
模型创建控制器和视图:
// grails-app/Controller/PublisherController.groovy
class PublisherController {
def scaffold = Publisher
}
// grails-app/domain/Publisher.groovy
class Publisher{
String name
String address
String city
String state
String zipcode
String toString(){
return name
}
}
通过在控制器中添加
def scaffold = Publisher
,Grails会在运行时在内存中创建控制器和视图。这在领域类还在不断变化的早期开发阶段非常有用,开发者无需频繁重建视图即可添加或删除属性。此外,我们还可以选择性地覆盖控制器动作和视图。例如,在
PublisherController
中添加自定义的
save
动作,其他动作仍会正常工作;若想为
list.gsp
视图设置特殊的外观和感觉,可在视图目录中添加该文件。
8.2 更改字段顺序
默认情况下,Grails会对动态脚手架生成的视图中的字段进行字母排序。若想指定字段顺序,可在领域类中创建一个静态
constraints
块:
class Publisher{
static constraints = {
name()
address()
city()
state()
zipcode()
}
String name
String address
String city
String state
String zipcode
String toString(){
return name
}
}
虽然这种方式可能不符合DRY原则,但
constraints
块的用途不仅限于字段排序,在数据验证等方面也有应用。
综上所述,Grails为开发者提供了丰富的功能和灵活的配置选项,通过合理运用这些特性,我们可以高效地开发出高质量的Web应用。无论是环境管理、数据库配置、页面定制还是控制器和视图的处理,Grails都有相应的解决方案,帮助开发者更轻松地完成开发任务。
9. 数据验证
在Grails应用中,数据验证是确保数据完整性和正确性的重要环节。我们可以在领域类中使用
constraints
块来定义验证规则。以下是一个示例:
class Book {
String title
String author
Integer pages
static constraints = {
title blank: false, maxSize: 100
author blank: false, maxSize: 50
pages nullable: true, min: 1
}
}
在上述代码中,我们为
Book
类的
title
、
author
和
pages
属性定义了验证规则:
-
title
:不能为空,最大长度为100。
-
author
:不能为空,最大长度为50。
-
pages
:可以为空,最小值为1。
当我们创建或更新
Book
对象时,Grails会自动根据这些规则进行验证。如果验证失败,对象的
hasErrors()
方法将返回
true
,我们可以通过
errors
属性获取详细的错误信息。以下是一个使用示例:
def save = {
def book = new Book(params)
if (book.validate()) {
book.save()
flash.message = "Book saved successfully"
redirect(action: 'show', id: book.id)
} else {
render(view: 'create', model: [book: book])
}
}
在
save
动作中,我们首先创建一个新的
Book
对象,然后调用
validate()
方法进行验证。如果验证通过,将对象保存到数据库并跳转到显示页面;如果验证失败,将渲染
create
视图并显示错误信息。
10. 拦截器
拦截器是Grails中一种强大的机制,用于在控制器动作执行前后进行额外的处理。我们可以创建一个拦截器类,并在其中定义拦截规则。以下是一个示例:
class BookInterceptor {
boolean before() { true }
boolean after() { true }
void afterView() {}
BookInterceptor() {
match(controller: 'book')
}
}
在上述代码中,我们创建了一个名为
BookInterceptor
的拦截器类。
before()
方法在控制器动作执行前调用,
after()
方法在控制器动作执行后调用,
afterView()
方法在视图渲染后调用。
match()
方法用于指定拦截的控制器。
我们还可以在拦截器中进行更复杂的处理,例如身份验证、日志记录等。以下是一个添加身份验证的示例:
class AuthInterceptor {
boolean before() {
if (!session.user) {
flash.message = "Please log in"
redirect(controller: 'login', action: 'index')
return false
}
return true
}
boolean after() { true }
void afterView() {}
AuthInterceptor() {
matchAll()
}
}
在
AuthInterceptor
中,我们在
before()
方法中检查用户是否已登录。如果未登录,将重定向到登录页面并返回
false
,阻止控制器动作的执行。
11. 过滤器
过滤器与拦截器类似,也是用于在请求处理的不同阶段进行额外的处理。不同的是,过滤器是基于Servlet规范的,它可以在请求到达控制器之前或响应返回给客户端之前进行处理。我们可以在
grails-app/conf/UrlMappings.groovy
文件中定义过滤器。以下是一个示例:
class UrlMappings {
static mappings = {
"/**"(filters: 'myFilter')
}
}
在上述代码中,我们使用
filters
属性指定了一个名为
myFilter
的过滤器,该过滤器将应用于所有请求。
我们还需要创建一个过滤器类来实现具体的过滤逻辑。以下是一个简单的日志过滤器示例:
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.FilterConfig
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
class LoggingFilter implements Filter {
void init(FilterConfig filterConfig) {}
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
println "Request received: ${request.getRequestURI()}"
chain.doFilter(request, response)
println "Response sent"
}
void destroy() {}
}
在
LoggingFilter
中,我们实现了
Filter
接口,并在
doFilter()
方法中记录请求和响应的信息。
12. 缓存
缓存是提高应用性能的重要手段之一。Grails支持多种缓存机制,包括内存缓存、分布式缓存等。我们可以在
grails-app/conf/DataSource.groovy
文件中配置缓存。以下是一个使用EhCache的示例:
hibernate {
cache.use_second_level_cache = true
cache.use_query_cache = true
cache.provider_class = 'org.hibernate.cache.EhCacheProvider'
}
在上述代码中,我们启用了二级缓存和查询缓存,并指定了缓存提供者为
EhCacheProvider
。
我们还可以在控制器动作中使用缓存注解来缓存方法的返回值。以下是一个示例:
import grails.plugin.cache.Cacheable
class BookController {
@Cacheable('bookList')
def list = {
if (!params.max) params.max = 10
[bookList: Book.list(params)]
}
}
在
list
动作上使用
@Cacheable
注解,指定缓存名称为
bookList
。当该动作被调用时,Grails会首先检查缓存中是否存在结果,如果存在则直接返回缓存中的结果,否则执行方法并将结果存入缓存。
13. 国际化
在多语言环境下,我们需要对应用进行国际化处理。Grails提供了丰富的支持来实现这一功能。我们可以在
grails-app/i18n
目录下创建不同语言的属性文件,例如
messages.properties
(默认语言)、
messages_en.properties
(英语)、
messages_zh.properties
(中文)等。以下是一个示例:
# messages.properties
book.title=Title
book.author=Author
book.pages=Pages
# messages_zh.properties
book.title=标题
book.author=作者
book.pages=页数
在视图中,我们可以使用
g:message
标签来获取国际化的文本。以下是一个示例:
<g:message code="book.title" />
在控制器中,我们可以通过
Locale
对象来设置当前语言。以下是一个示例:
def changeLocale = {
session.locale = new Locale(params.lang)
redirect(action: 'index')
}
在上述代码中,我们通过
session.locale
设置当前语言,并根据用户选择的语言重定向到首页。
14. 插件
Grails拥有丰富的插件生态系统,这些插件可以帮助我们快速实现各种功能。我们可以在
BuildConfig.groovy
文件中添加插件依赖。以下是一个添加
mail
插件的示例:
grails.plugin.location.'mail' = 'https://grails.org/plugins/grails-mail'
dependencies {
// other dependencies
}
plugins {
runtime ":mail:1.0.1"
}
在上述代码中,我们指定了
mail
插件的下载地址,并在
plugins
块中添加了插件依赖。
安装插件后,我们可以在应用中使用插件提供的功能。以
mail
插件为例,我们可以在控制器中发送邮件:
import grails.plugin.mail.MailService
class ContactController {
MailService mailService
def sendMail = {
mailService.sendMail {
to "recipient@example.com"
from "sender@example.com"
subject "Test Email"
body "This is a test email"
}
flash.message = "Email sent successfully"
redirect(action: 'index')
}
}
在上述代码中,我们注入了
MailService
,并在
sendMail
动作中使用它来发送邮件。
15. 总结
通过以上内容,我们全面了解了Grails开发中的各个方面,包括环境配置、数据库操作、控制器和视图处理、动态脚手架、数据验证、拦截器、过滤器、缓存、国际化和插件等。Grails提供了丰富的功能和灵活的配置选项,使得我们可以高效地开发出高质量的Web应用。
在实际开发中,我们可以根据项目的需求选择合适的技术和工具,合理运用Grails的各种特性,提高开发效率和应用性能。同时,我们还可以通过不断学习和实践,深入掌握Grails的高级特性,为项目的成功实施提供有力的支持。
以下是Grails开发的主要流程总结:
1. 环境配置:设置Grails环境、端口、数据库等。
2. 领域类定义:创建领域类并定义属性和验证规则。
3. 控制器和视图开发:实现控制器动作和对应的视图。
4. 动态脚手架使用:快速生成控制器和视图。
5. 拦截器和过滤器应用:进行请求处理和额外的处理。
6. 缓存和国际化处理:提高应用性能和支持多语言。
7. 插件使用:利用插件扩展应用功能。
mermaid flowchart LR
A[环境配置] –> B[领域类定义]
B –> C[控制器和视图开发]
C –> D[动态脚手架使用]
D –> E[拦截器和过滤器应用]
E –> F[缓存和国际化处理]
F –> G[插件使用]
通过遵循以上流程,我们可以有条不紊地进行Grails项目的开发,确保项目的顺利进行。
超级会员免费看
291

被折叠的 条评论
为什么被折叠?



