Grails3.0 第七章 web层 - wuyane/Experience GitHub Wiki

#web 层(The Web Layer) 这一章主要涉及到用grails3.0开发web应用程序,对controller,action,view ,Interceptor等分别做了讲解,内容比较细致,现自己整理并汇总了一下本章的主要知识要点,供大家参考。 ##控制器(Controller)

控制器是整个web开发的核心,用于接收用户的请求,并将请求交由相应的action处理,最后将结果返回给客户端,控制器里可以写多个Action,和Interceptor

创建Controller

grails create-controller book

控制器的作用域(Scoped Controllers)

  • prototype (default) :这个是grails中默认的,会在用户每一次请求时重新创建一个controller(多例)
  • session :在用户的会话范围创建一个controller
  • singleton :只有一个控制器的实例存在,(单例) 可以在controller 中加入一个静态属性指定一下controller是使用多例还是单例
static scope = "singleton"

或者去Config.groovy文件中去配置

grails.controllers.defaultScope = "singleton"

但grails推荐使用多例(prototype)这样可以避免数据被所有的请求所共享带来的安全隐患问题

拦截器(Interceptor)

写在controller中的Interceptor可以对这个controller下的所有Action进行拦截,只有before和after 两类拦截器,也可以通过except,only关键字排除和指定部分action

  • before 是还没有执行action之前拦截之后再做某些事情,如判断用户是否登录,如果Interceptor返回false就不执行Action
  • after 是执行action之后,还没有渲染页面之前时拦截之后做某些事情
def beforeInterceptor = [action: this.&auth, except: 'login']//拦截条件
def beforeInterceptor = [action: this.&auth, only: ['secure']]
def beforeInterceptor = [action: this.&auth, except: ['login', 'register']]

private auth() {
    if (!session.user) {
        redirect(action: 'login')
        return false
    }
}

def login() {
    // 显示登录页面
}

##动作方法(Action)

action 就是用来处理用户请求,在grails 中以方法的形式定义Action,早期版本使用的是闭包,一个action会映射成一个url,控制器中也默认的action

  • 如果一个控制器中只有一个action则这个action是默认的
  • 如果一个控制器中有一个index的action,则这个index是默认的
  • 如果在控制器中设置了默认action 则这个action就是默认的:
static defaultAction = "list"

action 中可以使用的作用域用来存取数据:

  • servletContext:整个应用范围
  • session :一次会话范围
  • request :一次请求范围
  • params :是一个map集合,可以存入键值对的数据
  • flash : 在本次请求和下一次请求中,之后再清除数据 存取的方式:
class BookController {
    def find() {
        def name = params["name"]
        def appContext = request["foo"]
        def loggedUser = session["currentUser"]
    }
}

或者

class BookController {
    def find() {
        def name = params.name
        def appContext = request.foot
        def loggedUser = session.currentUser
    }
}

action的入参:params与common对象

  • params:一个map集合,用户请求的参数以及controller,action,fromat,都会放入到params中 用params构建一个实体对象
def book=new Book(params)

更新一个对象

def b = Book.get(params.id)
    b.properties = params
    b.save()

复杂对象关系params存储:如Person 类中有HomeAddresse类,HomeAddress中有country,ctiy两个属性,则params是这样进行存的

[person: [homeAddress: [country: 'USA', city: 'St. Louis']]]
  • Command命令对象

和domain一样,domain类会生成表,属性会生成相应的列,grails会对domain类进行验证,而 Command对象不会生成表,grails不会对其验证,可以通过加注解,让grails 去验证

def save(BookCommand book){}

1.grails会根据params中的参数对BookCommand对象的属性类型进行相应的类型转换,转换失败则将错误放在这个命令对象的Errors集合中

2.类型转换成功之后再进行约束验证,如验证错误将错误信息存在Errors中,

3.进行数据处理

数据返回(Models and Views)

  • render的使用

一个简单的返回:

def show() {
    [book: Book.get(params.id)]
}

返回到一个指定的页面

render(view: 'show')

向页面中响应数据

render "Hello World!"

返回一个模板,并将数据带入模板页

render(template: 'book_template', collection: Book.list())

返回一个xml

render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")

或者

import grails.converters.*
render Book.list() as XML

或者使用MarkupBuilder返回一个xml

import groovy.xml.MarkupBuilder
def login() {
    def writer = new StringWriter()
    def builder = new MarkupBuilder(writer)
    builder.html {
        head {
            title 'Log in'
        }
        body {
            h1 'Hello'
            form {
            }
        }
    }

    def html = writer.toString()
    render html
}

响应一个json

import grails.converters.*
render Book.list() as JSON

或者

render(contentType: "application/json") {
    hello = "world"
}
  • redirect的使用 重定向到本类中的一个action()处理
redirect(action: login)

重定向到另一个controller的action中处理

redirect(controller: 'home', action: 'index')

重定向到一个url

redirect(url: "http://grails.org")

带参的重定向

redirect(action: 'myaction', params: [myparam: "myvalue"])

##视图(view)

GSP Tags

  • GSP标签
<g:set var="now" value="${new Date()}" />
<g:if test="${session.role == 'admin'}"></g:if>
<g:else></g:else>
<g:link action="show" id="1">Book 1</g:link>
<g:form name="myForm" url="[controller:'book',action:'list']">...</g:form>
<g:textField name="myField" value="${myValue}" />
<g:actionSubmit value="Some update label" action="update" />
<g:each in="${[1,2,3]}" var="num">
   <p>Number ${num}</p>
</g:each>
<g:render template="bookTemplate" model="[book: myBook]" />
  • 自定义标签 自定义标签放在grails-app/taglib 目录下,以TagLib结尾,默认以g作为前缀,也可以自定义命名空间
class SimpleTagLib {
    def emoticon = { attrs, body ->
       out << body() << (attrs.happy == 'true' ? " 周日" : " 周一")
    }
}

也可以这样向页面输出html

def formatBook = { attrs, body ->
    out << "<div id="${attrs.book.id}">"
    out << "Title : ${attrs.book.title}"
    out << "</div>"
}

GSP指令

<%@ page import="java.awt.*" %>

布局与Sitemesh

sitemesh布局类似一个页面的母版,可以在多个页面进行复用,比如网站的头部与底部,这个布局页在grails-app/views/layouts 目录下 例如 sitemesh页:

<html>
    <head>
        <title><g:layoutTitle default="这是母版页面" /></title>
        <g:layoutHead />
    </head>
    <body>
        <div class="menu">这个网站导航栏</menu>
            <div class="body">
                <g:layoutBody />
            </div>
        </div>
    </body>
</html>

目标页(要引用sitemesh的页面)

<html>
    <head>
        <title>这是目标页面</title>
        <meta name="layout" content="main" />
    </head>
    <body>嘻嘻!哈哈!</body>
</html>
  • layoutTitle 用于输出目标页面的title
  • layoutHead 用于输出目标页面的头部
  • layoutBody 用于输出目标页面的body中的内容

内联布局

<g:applyLayout name="myLayout" template="bookTemplate" collection="${books}" />

####防表单重复提交

<g:form useToken="true" ...>

##URL映射 就是定义url的规则,默认是/controller/action/id,grails2.5这个目录是在grails-app/conf/UrlMappings.groovy目录下,grails3.0现在放在grails-app/controllers/UrlMappings.groovy下

  • ** 映射到Controllers and Actions **
class UrlMappings {
    static mappings = {
    "/product"(controller: "product", action: "list")
    }
}
  • 映射到REST 资源
"/book"(resource:'book')

results URL映射

HTTP Method URL Grails Action
GET /book/create create
POST /book save
GET /book show
GET /book/edit edit
PUT /book update
DELETE /book delete
  • 重定向url映射
"/viewBooks"(redirect: '/books/list')
"/viewAuthors"(redirect: [controller: 'author', action: 'list'])
"/viewPublishers"(redirect: [controller: 'publisher', action: 'list', permanent: true])
  • 动态controller和Action
static mappings = {
    "/$controller/$action?/$id?"()
}
  • 映射到视图
static mappings = {
    "/"(view: "/index")  // map the root URL
}
  • 映射到响应代码
static mappings = {
   "403"(view: "/errors/forbidden")
   "404"(view: "/errors/notFound")
   "500"(view: "/errors/serverError")
}

或者

static mappings = {
   "403"(controller: "errors", action: "forbidden")
   "404"(controller: "errors", action: "notFound")
   "500"(controller: "errors", action: "serverError")
}
  • 映射到HTTP方法
static mappings = {
   "/product/$id"(controller:"product", action: "update", method: "PUT") 
}

##拦截器

虽然Grails的controller支持细粒度的拦截器,但使用有限,当在多个controller或者更大的应用程序时就会变得难以管理。Grails3.0创建的拦截器在grails-app/controllers目录下 创建Interceptors

$ grails create-interceptor MyInterceptor

默认内容如下:

class MyInterceptor {

  boolean before() { true }

  boolean after() { true }

  void afterView() {
    // no-op
  }

}

拦截器与过滤器

在grails 3.0以前的版本支持过滤器的概念,在新版本中也保持向后兼容,但被认为是已过时的,Grails 3.0中新的拦截器有很多优越的方面,最重要的是拦截器可以使用Groovy的CompileStatic注释来优化性能 ###匹配请求的Interceptor 可以配置拦截器匹配任何请求或者matchAll方法中指定的controller进行拦截

class AuthInterceptor {
  AuthInterceptor() {
    matchAll()
    .excludes(controller:"login")
  }

  boolean before() {
    // perform authentication
  }
}

或者使用命名参数

class LoggingInterceptor {
  LoggingInterceptor() {
    match(controller:"book", action:"show") // using strings
    match(controller: ~/(author|publisher)/) // using regex
  }

  boolean before() {
   
  }
}

可用的命名参数有:

  • namespace: controller的命名空间
  • controller: controller的名字
  • action: action的名字
  • method: HTTP方法
  • uri: 请求的URL(不能与其它参数结合使用)

拦截器优先级(控制拦截器的执行顺序)

class AuthInterceptor {

 // int order = HIGHEST_PRECEDENCE
    int order = LOWEST_PRECEDENCE  
}

也可以在grails-app/conf/application.yml中配置拦截器的优先级,这个配置会覆盖拦截器默认的顺序

beans:
  authInterceptor:
    order: 50

##内容协商 Grails支持内容协商,可以通过http请求头中的format参数或者映射的url,通俗说就是通过请求参数中的format给客户端返回相应类型的文件

配置Mime 类型

Grails就配置了不同的内容类型在grails-app/conf 配置。使用grails.mime.types。类型设置:

grails.mime.types = [ // the first one is the default format
    all:           '*/*', // 'all' maps to '*' or the first available format in withFormat
    atom:          'application/atom+xml',
    css:           'text/css',
    csv:           'text/csv',
    form:          'application/x-www-form-urlencoded',
    html:          ['text/html','application/xhtml+xml'],
    js:            'text/javascript',
    json:          ['application/json', 'text/json'],
    multipartForm: 'multipart/form-data',
    rss:           'application/rss+xml',
    text:          'text/plain',
    hal:           ['application/hal+json','application/hal+xml'],
    xml:           ['text/xml', 'application/xml']
]

使用format参数进行内容协商

比如说,一个控制器动作可以返回各种不同格式的资源:HTML,XML和JSON。哪种格式是客户端要获得的?客户端控制这个最简单,最可靠的方法是通过一个format的URL参数。 如果客户端面希望资源为XML,你可以这样使用URL:

http://my.domain.org/books?format=xml

在controller 中witFormat处理特定响应格式

import grails.converters.JSON
import grails.converters.XML

class BookController {

    def list() {
        def books = Book.list()

        withFormat {
            html bookList: books
            json { render books as JSON }
            xml { render books as XML }
            '*' { render books as JSON }
        }
    }
}
  • html:请求参数fromat='html'则withFormat中 html 块将被调用
    • :没有明确的格式相匹配(通配符)块可以用来处理所有其他格式。

##本单总结 Grails3.0 在web层没有太大的变化。Controller,Action,GSP都还是一样的,新增了拦截器Interceptor功能.相比老的过滤器性能更优越

⚠️ **GitHub.com Fallback** ⚠️