从本篇起,我们将开始进入Grails的Web层,首先让我们从Controller说起。
Grails中Controller的特点:
  • 线程安全:每次请求创建新实例
  • Controller – Action两级
  • 缺省URL Mapping:/controller/action
  • 文件名以Controller结尾
  • 文件位置:grails-app/controllers
  • 创建命令:grails create-controller
  • 在Controller文件内,Action是闭包
Controller的例子:
  1. class BookController {
  2. def list = {
  3. ...
  4. }
  5. }
做过Web的开发的都知道,缺省情况下是显示应用的 index.jsp或者类似的东西。在Grails中也有类似的概念:如果url中只指出了Controller但没说明action,那么缺省将定位到 controller的缺省action。缺省Action的规则如下:
  • 如果Controller只有一个action,那么它就是缺省action
  • 使用defaultAction属性指定action名字
  • 名字为index的action
Web 应用中常见的servletContext、session、request、params等范围在Grails中依然存在,同时Grails还增加了 flash范围,表示当前request和下个request,常用于redirect。对于这些范围的访问:scope['attr']或 scope.attr,如params["findBy"]。
Grails Web层的基础是Spring MVC,它当然也无法脱离MVC架构。对于Model,在Grails中就是Map。返回Model的方式有:
  • 返回Map:[ book : Book.get( params.id ) ]
  • 不明确指明返回一个Model,那么Model就由Controller的属性组成。注意,正是因为Grails保证了Controller对于每一个请求都会新创建一个实例,我们才能利用这种实例变量。
  • 返回Spring的ModelAndView:return new ModelAndView("/book/list", [ bookList : favoriteBooks ])
View承担了显示用户界面的任务,在Grails中的规则如下:
  • 没有指定View,那么就查找(jsp文件优先)grails-app/views/controller/action.gsp
  • 指 定View:render(view:“view名”, model:模型)。如:render(view:“display”, model:map),查找grails-app/views/当前controller/display.gsp;render(view:“ /shared/display”, model:map),查找grails-app/views/shared/display.gsp。
其他的render例子:
  • 文本:render "Hello World!"
  • view:render(view:'show')
  • Template:render(template:'book_template', collection:Book.list())
  • XML:render(text:“some xml", contentType:"text/xml",encoding:"UTF-8")
  • HTML片段,由于Grails中Tag可以象方法一样调用,在使用这种方式时,要注意避免跟这些Tag产生名字冲突,导致产生结果无意间调用了Tag。在使用时,应使用全限定名。如:builder.form
    1. render {
    2. builder.form{
    3. for(b in books) {
    4. div(id:b.id, b.title)
    5. }
    6. }
    7. }
重定向一个请求使用redirect:
  • redirect(action:'login')
  • redirect(controller:'home',action:'index')
  • redirect(uri:"/login.html")
  • redirect(action:myaction, params:[p:"v"])
  • redirect(action:"next", params:params)
  • redirect(controller: "test", action: "show", fragment: "profile"),对应/myapp/test/show#profile
Chaining的特点:
  • 在Action转移时,Model被保存
  • 后续Action可以访问前一个Action的Model
使用:
  • chain(action:"a", model:[one:1],params:[p:"v"])
  • 访问前一个Action传来的Model,使用chainModel。如:chainModel.one
例子如下,下例的最终结果是[one:1, two:2, three:3]:
  1. class ExampleChainController {
  2. def first = {
  3. chain(action:second,model:[one:1])
  4. }
  5. def second = {
  6. chain(action:third,model:[two:2])
  7. }
  8. def third = {
  9. [three:3]
  10. }
  11. }
谈到拦截器,这里需要注意,Grails的拦截器和Spring的拦截器不是一个概念:它只拦截Action,如果要拦截Controller用Filter。拦截器有两种:before和after,定义在Controller中。
  1. def beforeInterceptor = { //before返回false,后续action不执行
  2. println "Tracing action ${actionUri}"
  3. }
  4. //入参还可是ModelAndView
  5. def afterInterceptor = { model ->
  6. println "Tracing action ${actionUri}"
  7. }
除了直接指定闭包,还可以使用表达式来指定拦截哪些Action。表示式的形式:[实现拦截器的方法, 拦截器条件]。
  1. //auth实现拦截器,except指明拦截器应用的条件
  2. def beforeInterceptor = [action:this.&auth,except:'login']
  3. def auth() {
  4. ...
  5. }
  6. def login = {
  7. // display login page
  8. }
条件类型:except,only,使用如下:
  • [action:this.&auth,except:'login']
  • [action:this.&auth,except:['login','register']]
  • [action:this.&auth,only:['secure']]
对于Grails中的数据绑定,它是以Spring的数据绑定为基础,将请求参数与对象属性绑定到一起:
  • 绑定新对象:new Book(params)
  • 绑定已有对象:
    1. def b = Book.get(params.id)
    2. b.properties = params
Grails的绑定会自动对关联进行处理。
绑定1端关联:one-to-one和many-to-one,那么url采用形如/book/save?author.id=20,grails自动检测.id后缀,在绑定(如: new Book(params) )时,自动按id加载Author对象。
绑定多端:one-many和many-to-many,有多种方法:
  • 方 法1:<g:select name="books" from="${Book.list()}" size="5" multiple="yes" optionKey="id" value="${author?.books}" />,Grails会自动根据select的值来绑定。
  • 方法2:如果要更新关联的某些属性,那么就采用:
    1. <g:textField name="books[0].title" value="..." />
    2. <g:textField name="books[1].title" value="..." />
    Grails 会自动绑定,但是要保证更新顺序和显示顺序的一致性,对于List和Map不存在问题,对于Set要小心。如果显示的textField比实际关联的个数 多,且数组编号连续,如最后加一个“books[2].title”,Grails会自动给关联新增一个实例。如果显示的textField比实际关联的 个数多,且数组编号不连续,如最后加一个“books[5].title”,Grails会自动补齐中间的空缺。本例中会再添加4个:2、3、4、5。
绑定多个domain class同样也是在url上做文章,如/book/save?book.title=Title1&author.name=Author1。这时就使用params[‘前缀名’]分别取出数据:
  1. def b = new Book(params['book'])
  2. def a = new Author(params['author'])
对于数据绑定发生的错误,则是通过hasErrors来判断的。显示错误消息已经考虑的i18n,对应的消息键值以typeMismatch为前缀:
  • 按类名(通用): typeMismatch.java.net.URL
  • 按属性(特殊): typeMismatch.Book.publisherURL
上述各例都是针对Domain Class,但绑定同样也可针对属性单独进行:
  1. def p = Person.get(1)
  2. p.properties['firstName','lastName'] = params
绑定还可以发生在其他类型的对象上,使用bindData(sc, params):
  • params也可使用Map代替
  • 排除某属性绑定:bindData(sc, params, [exclude:'prop'])
  • 只包含指定属性:bindData(sc, params, [include:'prop'])
对于不想直接暴露Domain Class的场合,可以使用Command Object来代替。
对于XML和JSON响应,Grails还有一种简单的方法(JSON的话把XML换成JSON即可):render Book.list() as XML。或者你可以使用Builder:
  1. def books = Book.list()
  2. //json则为text/json
  3. render(contentType:"text/xml") {
  4. books {
  5. for(b in results) {
  6. book(title:b.title)
  7. }
  8. }
  9. }
Grails 1.2提供了新的JSONBuilder,缺省它会使用老的,如想使用新的,在config.groovy中:grails.json.legacy.builder=false。整个用法依旧如上:
  1. render(contentType:"text/json") {
  2. ...
  3. }
新JSONBuilder的语法:
  • 输出简单对象({"hello":"world"}):hello = "world"
  • 输出数组({"categories": ["a","b","c"]}):categories = ['a', 'b', 'c']
  • 输出对象数组({"categories":[ {"a":"A"} , {"b":"B"}] }):categories = [ { a = "A" }, { b = "B" } ]
  • 只输出数组([1,2,3]):
    1. render(contentType:"text/json") {
    2. element 1
    3. element 2
    4. element 3
    5. }
  • 输出复杂对象({"categories":["a","b","c"],"title":"Hello JSON","information":{"pages":10}}):
    1. render(contentType:"text/json") {
    2. categories = ['a', 'b', 'c']
    3. title ="Hello JSON"
    4. information = {
    5. pages = 10
    6. }
    7. }
  • 动态输出复杂对象:
    1. def results = Book.list()
    2. render(contentType:"text/json") {
    3. books = array {
    4. for(b in results) {
    5. book title:b.title
    6. }
    7. }
    8. }
除了在render中使用,你还可以直接调用它:
  1. def builder = new JSONBuilder()
  2. def result = builder.build {
  3. categories = ['a', 'b', 'c']
  4. title ="Hello JSON"
  5. information = {
  6. pages = 10
  7. }
  8. }
  9. println result.toString()
  10. def sw = new StringWriter()
  11. result.render sw
再来看看最常见的文件上载,前台页面如下:
  1. <g:form action="upload" method="post"
  2. enctype="multipart/form-data">
  3. <input type="file" name="myFile" />
  4. <input type="submit" />
  5. </g:form>
后台的处理有两种选择:
  • 直接使用MultipartFile

    1. def f = request.getFile('myFile')
    2. if(!f.empty) {
    3. f.transferTo( new File('/some/local/dir/myfile.txt') )
    4. }
  • 数据绑定
    1. class Image {
    2. byte[] myFile
    3. }
    4. def img = new Image(params)
如果想知道上传的文件名,使用MultipartFile.originalFilename。
Grails的Command Object跟Spring MVC中的没什么区别,是一个View Object。特点如下:
  • 使用上相当于简版Domain Class(不要误解,Command Object和Domain Class是两回事)。
  • 它在Controller目录中定义,一般包含在使用它的Controller文件中,但也可单独抽出。
  • 它还可以使用约束,但不能使用那些要查找数据库的约束,如unique。
  • 它也支持依赖注入。
使用例子:
  1. class LoginController {
  2. def login = { LoginCommand cmd ->
  3. if(cmd.hasErrors()) {
  4. redirect(action:'loginForm')
  5. }
  6. else {
  7. // do something else
  8. }
  9. }
  10. }
  11. class LoginCommand {
  12. String username
  13. String password
  14. static constraints = {
  15. username(blank:false, minSize:6)
  16. password(blank:false, minSize:6)
  17. }
  18. }
重复提交对于Web开发是个老问题了,Grails对此也提供了应对措施:
  • 前台页面: <g:form useToken="true" ...>
  • Controller:
    1. withForm {
    2. // good request
    3. }.invalidToken {
    4. // bad request
    5. }
  • 如果没有invalidToken,则可通过flash.invalidToken访问
  • 注意:withForm需要用到 session,因此,在集群环境下,要设置“session affinity”。这同样也适用于任何使用session的程序
params 提供了类型转换方法,可用于简单场合:params.int('total')。当某参数对应有多个值时,如?name=a&name=b,对其 处理需小心。因为params.name缺省返回字符串,而字符串在Groovy中也可以在循环中使用,这样,导致处理逻辑错误。为了确保总是获得 List,需使用params的list方法:params.list('name')。

Grails 1.2参考文档速读(10):Controller的更多相关文章

  1. [转载]正则表达式参考文档 - Regular Expression Syntax Reference.

    正则表达式参考文档 - Regular Expression Syntax Reference. [原创文章,转载请保留或注明出处:http://www.regexlab.com/zh/regref. ...

  2. Spring Boot 2.2.2.RELEASE 版本中文参考文档【3.2 - 3.10】

    Spring Boot 2.2.2.RELEASE版本中文文档持续更新中~如有需要获取参考文档文件,关注公众号JavaSo,回复“参考文档”即可. 3.2 结构化代码 Spring Boot不需要任何 ...

  3. 数据库 PSU,SPU(CPU),Bundle Patches 和 Patchsets 补丁号码快速参考 (文档 ID 1922396.1)

    数据库 PSU,SPU(CPU),Bundle Patches 和 Patchsets 补丁号码快速参考 (文档 ID 1922396.1)

  4. Mongoose学习参考文档——基础篇

    Mongoose学习参考文档 前言:本学习参考文档仅供参考,如有问题,师请雅正 一.快速通道 1.1 名词解释 Schema : 一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力 Model ...

  5. css参考文档; 官方英文说明!! 1 margin padding 百分比参照物 2 margin值为auto时的说明 3 div在div里垂直居中方法 4 dispaly:flex说明

    css参考文档        http://css.doyoe.com/ 两篇很好的文章:(下面的css官方英文说明链接 有时间可以研究下 http://www.w3.org/TR/css3-box/ ...

  6. oracle数据库 PSU,SPU(CPU),Bundle Patches 和 Patchsets 补丁号码快速参考 (文档 ID 1922396.1)

    数据库 PSU,SPU(CPU),Bundle Patches 和 Patchsets 补丁号码快速参考 (文档 ID 1922396.1) 文档内容   用途   详细信息   Patchsets ...

  7. html5 兼容参考文档 与 浏览器hack兼容参考文档

    移动端兼容参考文档 http://mobilehtml5.org/ 浏览器hack http://browserhacks.com/ 附上部分截图

  8. phpmyadmin-您可能正在上传很大的文件,请参考文档来寻找解决方法

    phpmyadmin-您可能正在上传很大的文件,请参考文档来寻找解决方法   实这个很简单的只要更改php.ini里三个配置即可.(见下面加粗部分,改成你自己的需求即可) ; Maximum allo ...

  9. CsvHelper文档-2读

    CsvHelper文档-2读 这个库默认不需要做任何设置就可以很容易的使用它.如果你的类属性名称直接匹配csv的标题名称,那么可以按照下面的实例来用: (以下所有的代码都需要引用using csvhe ...

随机推荐

  1. CentOS学习笔记--文件权限概念

    Linux 文件权限概念 当你的屏幕出现了『Permission deny』的时候,不要担心,『肯定是权限设定错误』啦!(以下节选自 鸟哥的 Linux 私房菜 第六章.Linux 的文件权限与目录配 ...

  2. Winform开发常用控件之Checkbox和CheckedListBox

    Winform的开发基本都是基于控件事件的,也就是事件驱动型的. 多选框的放置和值的获取有很多种,这里介绍几个简单常用的方法 1.直接放置Checkbox,并获取Checkbox的值 上图 做法也非常 ...

  3. ASP.NET中实现页面间的参数传递

    ASP.NET中实现页面间的参数传递   编写人:CC阿爸 2013-10-27 l  近来在做泛微OA与公司自行开发的系统集成登录的问题.在研究泛微页面间传递参为参数,综合得了解了一下现行页面间传参 ...

  4. 一些CSS"bug"

    1.img三像像素问题 解决办法:img{display:block;} or img{vertical-align:middle;} 问题原因:img是行内元素,默认的垂直对齐方式 baseline ...

  5. PHP isset()与empty()的区别详解

    通过对PHP语言的学习,应该知道它是基于函数的一款HTML脚本语言. 庞大的函数库支持着PHP语言功能的实现. 有关PHP函数isset()与empty()的相关用法. PHP的isset()函数 一 ...

  6. php下使用phpmailer发送邮件

    由于默认虚拟空间不支持mail()函数,客户需要留言发送邮件,找到phpmailer发送不成功,调试成功后记录一下. 最新的下载地址在github,https://github.com/Synchro ...

  7. android获取com.android.internal.R

    使用class.jar, layout.jar可以直接导入com.android.internal.R 但是有个方法获取不到值mDatePicker.findViewById(com.android. ...

  8. spring error

    <aop:config> <aop:pointcut id="allMethod" expression="execution(* a.j.shop.s ...

  9. Ruby处理二进制(未完成)

    https://practicingruby.com/articles/binary-file-formats http://stackoverflow.com/questions/16821435/ ...

  10. MIFARE系列6《射频卡与读写器的通讯》

    1. 复位应答(Answer to request) 读写器呼叫磁场内的卡片,卡片对呼叫做出应答.对刚进入磁场得到电复位处于休闲状态的卡片,卡请求(REQA,0x26):对于已进行过读写操作并进入休眠 ...