Grails 1.2参考文档速读(20):终章

不知不觉间已经到了本系列的最后,在这一节里,我们将看到如何在Grails中使用Web服务,了解其与Spring和Hibernate相关的配置,以及关于脚手架和部署有关的内容。

Web服务如今已经成为潮流,不论你是否愿意,只要你还在开发应用,你就得去适应它。Grails支持:

  • REST,针对Controller,通过URL Mapping,每个HTTP方法对应一个Action
  • SOAP,通过plugin完成
  • RSS/ATOM,通过Feed plugin

实现Restful的服务在Grails中简直就是小菜一碟,通过URL Mapping就可以将一个普通的Controller暴露为一个Restful服务:

static mappings = {
   "/product/$id?"(resource:"product")   
}

缺省Http方法和Action的对应关系:

  • GET:show
  • PUT:update
  • POST:save
  • DELETE:delete

如果你不满意,也可以自行调整:

"/product/$id"(controller:"product"){
    action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"]
}

但是该方法不会像前面一样自动进行JSON/XML的Marshalling,除非在映射中指明parseRequest参数:

"/product/$id"(controller:"product", parseRequest:true){
    action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"]
}

客户端调用Restful服务的方法也不复杂:

  • 使用Groovy HTTPBuilder:
        import groovyx.net.http.*
        import static groovyx.net.http.ContentType.JSON
        
        def http = new HTTPBuilder("http://localhost:8080/amazon")
        
        http.request(Method.GET, JSON) {
            url.path = '/book/list'
            response.success = {resp, json ->
                json.books.each { book ->
                    println book.title
                }
            }
         }
  • 从浏览器发起非GET/POST方法:
        <g:form controller="book" method="DELETE">

关于XML Marshalling:

  • 读:可以参见这里
  • 写,步骤1:传入xml
        <?xml version="1.0" encoding="ISO-8859-1"?>
        <product>
            <name>MacBook</name>
            <vendor id="12">
                <name>Apple</name>
            </vender>
        </product>
  • 写,步骤2:数据绑定
        def p = new Product(params['product'])
        p.save()

实现SOAP服务是通过XFire Plugin实现的,它可以把Service暴露为Web服务:

class BookService {  
    static expose=['xfire']
    Book[] getBooks(){
    Book.list() as Book[]
  }
}

如果想知道它的wsdl,可以访问:http://127.0.0.1:8080/your_grails_app/services/book?wsdl

同样,对于RSS/Atom的支持,Grails也是通过插件:Feed Plugin。它底层使用的是ROME库:

def feed = {
    render(feedType:"rss", feedVersion:"2.0") {
        title = "My test feed"
        link = "http://your.test.server/yourController/feed"
        Article.list().each() {
            entry(it.title) {
                link = "http://your.test.server/article/${it.id}"
                it.content // return the content
            }
        }
    }
}

Spring应用中都会使用到ApplicationContext,Grails是一个Spring应用,当然也不例外,其中的ApplicationContext构造过程如下:

  • 从web-app/WEB-INF/applicationContext.xml构造父ApplicationContext,它会构造出GrailsApplication和GrailsPluginManager。
  • Grails利用GrailsApplication分析出惯例,然后构造子ApplicationContext,它作为Web应用的根ApplicationContext。

Grails的配置很多时候都发生在运行时,每个插件会给ApplicationContext注册一些Bean,关于这请查阅相关文档。当然,如果你对其最终的ApplicationContext感兴趣,不妨试试Spy插件,本站对它已有介绍

Grails并没有堵塞你自行定义Spring配置文件的道路,如果你想补充进行额外配置:

  • 方法1:grails-app/conf/spring/resources.xml
  • 方法2:grails-app/conf/spring/resources.groovy。这除了享受到groovy语法,还可以结合“环境”。

在配置文件中可以引用Grails中的对象,如dataSource、sessionFactory。Grails还引入了Bean DSL,例子:

import grails.util.*
beans = {
	switch(GrailsUtil.environment) {
		case "production":
			myBean(my.company.MyBeanImpl) {
				bookService = ref("bookService")
			}
		break
		case "development":
			myBean(my.company.mock.MockImpl) {
				bookService = ref("bookService")
			}	
		break
	}	
}

这种DSL一般规则:

  • 方法名为bean名
  • 第一个参数为Bean的类名
  • 闭包定义bean相关的属性
  • 第一个参数和闭包之间是构造函数的参数
  • 通过bean名即可引用Bean

这部分功能,即Bean Builder,亦可单独使用,在参考文档中给出了在Spring MVC应用中使用它的例子,这里就不重点阐述了。

Bean DSL的常见例子:

  • 构造函数
        exampleBean(MyExampleBean, "firstArgument", 2) {
            someProperty = [1,2,3]
        }
  • 工厂方法
        exampleBean(MyExampleBean) { bean ->
            bean.factoryMethod = "getInstance"
            bean.singleton = false
            someProperty = [1,2,3]
        }
  • Factory Bean
        repositoryService(org.jbpm.api.RepositoryService){ bean->
            bean.factoryBean = "processEngine"  
            bean.factoryMethod = "getRepositoryService"  
        }
  • 动态创建
        "${beanName}Bean"(MyExampleBean) {
            someProperty = [1,2,3]
        }
  • 匿名(内部)Bean
        marge(Person.class) {
            name = "marge"
            husband =  { Person p ->
                name = "homer"
                age = 45
                props = [overweight:true, height:"1.8m"]
            }
            children = [bart, lisa]
        }
  • 抽象Bean和父Bean定义
        abstractBean(KnightOfTheRoundTable) { bean ->
            bean.'abstract' = true
            leader = "Lancelot"
        }
        quest(HolyGrailQuest)
        knights("Camelot") { bean ->
            bean.parent = abstractBean  <-注意
            quest = quest
        }

Spring的配置文件中可以使用XML的名字空间,在Bean DSL中也可以:

  • 声明:xmlns context:"http://www.springframework.org/schema/context"
  • 使用(component-scan是context名字空间中定义的标签):context.'component-scan'( 'base-package' :"my.company.domain" )

名字空间使用例子

xmlns aop:"http://www.springframework.org/schema/aop"
aop { 
   config("proxy-target-class":true) { 
       aspect( id:"sendBirthdayCard",ref:"birthdayCardSenderAspect" ) { 
          after method:"onBirthday", 
                pointcut: "execution(void ..Person.birthday()) and this(person)" 
       } 
   } 
}

此外,我们还可以在Spring配置文件中使用Grails Config文件中定义的变量,配置文件中定义的脚本变量都可作为grails-app/conf/spring/resources.xml中的占位符

  • Config
        database.driver="com.mysql.jdbc.Driver"
        database.dbname="mysql:mydb"
  • Resources.xml
    <bean id="dataSource" 
       class="org.springframework.jdbc.datasource
                        .DriverManagerDataSource">
       <property name="driverClassName">
           <value>${database.driver}</value>
       </property>
       <property name="url">
           <value>jdbc:${database.dbname}</value>
       </property>
     </bean>

如果你想覆盖缺省的配置,可以按下列格式:

[bean name].[property name] = [value]

Grails中对于Hibernate的处理与Spring的非常类似:

  • 配置文件:grails-app/conf/hibernate/hibernate.cfg.xml
  • 映射文件:grails-app/conf/hibernate

Grails中还考虑到了你的现有代码资产,重用现有Domain Class(Java类):

  • 步骤1:配置文件和映射文件复制到相应目录
  • 步骤2:复制Java代码 => src/java

Grails也支持使用注解创建Domain Class:

  • 在src/java中创建java类,在类中使用注解
  • 注册
    <!DOCTYPE hibernate-configuration SYSTEM
    "http://hibernate.sourceforge.net/
               hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
        <session-factory>
            <mapping package="com.books" />
            <mapping class="com.books.Book" />
        </session-factory>
    </hibernate-configuration>

如果你想改变配置文件的位置,在DataSource.groovy中:

hibernate {
	config.location = "file:/path/to/my/hibernate.cfg.xml"
}

配置文件也可有多个:config.location = ["file:/path/to/one/hibernate.cfg.xml","file:/path/to/two/hibernate.cfg.xml"]

Grails的脚手架提供的功能:

  • 需要的view
  • Controller中与CRUD对应的Action

激活脚手架很简单,设置Controller的scaffold属性:

  • Controller名字和Domain Class名字相同时:def scaffold= true
  • Controller名字和Domain Class名字不同时:def scaffold= domain class name

至于其他需要知道的知识:

  • 可以添加其它Action
  • 覆盖脚手架产生的Action就是定义与之名字相同的Action。脚手架产生的Action有list、show、edit、delete、create、save、update
  • Domain Class的约束会对View产生影响,属性在约束中出现的顺序,也是它们在View中出现的顺序。

脚手架相关的命令:

  • generate-controller DomainClass名
  • generate-views DomainClass名
  • generate-all DomainClass名

如:grails generate-all com.bookstore.Book,建议使用包名。

如果想自定义模板:

  • 先安装模板:grails install-templates
  • 再修改模板:src/templates

跟部署相关的命令有:

  • grails run-app,它有两个环境变量:disable.auto.recompile,关闭检查;recompile.frequency,设置检查的秒数,缺省为3
  • grails run-war,运行的是war,会关闭reload

grails war比较特殊,有必要单独说明:

  • 改变缺省war的目录:方法1:grails war path;方法2:修改grails-app/conf/BuildConfig.groovy中的grails.war.destFile = "foobar-prod.war"
  • 缺省包含的lib:grails必需的lib+工程lib+插件lib
  • 如果你想自行定义相应的lib,那么修改BuildConfig.groovy,相关的属性:grails.war.dependencies、grails.war.copyToWebApp、grails.war.resources

后记:终于完工了!自此,你应该对Grails有了一个大致的了解,还有更多的秘密等待着你的探索。再次重申,本系列只是一个快速概览,并不能代替参考文档,顶多相当于一个敲门砖!最后,预祝大家虎年愉快,生龙活虎!


By foxgem - Posted on 02 二月 2010

能否出一个电子书

这个系列能否出一个电子书?这样看起来比较方便 :)

兄台的话说到我心坎里了!

兄台的话说到我心坎里了!但,我考虑到出一个电子书最好还是能够严谨一些,这些帖子有部分即兴的成分在。所以,当时这个念头也就是在心中一转而已。加上目前正在翻译InfoQ的那本Grails的mini书的第二版,手头时间不是太多。打算在那本mini书交稿之后着手此事,估计到时候Grails 1.3会出来,届时就直接融合1.3的吧。

真的不错!

真的不错!辛苦了!

辛苦了,感谢foxgem善始善终完成了这一套速读。我一路看

辛苦了,感谢foxgem善始善终完成了这一套速读。我一路看下来收获不小。