篇将结束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 }
}
}
}
客户端设置请求类型的方式如下:
最后来看看对于内容协商的测试,总的代码流程如下(集成测试的例子):
void testJavascriptOutput() {
def controller = new TestController()
设置内容格式
controller.testAction()
assertEquals "alert('hello')", controller.response.contentAsString
}
通过修改以上“设置内容格式”,就可以完成不同格式的测试。设置方法有: