篇将结束Grails Web层的讨论,内容涉及:Filter、Ajax和内容协商。
首先来看看Filter。深入之前,请记住:Grails Filter != JEE Filter,本文中其它出现Filter的地方都可认为是针对Grails而言的。
Filter的创建很简单,它实际上就是grails-app/conf下以Filters结尾的Groovy类:
class ExampleFilters {
   def filters = {
       //每个filter定义
   }
}
对于每个filter,形式如下:
sampleFilter(controller:'*', action:'*') {//参数指定了应用范围
       ...
}
Grails的Filter同样有Filter链的概念,它本质上是由多个filter定义组成,定义顺序=执行顺序。
Filter应用的范围可以是针对Controller和Action,过滤规则的属性:
  • controller,controller匹配模式,缺省使用.代替
  • action,action匹配模式,缺省使用.代替
  • regex (true/false),使用正则表达式语法(不使用.代替)
  • find (true/false),是否允许部分匹配
  • invert (true/false),规则取反
例子:
  • 全部Controller和全部Action:sampleFilter(controller:'*', action:'*')
  • 只有BookController:sampleFilter(controller:'book', action:'*')
也可以是针对URI,使用Ant路径匹配语法:
  • 以/book开始的URI:sampleFilter(uri:'/book/**')
  • 全部URI:sampleFilter(uri:'/**')
Filter的类型如下,它们都是在Filter内部定义的:
  • before:action执行之前,返回false指示随后的filter和action都不执行。
  • after:action执行之后执行,第一个参数是view model。
  • afterView:view呈现之后执行。
例子如下:
class SecurityFilters {
   def filters = {
       loginCheck(controller:'*', action:'*') {
           before = {
              if(!session.user && !actionName.equals('login')) {
                  redirect(action:'login')
                  return false
               }
           }       
       }
   }
}
Filter中可以使用的属性有:request、response、session、servletContext、flash、params、grailsApplication、applicationContext、actionName(拦截的Action名字)、controllerName(拦截的Controller名字)。
Filter中可以使用的方法有:render和redirect。
Ajax在如今的Web开发中已十分普遍,Grails的Web层同样对它也有支持。对于Ajax的支持,Grails是通过Prototype来实现,但是通过安装其他插件,你也可以使用其他的js lib来替代。对于js库的引用是在<head>中加入以下语句完成的:
<g:javascript library="prototype" />
ajax处理的总的流程大致如下:
  • client:发起ajax请求
  • server:处理
  • client:根据结果响应
Grails中的根Ajax相关的tag使用套路大致相同,现以remotelink为例说明:
<div id="message"></div>
<div id="error"></div>
<g:remoteLink action="delete" id="1"
     update="[success:'message',failure:'error']">
              Delete Book
</g:remoteLink>
其中:
  • <div>:显示服务器处理ajax请求后的返回结果
  • update:成功和失败时,显示结果的<div>的id。update还可以只指定一个:update="message",其值是<div>的id。
Form的远程提交也是ajax的一种应用,在Grails中有两个标签来支持:
  • <g:formRemote>
    <g:formRemote url="[controller:'book',action:'delete']" 
                         update="[success:'message',failure:'error']">
           <input type="hidden" name="id" value="1" />
           <input type="submit" value="Delete Book!" />
        </g:formRemote >
  • <g:submitToRemote>
    <form action="delete">
           <input type="hidden" name="id" value="1" />
           <g:submitToRemote action="delete" 
                    update="[success:'message',failure:'error']"/>
        </form>
Ajax的事件处理与一般的js事件类似,主要事件包括:
  • onSuccess:成功
  • onFailure:失败
  • on_ERROR_CODE(如on404):特定错误码事件
  • onUninitialized:ajax引擎初始化失败
  • onLoading:加载响应时
  • onLoaded:加载响应完成
  • onComplete:远程函数调用完成,包括相应的内容更新
其中事件值是js函数,例子如下:
<g:remoteLink action="show" id="1" 
              update="success" 
              onLoading="showProgress()" 
              onComplete="hideProgress()">
        Show Book 1
</g:remoteLink>
如果想引用XmlHttpRequest对象,可通过隐式事件参数e。
<g:javascript>
   function fireMe(e) {
       alert("XmlHttpRequest = " + e)
   }
}
</g:javascript>
<g:remoteLink action="example" 
              update="success" 
              onSuccess="fireMe(e)">
       Ajax Link
</g:remoteLink>
从服务器端看,按返回结果类型,实现ajax的方式可分为:
  • 内容为中心:返回HTML
  • 数据为中心:返回XML或JSON
  • 脚本为中心:返回JS
内容为中心的例子:
  • 服务器:
    def showBook = {
            def b = Book.get(params.id) 
            render(template:"bookTemplate", model:[book:b])
        }
  • 客户端:
    <g:remoteLink action="showBook" id="${book.id}" 
                 update="book${book.id}">
              Update Book
        </g:remoteLink>
        <div id="book${book.id}">
                ¡-¡-
        </div>
数据为中心(以JSON为例):
  • 服务器:
    import grails.converters.*
        def showBook = {
            def b = Book.get(params.id)
            render b as JSON
        }
  • 客户端:
    <g:javascript>
        function updateBook(e) {
            var book = eval("("+e.responseText+")")
            $("book"+book.id+"_title").innerHTML = book.title
        }
        <g:javascript>
        <g:remoteLink action="test" update="foo" onSuccess="updateBook(e)">
            Update Book
        </g:remoteLink>
        <g:set var="bookId">book${book.id}</g:set>
        <div id="${bookId}">
            <div id="${bookId}_title">The Stand</div>
        </div>
以脚本为中心实际就是发送js脚本到客户端,然后客户端来执行。服务器代码例子:
def showBook = {
    def b = Book.get(params.id)
    response.contentType = "text/javascript"
    String title = b.title.encodeAsJavascript()
    render "$('book${b.id}_title')='${title}'"
}
Grails对内容协商的支持是通过3种方式实现的:
  • HTTP Accept header
  • request.format
  • URL扩展
使用内容协商是有前提的:在grails-app/conf/Config.groovy中使用grails.mime.types指定支持的mime type。
在服务器端通过withFormat来处理各种类型的请求:
import grails.converters.*
class BookController {
      def books
      def list = {
         this.books = Book.list()
         //确保withFormat是action的最后一个调用
         //action会根据withFormat结果决定后续动作
         withFormat {
                html bookList:books
                js { render "alert('hello')" } 
                xml { render books as XML }
         }
       }
}
客户端设置请求类型的方式如下:
  • Http Accept Header,Grails会自动放入到request的format中
  • 显式设置Request的format参数。方法有二:其一,/book/list?format=xml;其二,通过URL Mapping:
    "/book/list"(controller:"book", action:"list") {
         format = "xml"
        }
  • URL扩展:根据URL扩展名决定。这一特性是缺省的。要关闭,修改config.groovy中grails.mime.file.extensions。如:/book/list.xml对应的格式就是XML。
最后来看看对于内容协商的测试,总的代码流程如下(集成测试的例子):
void testJavascriptOutput() {
 def controller = new TestController()
 设置内容格式
 controller.testAction()
 assertEquals "alert('hello')", controller.response.contentAsString
}
通过修改以上“设置内容格式”,就可以完成不同格式的测试。设置方法有:
  • 法1:accept header。
    controller.request.addHeader "Accept", “text/javascript"
  • 法2:request.format。
    controller.params.format = 'js'