Grails:在控制器的基类中重定向或转发后防止进一步执行代码
我有以下控制器结构:
abstract class AbstractController {
// ...
}
abstract class AbstractDiagramController extends AbstractController {
// ...
}
class PopulationController extends AbstractDiagramController {
// ...
}
大多数控制器操作调用抽象基类的各种方法。如果现在这些基本方法之一需要向客户端发送重定向(或转发),Grails 无论如何都不会阻止应用程序处理控制器操作的剩余操作代码。
从我的角度来看,这是一种不受欢迎的行为,因为基本方法会进行某种验证(例如验证参数、用户、会话等),并且控制器假设验证成功(因此会产生后续错误)。
我怎样才能防止这种不足的行为?
亲切的问候, 克里斯托弗
PS:我已经发现 这个问题,但答案并没有满足我的需求,因为它们都没有涉及基本控制器:
PPS: I am using Grails in version 1.3.7
EDIT
This is a的反应维克多·塞尔吉延科的评论。 在这里我给出了我的问题的更详细的代码示例。
// The very base controller
abstract class AbstractController {
// The interceptor gets called before any action of inheritors get processed
def beforeInterceptor = [action:this.&initialize]
// Method validates various stuff
protected initialize() {
if( someThingIsWrong() ) {
// This redirect should stop any other code of inheritors
redirect( controller: "foo", action: "bar" )
return false
}
}
}
// The second base controller
abstract class AbstractDiagramController extends AbstractController {
// This object must get initialized. If not (eg any errors or exceptions occured)
// all inheritors actions are not allowed to do anything
MyObject myGreatObject = null
// Overriden, because of additional individual diagram validation
@Override
protected initialize() {
// Do parents stuff first
super.auth()
// If parent failed, this code should not get executed anymore.
// Yes, here I could check if parent returned false and return false as well before
// continuing the next validation. Anyway I have to do this because grails, comprehendibly,
// will throw an exception if two redirects were executed in a row.
// (With this I just want to visualize the behaviour I'd expect)
if( someThingElseIsWrong() ) {
redirect( controller: "hello", action: "world")
return false
}
// After validation I can initialize the object
myGreatObject = new MyObject()
}
}
// A controller implementation
class MyDiagramController extends AbstractDiagramController {
// Overriden because of inidividual validation
@Override
protected initialize() {
// First do parent stuff
boolean succeeded = super.auth()
// Again, annoying double check
if( !succeeded ) {
return false
}
}
def myAction = {
myGreatObject.SpinAroundAndBeHappy()
}
}
看起来将用例减少到最少的代码行是个好主意。现在看来,Victor 的建议(canContinue
或 hasErrors
)可以以某种方式解决这种不愉快的情况,尽管这是某种解决方法。
但不知怎的,我不喜欢那些双重检查。我仍然在与这样一个事实作斗争:抽象基本控制器之上的所有层都必须对之前已经发生的无效验证做出反应(并且也应该由基本控制器自己管理)。从我的角度来看,这些检查不应该是控制器实现的业务。
PS:我希望代码中没有出现严重错误。
I have following controller structure:
abstract class AbstractController {
// ...
}
abstract class AbstractDiagramController extends AbstractController {
// ...
}
class PopulationController extends AbstractDiagramController {
// ...
}
Most controller-actions call various methods of the abstract base classes. If now one of these base-methods needs to send a redirect (or forward) to the client, Grails won't prevent the application from processing the remaining action code of the controller-action anyway.
From my point of view this is an undesirable behaviour, due the base-methods do some kind of validation (like validating parameter, user, session etc), and the controller assumes that the validation succeeded (thence produces subsequent errors).
How can i prevent this insufficient behaviour?
Kind regards,
Christopher
PS: I found already this question, but the answers did not satisfy my needs, because none of them deal with a base controller:
PPS: I am using Grails in version 1.3.7
EDIT
This is a reaction of Victor Sergienko's comments.
Here I give a more detailled code-example of my problem.
// The very base controller
abstract class AbstractController {
// The interceptor gets called before any action of inheritors get processed
def beforeInterceptor = [action:this.&initialize]
// Method validates various stuff
protected initialize() {
if( someThingIsWrong() ) {
// This redirect should stop any other code of inheritors
redirect( controller: "foo", action: "bar" )
return false
}
}
}
// The second base controller
abstract class AbstractDiagramController extends AbstractController {
// This object must get initialized. If not (eg any errors or exceptions occured)
// all inheritors actions are not allowed to do anything
MyObject myGreatObject = null
// Overriden, because of additional individual diagram validation
@Override
protected initialize() {
// Do parents stuff first
super.auth()
// If parent failed, this code should not get executed anymore.
// Yes, here I could check if parent returned false and return false as well before
// continuing the next validation. Anyway I have to do this because grails, comprehendibly,
// will throw an exception if two redirects were executed in a row.
// (With this I just want to visualize the behaviour I'd expect)
if( someThingElseIsWrong() ) {
redirect( controller: "hello", action: "world")
return false
}
// After validation I can initialize the object
myGreatObject = new MyObject()
}
}
// A controller implementation
class MyDiagramController extends AbstractDiagramController {
// Overriden because of inidividual validation
@Override
protected initialize() {
// First do parent stuff
boolean succeeded = super.auth()
// Again, annoying double check
if( !succeeded ) {
return false
}
}
def myAction = {
myGreatObject.SpinAroundAndBeHappy()
}
}
Looks like it was a good idea to reduce the use-case to the minimum lines of code. Now it seem like Victor's suggestions (either canContinue
or hasErrors
) could solve this unpleasant circumstances somehow, even though it's some kind of workaround.
But somehow I don't like those double-checks. I'm still struggling against the fact, that all layers above the abstract base controller have to react on invalid validations that happened already before (and also should be managed by the base controllers on their own). From my point of view those checks should not be the business of the controller implementations.
PS: I hope no grave mistakes have slipped in the code.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
作为解决方法,您可以从祖先操作返回
boolean canContinue
或引发异常,或者根据您的情况检查instance.hasErrors()
。编辑:事实上
initialize()
在操作之前被调用,看起来像访问控制或操作语义的另一个完全覆盖(在操作的任何部分之前)被执行)。请告诉我的假设是否错误。当我们对不同操作进行自定义安全访问时,我们使用自己的标签(例如
@SecuredDoodah
)对操作进行注释(请参阅@Secured),并添加了一个完全覆盖操作的Filter
(对于我们来说,Filter
响应 403,但这不是必需的)。Filter
可能比beforeInterceptor
更好。如果您需要从Filter
传递某些状态,例如示例中的myGreatObject
,您可以将 Service 注入 Filter 并将状态保存在 Service 中。我确信有比我的想法更好的方法,但这对于控制器来说应该是透明的。
As a workaround, you can return a
boolean canContinue
from an ancestor action or throw an exception, or checkinstance.hasErrors()
in your case.EDIT: The fact
initialize()
is called before an action looks like access control or another complete override of action semantics (before any part of action is executed). Please tell if my assumption is wrong.When we did a custom security access to different actions, we annotated the actions with own tags like
@SecuredDoodah
(see @Secured), and added aFilter
that completely overrides the action (for us,Filter
responds with 403, but it's not necessary).Filter
might be better thanbeforeInterceptor
. If you need to pass some state fromFilter
, likemyGreatObject
in the sample, you can inject a Service into Filter and save the state in the Service.I'm sure there are better ways then my idea, but this should work transparently for Controllers.
您受到以下事实的限制:这是 Java/Groovy,方法调用无法立即触发方法(或闭包)退出。我看到另一个框架作弊,当你调用渲染、重定向等时,它会抛出一个异常,该异常会捕获框架基类。这就像 Goto,但实际上并不存在。
但这是一种昂贵的方法 - 不必要地填充所有这些堆栈帧是浪费的,因为这不是异常情况,并且堆栈将始终被忽略。
不幸的是,您需要像 Victor 那样使用布尔返回值的方法。
You're limited by the fact that this is Java/Groovy and there's no way for a method call to immediately trigger an exit from a method (or Closure). I saw that another framework cheats and when you call render, redirect, etc. it throws an exception that's caught it the framework base class. This acts like a Goto, which doesn't really exist.
It's an expensive approach though - filling in all those stack frames unnecessarily is wasteful since it's not an exception case and the stack will always be ignored.
Unfortunately you need an approach like Victor's where you use boolean return values.