13

在我的 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 是一个异常,而不是 RuntimeException。我不确定这是否重要。

4

4 回答 4

15

与 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) {

}
于 2013-11-14T21:21:24.410 回答
9

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

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

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

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

爪哇

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

时髦的

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

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

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

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

圣杯

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

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

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

于 2014-08-21T13:47:55.350 回答
2

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

于 2014-10-31T09:13:51.133 回答
0

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

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

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

于 2017-01-19T22:04:47.193 回答