【问题标题】:Using exceptions in a simple database-driven Java web application在简单的数据库驱动的 Java Web 应用程序中使用异常
【发布时间】:2012-05-14 19:53:20
【问题描述】:

我有一个关于中型 Java Web 应用程序异常的问题。 有一个使用 JDBC 实现的数据访问层,其逻辑主要集中在一个 servlet 中(UI 是 JSP)。像这样的应用程序的常规异常层次结构是什么?

我应该在数据访问层捕获异常并重新抛出另一个异常(例如 DataAccessException)还是让最高级别处理它们(servlet)。

此外,我有一个在数据访问层中调用的连接池,它有自己的异常类型。这些异常应该在数据访问层内部捕获并作为DataAccessException重新抛出,还是应该由更高级别直接处理?

让一个主应用程序异常与 2 个子异常:LogicException 和 TechnicalException 一起是个好主意。 Logic 将具有类似于 AuthentificationFailedException 等的子类,而 TechnicalExceptions 将负责传达有关失败的信息,例如数据访问层异常、FileNotFound(应该是)等等?

谢谢!

【问题讨论】:

  • 这类问题有标签吗?关于如何大规模构建代码的问题?我发现这个主题非常有趣,非常需要一个好的论坛。

标签: java exception web-applications exception-handling dao


【解决方案1】:

通常我用更高级别、更有意义的异常来包装较低级别的异常。这通常需要更多的工作,但可以让您在层之间解耦。

假设我正在编写一个恰好从数据库读取的配置子系统。如果我不换行,我会有类似的东西:

public String getConfigurationProperty(String name) throws SQLException {
    // Try to read from my configuration table
}

如果我做包装,我会

public String getConfigurationProperty(String name) throws ConfigurationException {
    try {
        // Try to read from my configuration table 
    } catch (SQLException ex) {
        ConfigurationException wrapper = // Some subclass of ConfigurationException that wraps ex
        throw wrapper;
    }
}

这肯定是更多的工作。优点是,如果以后我想将配置后端更改为基于文件的后端,没有包装器,我的方法将变为

public String getConfigurationProperty(String name) throws IOException {
    // Try to read from my configuration file
}

然后我将不得不更改我的所有客户端代码以处理IOExceptions 而不是SQLExceptions。如果您进行包装,您只需要更改后端,因为您的客户端代码已经使用ConfigurationExceptions 及其子类编写。请注意,这与您使用受检异常还是未受检异常无关:如果您想进行异常处理,您几乎总是需要至少知道要处理的异常类型的一些近似值。

现在,这就是我倾向于做的事情。有人认为无论如何都无法正确处理大多数异常,并且大多数情况下所有这些包装都是无稽之谈。

public String getConfigurationProperty(String name) throws ConfigurationException {
    try {
        // Try to read from my configuration file 
    } catch (IOException ex) {
        ConfigurationException wrapper = // Some subclass of ConfigurationException that wraps ex
        throw wrapper;
    }
}

【讨论】:

    【解决方案2】:

    在应用程序中,我看到所有异常都在抛出它们的层中被捕获,包装在更通用的异常中(在您的情况下为 DataAccessException),然后以这种方式重新抛出。 这是因为通常引发异常的层没有足够的上下文信息,因此无法决定如何以适当的方式处理错误。处理这些异常的最佳位置是位于引发异常的层的正上方的层:它有足够的信息来“优雅地失败”,而不会让该异常在堆栈跟踪上走得太远。 在您的情况下,在 servlet 中捕获异常就可以了。

    但异常的层次结构取决于您正在处理的应用程序。您的逻辑技术部门可能是一个不错的选择……也可能不是。 :) 在“异常处理”中没有“正确”的选择,而且这是一个过于复杂的论点,无法在单个问答对中处理。 :)

    【讨论】:

      【解决方案3】:

      通常,在应用程序的任何给定层中,您都希望捕获的异常对该层有意义。因此,您不想在 Servlet(或 MVC 控制器)中捕获 SybaseException,因为您可能有一个 DAO 层来隐藏该级别的实现细节。

      几个一般提示:

      • 您应该配置您的 apache+servlet 容器以返回一个 500 错误上的合理页面。
      • 您的控制器应该使用用户友好的方式包装大多数错误 信息。捕获的异常应该提供足够的信息 控制器制定合理的信息。
      • 我会考虑将 RuntimeException 子类化为基础应用程序 异常,其中包含技术错误消息,以及 本地化的用户错误消息。
      • 当你发现异常时,想想你想做什么。设计您的例外,以便您可以直接执行所需的操作。

      最后,并不是每个人都会同意这一点,但是你可以提出一个强有力的理由来让你的所有异常都是 RuntimeException 的子类,这样它们就不会被检查。我已经在许多采用这种方法的大型代码库上工作过,它通常工作得很好,并且减少了很多 catch-rethrow 样板代码。仅仅因为 java 提供了检查异常,并不意味着你必须使用它们。这显然是我的观点,但重要的是要意识到并不是每个人都使用检查异常,事实上,许多代码库会明确避免它们。

      【讨论】:

        【解决方案4】:

        我还参与了中型 Web 应用程序的开发。我们使用的方法是将异常从数据库层抛出到处理逻辑的 servlet,该逻辑捕获它们并向用户生成正确的错误消息。

        这是....不是去 IMO 的路。 原因:整个 servlet 现在都依赖于实现。假设我们想要切换实现(我们现在想要)。我们现在需要更改服务集其余部分中的每个捕获,因为新实现不会抛出相同的异常。通用异常是正确的选择。

        不仅例外,业务对象也应该是通用的。最好是接口。这将允许您更轻松地更改实现,而您的 locig 处理代码将在您的 servlet 整个生命周期中依赖于相同的对象。

        【讨论】:

        • 处理由异常处理创建的依赖关系的一种方法是“直接超类处理程序”:您可以创建一个父类,比如说“DataAccessException”,这将是您将捕获的异常在你的 try-catch 块中。但是您的图层将抛出该异常的直接子类,例如“DataAccessImpl_1Exception”。以这种方式处理异常,并且依赖绑定更加松散:您可以实现另一个“DataAccessImpl_2Exception”并且仍然捕获您的异常......当然有优点和缺点...... :)
        • 是的,但这只有在我编写抛出的异常时才有可能。如果我使用第三方库,我不能让他们的例外扩展我的例外。
        • 是的,但是您可以捕获所有这些异常并将其包装在“中间层”中,然后重新抛出您想要的任何异常。只需看看 gpeche 发布的示例。显然您将不得不编写更多代码,但这些代码通常已经足够解耦以允许您以简单的方式“实现切换”,如果您认为这些“切换”迟早会发生,那么值得付出努力。跨度>
        猜你喜欢
        • 1970-01-01
        • 2012-03-02
        • 2018-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-11
        相关资源
        最近更新 更多