【问题标题】:Exception Thrown From Service Not Being Caught in Controller服务引发的异常未在控制器中捕获
【发布时间】:2013-11-14 20:28:52
【问题描述】:

在我的 Grails 服务中,我有如下代码:

def createCharge(chargeParams) {
  try {
    def charge = Charge.create(chargeParams)
  } catch (CardException e) {
    throw e
  }
}

从我的控制器我执行以下操作:

try  {
   service.createCharge(chargeParams)
} catch(CardException e) {

}

但是,我的控制器没有捕捉到 CardException 的重新抛出。如果我通过以下方式将 CardException 包装在 RuntimeException 中:

throw new RuntimeException(e)

和/或从捕获中删除签名以仅捕获(e)而不键入它,它可以工作,但是我从异常中丢失了一些信息,例如消息。

请注意,CardException 是 Exception,而不是 RuntimeException。我不确定这是否重要。

【问题讨论】:

    标签: exception grails stripe-payments


    【解决方案1】:

    与 Java 不同,您不必声明由 Groovy 方法抛出的(已检查)异常,因为任何未声明的已检查异常都包装在 UndeclaredThrowableException 中。所以这个:

    def createCharge(chargeParams) {
      try {
        def charge = Charge.create(chargeParams)
      } catch (CardException e) {
        throw e
      }
    }
    

    实际上等同于:

    def createCharge(chargeParams) throws UndeclaredThrowableException {
      try {
        def charge = Charge.create(chargeParams)
      } catch (CardException e) {
        throw new UndeclaredThrowableException(e)
      }
    }
    

    上面抛出的异常,显然不会被:

    try  {
       service.createCharge(chargeParams)
    } catch(CardException e) {
    
    }
    

    但它会被:

    try  {
       service.createCharge(chargeParams)
    } catch(e) {
    
    }
    

    因为这只是以下的简写:

    try  {
       service.createCharge(chargeParams)
    } catch(Exception e) {
    
    }
    

    【讨论】:

    • 好吧,我需要检查异常,因为我需要从 Stripe API 处理几个。我最终选择了一条不同的路线,因为我还需要始终抛出 RuntimeException 以便我的事务正确回滚。我将您的答案标记为正确,因为它是对我问题的正确回答。谢谢。
    • 不客气。顺便说一句,您可以通过将以下注释添加到您的 Grails 服务 @Transactional(rollbackFor = Throwable) 来对所有异常(选中和未选中)进行事务回滚
    • 感谢您的提示!不知道。
    • @Gregg 没问题,我一般把这个添加到我所有的服务中。
    【解决方案2】:

    与 Java 不同,您不必声明 Groovy 方法抛出的(已检查)异常,因为任何未声明的已检查异常都包装在 UndeclaredThrowableException 中。

    您似乎暗示 Groovy 用 UndeclaredThrowableException 包装了已检查的异常,但事实并非如此。如果 Grails 服务抛出未经检查的异常,该异常最终会被 UndeclaredThrowableException 包装,但这是一种 java.lang.reflection 机制,仅在涉及代理类时才会发生。

    发生这种情况是因为涉及到 Grails 服务。我不确定至少有一个代理类究竟涉及多少个代理类:一个执行事务处理的类(由 Spring 提供)。

    该类将使用事务包装 Service 中的任何方法,并在发生 RuntimeException 时回滚事务。默认情况下,Spring 事务管理在检查异常的情况下不会回滚。

    Java

    这在普通的旧 java 中是有意义的,因为开发人员会在应用程序代码中看到异常,并且会被警告要对其进行处理。如果开发人员很聪明,他将在事务范围内处理任何异常。如果他不回滚事务,他基本上是在说:“在这种情况下,事务提供的数据完整性对我来说并不重要。我会以其他方式从这个错误中恢复过来”

    时髦

    这在 Groovy 世界中没有意义,因为 Groovy 编译器不强制处理异常。它实际上以与 RuntimeExceptions 完全相同的方式处理异常。

    但是有一点需要注意:反射机制看到代理抛出的异常不在原始服务的方法签名中。这是可能的,因为:

    1. Groovy 不强制处理异常
    2. 代理方法总是可以抛出任何 Throwable(检查 InvocationHandler JavaDoc)

    由于使用的反射机制来自Java,所以必须符合Java规则。所以它必须将异常包装在 RuntimeException 中,.. 在这种情况下是 UndeclaredThrowableException。

    Grails

    现在它变得非常棘手,因为如果您从 Controller 调用 Service 方法并发生异常。您会看到一个 RuntimeException 冒泡(由于某些隐藏机制),但您的事务不会回滚(由于某些隐藏机制)。

    这种行为非常危险,因为开发人员确实必须记住正确处理任何异常(编译器不会帮助处理),或者开发人员必须确保正确指示任何服务使用@Transactional(rollbackFor = Throwable)。

    这是一个我认为 Grails 的开发人员在最初设计它时忽略的设计问题。但我认为默认行为是如此错误和如此危险,这真的应该改变。

    【讨论】:

      【解决方案3】:

      我认为解决问题的简单方法是在服务方法中添加throws CardException 语句,这样异常将不再被包裹在 UndeclaredThrowableException 中,控制器将捕获正确的异常类型。

      【讨论】:

        【解决方案4】:

        只需捕获 UndeclaredThrowableException,从中获取消息,然后在需要时重新抛出。

        catch (UndeclaredThrowableException e) {
            def s = e.getUndeclaredThrowable().getMessage()
            // do something with the message
            throw e
        }
        

        上面的代码片段只会捕获您在代码中明确抛出的异常(例如 CardException)。例如 NullPointerException 不会被捕获并冒泡。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-10-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-05
          • 2020-07-20
          相关资源
          最近更新 更多