Grails3.0 第七章 web层 - wuyane/Experience GitHub Wiki
#web 层(The Web Layer) 这一章主要涉及到用grails3.0开发web应用程序,对controller,action,view ,Interceptor等分别做了讲解,内容比较细致,现自己整理并汇总了一下本章的主要知识要点,供大家参考。 ##控制器(Controller)
控制器是整个web开发的核心,用于接收用户的请求,并将请求交由相应的action处理,最后将结果返回给客户端,控制器里可以写多个Action,和Interceptor
grails create-controller book
- prototype (default) :这个是grails中默认的,会在用户每一次请求时重新创建一个controller(多例)
- session :在用户的会话范围创建一个controller
- singleton :只有一个控制器的实例存在,(单例) 可以在controller 中加入一个静态属性指定一下controller是使用多例还是单例
static scope = "singleton"
或者去Config.groovy文件中去配置
grails.controllers.defaultScope = "singleton"
但grails推荐使用多例(prototype)这样可以避免数据被所有的请求所共享带来的安全隐患问题
写在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"
- 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
}
}
- 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.进行数据处理
- 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标签
<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>"
}
<%@ page import="java.awt.*" %>
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给客户端返回相应类型的文件
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']
]
比如说,一个控制器动作可以返回各种不同格式的资源: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功能.相比老的过滤器性能更优越