6.6 过滤器 - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith
Version: null
6.6 过滤器
Although Grails controllers support fine grained interceptors, these are only really useful when applied to a few controllers and become difficult to manage with larger applications. Filters on the other hand can be applied across a whole group of controllers, a URI space or to a specific action. Filters are far easier to plugin and maintain completely separately to your main controller logic and are useful for all sorts of cross cutting concerns such as security, logging, and so on.
虽然,Grails的控制器支持良好的细粒度拦截器,但它们只是对少数控制器有用,当处理大型应用时就会变得很困难。另一方面,过滤器能横跨整组控制器,一个URI空间或者一种具体的操作。相比插件来说,过滤器更容易、更彻底地维护分离你控制器的主要逻辑,也非常有利于像安全,日志等等这样的横切关注点。
6.6.1 应用过滤器
To create a filter create a class that ends with the convention Each filter you define within the The scope of the filter can be one of the following things:
In addition, the order in which you define the filters within the
要创建一个过滤器,只需要在Filters in the grails-app/conf directory. Within this class define a code block called filters that contains the filter definitions:class ExampleFilters {
def filters = {
// your filters here
}
}filters block has a name and a scope. The name is the method name and the scope is defined using named arguments. For example to define a filter that applies to all controllers and all actions you can use wildcards:sampleFilter(controller:'*', action:'*') {
// interceptor definitions
}- A controller and/or action name pairing with optional wildcards
- A URI, with Ant path matching syntax
controller- controller matching pattern, by default * is replaced with .* and a regex is compiledcontrollerExclude- controller exclusion pattern, by default * is replaced with .* and a regex is compiledaction- action matching pattern, by default * is replaced with .* and a regex is compiledactionExclude- action exclusion pattern, by default * is replaced with .* and a regex is compiledregex(true/false) - use regex syntax (don't replace '*' with '.*')uri- a uri to match, expressed with as Ant style path (e.g. /book/**)uriExclude- a uri pattern to exclude, expressed with as Ant style path (e.g. /book/**)find(true/false) - rule matches with partial match (seejava.util.regex.Matcher.find())invert(true/false) - invert the rule (NOT rule)
- All controllers and actions
all(controller: '*', action: '*') {}- Only for the
BookController
justBook(controller: 'book', action: '*') {}- All controllers except the
BookController
notBook(controller: 'book', invert: true) {}- All actions containing 'save' in the action name
saveInActionName(action: '*save*', find: true) {}- All actions starting with the letter 'b' except for actions beginning with the phrase 'bad*'
actionBeginningWithBButNotBad(action: 'b*', actionExclude: 'bad*', find: true) {}- Applied to a URI space
someURIs(uri: '/book/**') {}- Applied to all URIs
allURIs(uri: '/**') {}filters code block dictates the order in which they are executed. To control the order of execution between Filters classes, you can use the dependsOn property discussed in filter dependencies section.Note: When exclude patterns are used they take precedence over the matching patterns. For example, if action is 'b*' and actionExclude is 'bad*' then actions like 'best' and 'bien' will have that filter applied but actions like 'bad' and 'badlands' will not.
grails-app/conf目录下创建一个符合规约以Filters结尾的类即可。在此类中,定义一个名为filters的代码块,用以包含过滤器的定义:class ExampleFilters {
def filters = {
// your filters here
}
}filters代码块内的每一个过滤器有一个名称和作用域。名称就是其方法名,作用域是通过命名参数定义的。比如,要定义一个应用于所有控制器和操作的过滤器,你可以使用通配符:sampleFilter(controller:'*', action:'*') {
// interceptor definitions
}- 一个控制器或者操作名称,支持可选的通配符
- 一个URI,符合Ant路径(path)匹配语法
controller- 控制器匹配模式,缺省情况下,其用*可以替代.*,并且被编译为一个正则表达式controllerExclude- 控制器的排除模式,缺省情况下,其用*可以替代.*,并且被编译为一个正则表达式action- 操作匹配模式,缺省情况下,其用*可以替代.*,并且被编译为一个正则表达式actionExclude- 操作排除模式,缺省情况下,其用*可以替代.*,并且被编译为一个正则表达式regex(true/false) - 使用正则表达式语法(不使用'*'代替'.*')uri- 一个uri匹配,使用Ant风格的路径(path)(比如 /book/**)uriExclude- 一个uri排除匹配,使用Ant风格的路径(path)(比如 /book/**)find(true/false) - 符合部分匹配的规则匹配(更多请参考java.util.regex.Matcher.find())invert(true/false) - 反转规则(不符合此规则的条件)
- 匹配所有的控制器和操作
all(controller: '*', action: '*') {}- 仅仅匹配
BookController
justBook(controller: 'book', action: '*') {}- 匹配所有的控制器,除了
BookController
notBook(controller: 'book', invert: true) {}- 匹配所有操作名包含'save'的操作
saveInActionName(action: '*save*', find: true) {}- 匹配所有操作字母以'b'开头的操作,不过'bad*'除外
actionBeginningWithBButNotBad(action: 'b*', actionExclude: 'bad*', find: true) {}- 应用于一个URI
someURIs(uri: '/book/**') {}- 应用于所有的URIs
allURIs(uri: '/**') {}filters代码块中定义的过滤器顺序就是它们被执行的顺序。要控制Filters类之间的执行顺序,你可以使用dependsOn属性,更多信息将在过滤器的依赖章节讨论。注意:当使用排除模式的时候,其优先级将高于其他的匹配模式。比如一个作用域,其action是'b*'而actionExclude是'bad*',那么操作名称是'best'和'bien'将应用于此过滤器,而操作名是'bad'和'badlands'却没有。
6.6.2 过滤器的类型
Within the body of the filter you can then define one or several of the following interceptor types for the filter:
Here the In this logging example we just log various request information, but note that the
在过滤器的主体内,你可以定义下列过滤器中拦截器类型的一个或者几个:
before- Executed before the action. Returnfalseto indicate that the response has been handled that that all future filters and the action should not executeafter- Executed after an action. Takes a first argument as the view model to allow modification of the model before rendering the viewafterView- Executed after view rendering. Takes an Exception as an argument which will be non-nullif an exception occurs during processing. Note: this Closure is called before the layout is applied.
class SecurityFilters {
def filters = {
loginCheck(controller: '*', action: '*') {
before = {
if (!session.user && !actionName.equals('login')) {
redirect(action: 'login')
return false
}
}
}
}
}loginCheck filter uses a before interceptor to execute a block of code that checks if a user is in the session and if not redirects to the login action. Note how returning false ensure that the action itself is not executed.Here's a more involved example that demonstrates all three filter types:import java.util.concurrent.atomic.AtomicLongclass LoggingFilters { private static final AtomicLong REQUEST_NUMBER_COUNTER = new AtomicLong() private static final String START_TIME_ATTRIBUTE = 'Controller__START_TIME__' private static final String REQUEST_NUMBER_ATTRIBUTE = 'Controller__REQUEST_NUMBER__' def filters = { logFilter(controller: '*', action: '*') { before = { if (!log.debugEnabled) return true long start = System.currentTimeMillis() long currentRequestNumber = REQUEST_NUMBER_COUNTER.incrementAndGet() request[START_TIME_ATTRIBUTE] = start request[REQUEST_NUMBER_ATTRIBUTE] = currentRequestNumber log.debug "preHandle request #$currentRequestNumber : " + "'$request.servletPath'/'$request.forwardURI', " + "from $request.remoteHost ($request.remoteAddr) " + " at ${new Date()}, Ajax: $request.xhr, controller: $controllerName, " + "action: $actionName, params: ${new TreeMap(params)}" return true } after = { Map model -> if (!log.debugEnabled) return true long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE] def msg = "postHandle request #$requestNumber: end ${new Date()}, " + "controller total time ${end - start}ms" if (log.traceEnabled) { log.trace msg + "; model: $model" } else { log.debug msg } } afterView = { Exception e -> if (!log.debugEnabled) return true long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE] def msg = "afterCompletion request #$requestNumber: " + "end ${new Date()}, total time ${end - start}ms" if (e) { log.debug "$msg \n\texception: $e.message", e } else { log.debug msg } } } } }
model map in the after filter is mutable. If you need to add or remove items from the model map you can do that in the after filter.
before- 在操作之前执行。返回值false表示响应已经被符合条件的过滤器处理过,并且其操作不被执行after- 在操作之后执行。其第一个参数为视图模型(view model),并且允许在渲染视图之前修改此模型afterView- 在渲染视图之后执行。如果有异常发生,其第一个参数为一个非null的异常。注意:此闭包在应用布局以前被调用。
class SecurityFilters {
def filters = {
loginCheck(controller: '*', action: '*') {
before = {
if (!session.user && !actionName.equals('login')) {
redirect(action: 'login')
return false
}
}
}
}
}loginCheck过滤器使用了before拦截器来执行一个代码块,用以检查一个用户是否在会话当中,如果不在,就重定向到login操作。注意:如何通过返回false的方式来确保操作本身不被执行。下面是一个更深入的示例来演示所有的三种过滤器类型:import java.util.concurrent.atomic.AtomicLongclass LoggingFilters { private static final AtomicLong REQUEST_NUMBER_COUNTER = new AtomicLong() private static final String START_TIME_ATTRIBUTE = 'Controller__START_TIME__' private static final String REQUEST_NUMBER_ATTRIBUTE = 'Controller__REQUEST_NUMBER__' def filters = { logFilter(controller: '*', action: '*') { before = { if (!log.debugEnabled) return true long start = System.currentTimeMillis() long currentRequestNumber = REQUEST_NUMBER_COUNTER.incrementAndGet() request[START_TIME_ATTRIBUTE] = start request[REQUEST_NUMBER_ATTRIBUTE] = currentRequestNumber log.debug "preHandle request #$currentRequestNumber : " + "'$request.servletPath'/'$request.forwardURI', " + "from $request.remoteHost ($request.remoteAddr) " + " at ${new Date()}, Ajax: $request.xhr, controller: $controllerName, " + "action: $actionName, params: ${new TreeMap(params)}" return true } after = { Map model -> if (!log.debugEnabled) return true long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE] def msg = "postHandle request #$requestNumber: end ${new Date()}, " + "controller total time ${end - start}ms" if (log.traceEnabled) { log.trace msg + "; model: $model" } else { log.debug msg } } afterView = { Exception e -> if (!log.debugEnabled) return true long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE] def msg = "afterCompletion request #$requestNumber: " + "end ${new Date()}, total time ${end - start}ms" if (e) { log.debug "$msg \n\texception: $e.message", e } else { log.debug msg } } } } }
after过滤器中的model是可变的。如果你需要增加或者移除model的内容,可以在after过滤器中实现。
6.6.3 变量和作用域
Filters support all the common properties available to controllers and tag libraries, plus the application context:
过滤器支持控制器和标签库的所有公共属性,外加应用环境上下文(application context):
- request - The HttpServletRequest object
- response - The HttpServletResponse object
- session - The HttpSession object
- servletContext - The ServletContext object
- flash - The flash object
- params - The request parameters object
- actionName - The action name that is being dispatched to
- controllerName - The controller name that is being dispatched to
- grailsApplication - The Grails application currently running
- applicationContext - The ApplicationContext object
- request - HttpServletRequest对象
- response - HttpServletResponse对象
- session - HttpSession对象
- servletContext - ServletContext对象
- flash - flash对象
- params - 请求参数对象
- actionName - 正在使用的操作名称
- controllerName - 正在使用的控制器名称
- grailsApplication - 当前正在运行的Grails应用
- applicationContext - ApplicationContext对象
6.6.4 过滤器依赖
In a MyFilters specifically
在一个Filters class, you can specify any other Filters classes that should first be executed using the dependsOn property. This is used when a Filters class depends on the behavior of another Filters class (e.g. setting up the environment, modifying the request/session, etc.) and is defined as an array of Filters classes.Take the following example Filters classes:class MyFilters {
def dependsOn = [MyOtherFilters] def filters = {
checkAwesome(uri: "/*") {
before = {
if (request.isAwesome) { // do something awesome }
}
} checkAwesome2(uri: "/*") {
before = {
if (request.isAwesome) { // do something else awesome }
}
}
}
}class MyOtherFilters {
def filters = {
makeAwesome(uri: "/*") {
before = {
request.isAwesome = true
}
}
doNothing(uri: "/*") {
before = {
// do nothing
}
}
}
}dependsOn MyOtherFilters. This will cause all the filters in MyOtherFilters whose scope matches the current request to be executed before those in MyFilters. For a request of "/test", which will match the scope of every filter in the example, the execution order would be as follows:
- MyOtherFilters - makeAwesome
- MyOtherFilters - doNothing
- MyFilters - checkAwesome
- MyFilters - checkAwesome2
Filters classes are enabled and the execution order of filters within each Filters class are preserved.If any cyclical dependencies are detected, the filters with cyclical dependencies will be added to the end of the filter chain and processing will continue. Information about any cyclical dependencies that are detected will be written to the logs. Ensure that your root logging level is set to at least WARN or configure an appender for the Grails Filters Plugin (org.codehaus.groovy.grails.plugins.web.filters.FiltersGrailsPlugin) when debugging filter dependency issues.
Filters类中,你可以使用dependsOn属性来指定其他任意Filters类先被执行。这经常用在一个Filters类依赖于另外一个Filters类的行为的时候(比如,设置环境,修改请求/会话等),并且可以定义为一个Filters类的数组。以如下所示的Filters类为例:class MyFilters {
def dependsOn = [MyOtherFilters] def filters = {
checkAwesome(uri: "/*") {
before = {
if (request.isAwesome) { // do something awesome }
}
} checkAwesome2(uri: "/*") {
before = {
if (request.isAwesome) { // do something else awesome }
}
}
}
}class MyOtherFilters {
def filters = {
makeAwesome(uri: "/*") {
before = {
request.isAwesome = true
}
}
doNothing(uri: "/*") {
before = {
// do nothing
}
}
}
}dependsOn为MyOtherFilters。这将导致MyOtherFilters中符合当前请求的所有过滤器优先于MyFilters执行。对一个"/test"请求来说,示例中的每一个过滤器都会匹配到,那么其执行的顺序将如下所示:
- MyOtherFilters - makeAwesome
- MyOtherFilters - doNothing
- MyFilters - checkAwesome
- MyFilters - checkAwesome2
Filters类之间的执行顺序是定制的,并且每个Filters类内的过滤器顺序是预置的。如果任何循环依赖被检测到的话,那么循环依赖的过滤器将被加到过滤器链最后,并且处理将继续进行。任何循环依赖的信息将被记录到日志当中,不过在调试过滤器依赖问题的时候,要确保你的根日志级别至少是WARN或者为Grails的过滤器插件(org.codehaus.groovy.grails.plugins.web.filters.FiltersGrailsPlugin)配置一个输出器。

