Enterprise Library 异常处理应用程序块开发任务的细节
本文档维护在:http://wiki.entlib.net.cn/EntlibHelp31ExceptionHandlingApplicationBlock.ashx。
本主题讨论了计划异常处理策略的不同方面。它被分割成三个方面:
要构建成功的、灵活的、易于维护和支持的应用程序,必须为异常管理使用适当的策略。必须设置系统以确保它可以完成下列事件:
生成可被外部监控的事件以帮助系统操作
如果将时间花在设计清晰和一致的异常管理系统的开始,它将使你从不得不在开发期间拼凑系统中解放出来,或者同样糟糕的,从不得不让异常处理重新回到已存在的代码基础中解放出来。
异 常管理系统必须被很好的封装,并且必须将记录日志和报告的细节与应用程序的业务逻辑去耦。它还必须能够生成可被操作者监控的标准以提供对应用程序当前健康 和状态的了解。这有助于创建可以快速且精确的通知操作者任务发生的问题的应用程序。它还可以提供用于帮助开发人员和支持服务解决问题的有价值的信息。
异常处理过程和异常处理应用程序块
图 1 图示说明了应用程序将完成处理异常的基本步骤。
在应用程序代码中的每个方法和过程都将按照这个过程,以确保异常在适当的上下文环境中被处理,环境由当前方法的范围所定义。这个过程在异常顺着调用栈向上传播时会继续发生。
何时捕获异常
方法仅在它已执行下列活动的一个或多个时捕获异常:
如果特定的方法没有执行这些活动中的一个或多个,方法将不捕获异常。代替的是它将沿调用栈向上传播。它保持了代码干净和清楚,因为每个方法仅捕获了那些在特定方法范围内必须处理的异常,而允许所有其他异常继续传播。
异常传播
在此有三种主要的方法来传播异常:
使用这种方法,可以捕获异常。这让你作用于异常、清理或者执行在当前方法范围中需要的任何其他处理。如果没有恢复,将异常包装到一个新的异常中,然后抛出新的异常给调用者。Exception 类的 InnerException 属性显式的让你保留前一个捕获的异常。这允许原始异常被包装在一个或多个相关外部异常内做为内部异常。InnerException 属性在异常类的构造函数中设置。
在使用异常处理应用程序块时,通过使用配置的策略传播异常。关于配置策略的更多信息,请参见输入配置信息。
隐藏异常信息
对于暴露给外部公司和系统的 Web 服务,可能不想所有的异常信息都暴露给客户端。异常将抛出给客户端以让他们能处理,但可能不想让每个异常的细节都发送给公司的外部。可能想要抛出仅指出服务当前出现了问题的异常。
在 这种情况下,可以创建一个包含想传送的信息的通用应用程序异常。在 Web 服务器捕获未处理的异常时,将记录异常日志,完成任何必要的处理,然后构造一个通用应用程序异常的新实例。然后可以在通用应用程序异常中设置任何想要的信 息,并抛出它给客户端。这让你在 Web 服务中记录详细的日志信息,并抛出没有细节的异常给客户端。
在使用异常处理应用程序块时,使用替换处理程序隐藏异常消息。关于隐藏异常信息的更多信息,请参见替换异常的场景。
通知
通知是任何异常管理系统中的一个重要组件。日志在帮助你理解什么出错以及修正问题必须做什么中是重要的,但是通知中通知你的相关条件放在首位。没有正确的通知,异常会无法被检测。
由应用程序适配的通知处理必须与应用程序代码解耦。在修改通知机制时不用每次都修改代码,例如在修改接收者列表时。应用程序必须联系错误条件并依赖解耦的监控系统,以标识那些错误并采取适当的行动。
如果应用程序在使用监控系统的环境中将不运行,有几个选择可以从应用程序中创建通知。一种方法是,不得不与任何操作人员或者监控系统的开发人员紧密合作以定义监控和发送通知的正常过程。
在使用异常处理应用程块时,使用定制处理程序来通知用户。关于通知用户的更多信息,请参见通知用户场景。
用于异常处理的计划
在定义应用程序的架构后,将确定由应用程序产生的异常如何处理。策略将满足所有的安全、秘密和性能需求。下面是用于异常处理策略的通用方针:
通过使用异常策略,应用程序在响应异常所发生的行为中可以在不修改应用程序代码的情况下改变。
配置异常策略
可以使用配置控制台创建异常处理策略。异常策略有一个名称,并构造了一个被策略处理的异常类型的集合。每个异常类型都有一个顺序执行的处理程序的列表。
一 个应用程序可以有多个策略。它让应用程序的不同部分以不同的方式处理同样的异常类型。例如,可以是用于异常处理架构顶层的一个策略,它指定了将被定制的数 据异常类型包装的异常。可以是用于 Web 用户接口层的另一个策略,它指定了将被记录日志的异常,清理任何敏感或不必要的信息,然后显示给用户。
也可以使用配置控制台重命名和删除异常策略。应用程序代码用名称来引用策略。因此,如果删除或者重命名它们,确保应用程序引用了有效的策略名称。
配置异常类型
每 个异常策略都可以被配置为处理特定的异常类型。应用程序传递要处理的异常和将被用于处理异常的策略的名称到异常处理应用程序块。应用程序块查找策略以看是 否它已被配置为处理那种类型的异常。如果它找到一个匹配,应用程序块就执行一系列的异常处理程序。应用程序块尝试匹配异常类型到列出在配置文件中的尽可能 多的异常类型。如果没有找到一个匹配,应用程序块将查找异常类型的基类。
在这种方法中,应用程序可以被配置用于特定异常类型(例如,它可以用一个定制的应用程序异常类型包装或替换 System.SecurityException 异常类型)的特定行为,但是它可以有用于更多通用异常类型的不同活动(例如,它可以记录 System.Exception 异常类型的日志)。每个异常类型都可以被配置为指定用于异常类型的应用程序块将在异常处理程序链运行后执行的行为。
理解异常处理程序
使用配置控制台,可以配置必须为每个异常类型运行的处理程序(例如,记录日志信息、包装异常、或者替换异常的处理程序)。还可以为每个异常类型配置多个处理程序。它们按被列出在配置文件中的顺序运行。
在 链中的每个异常处理程序都接收当前的异常。对于第一个处理程序,这是原始异常。这是应用程序传递给应用程序块的异常。然后,在链中的任何处理程序都可以修 改异常(例如,通过用另一个异常包装或者替换它)。后面的处理程序接收从前一个处理程序返回的异常。如果特定的异常类型被配置为在运行最后一个处理程序后 抛出异常,将抛出从最后的处理程序中返回的异常。在为应用程序定义了异常处理策略后,可以在适当的位置修改应用程序代码以应用策略。更多信息,请参见发送 异常到异常处理应用程序块。
下面的代码展示了如何传递 DataAccessException 类型的异常到异常处理应用程序块。在示例中,它导致了一个名为 Data Access Policy 的策略被应用。代码检查由 HandleException 方法返回的值是 true 还是 false 。如果值是 true ,原始异常将被重新抛出。所有其他的异常类型都被传播回调用代码。
调试和异常传播
CLR 捕获没有被捕获块捕获的异常(这些异常被认为是没有处理的异常)。在使用 Visual Studio 调试器调试应用程序时,调试器在跟踪栈中未处理的异常发生的位置停止执行。这允许你检查局部变量并导航整个运行栈。
处理特定异常
尽可能多的,尝试使用特定的异常类型匹配捕获块。可取的是有一个通用捕获块重新抛出捕获块没有处理的异常类型。此方法达到了捕获块干净的目的,并且调用栈在调试器下的其他异常类型依然可用。
下列代码展示了如何处理 DataAccessException 类型的异常。在示例中,此异常导致了名为 Data Access Policy 的策略被应用。
用户过滤异常
Visual Basic 和托管 C++ 支持用户过滤的异常。用户过滤的异常处理程序基于异常类型之外的其他信息捕获并处理异常。附加信息允许你创建比仅使用异常类型的捕获块更精确的捕获块。
Visual Basic 用户过滤的异常处理程序使用 Catch 语句与 When 关键字。附加信息被包含在 When 子句中,意味着如果子句是 true ,捕获块仅处理它设计使用的特定异常。在 When 子句为 false 的情况下,不会进入捕获块。这意味着异常不会被捕获,并且在使用调试器时,调用栈是被禁止的。
这种技术在单个异常对象对应多个错误时非常有用。在这种情况下,异常对象通常有一个包含与错误相关的特定错误代码的属性。可以在表达式中使用错误代码属性选择仅在 Catch 语句中要处理的特殊错误。
下列 Visual Basic 示例说明了 Catch/When 语句。
要查看如何在托管 C++ 中使用用户过滤的异常,请参见 Using User-Filtered Exceptions。
对于更多的异常处理的建议,请参见 Best Practices for Exception Handling。
本主题讨论了计划异常处理策略的不同方面。它被分割成三个方面:
- 决定适当的异常策略和活动
- 指定基于异常类型和策略的不同的处理活动
- 发送异常到异常处理应用程序块
3.1 - 决定适当的异常策略和活动
此信息摘自异常管理架构指南。对于用于创建使用 .NET 技术的异常管理系统的完整设计和实现指南,请参见那篇文档。要构建成功的、灵活的、易于维护和支持的应用程序,必须为异常管理使用适当的策略。必须设置系统以确保它可以完成下列事件:
- 检测异常
- 记录日志并报告信息
生成可被外部监控的事件以帮助系统操作
如果将时间花在设计清晰和一致的异常管理系统的开始,它将使你从不得不在开发期间拼凑系统中解放出来,或者同样糟糕的,从不得不让异常处理重新回到已存在的代码基础中解放出来。
异 常管理系统必须被很好的封装,并且必须将记录日志和报告的细节与应用程序的业务逻辑去耦。它还必须能够生成可被操作者监控的标准以提供对应用程序当前健康 和状态的了解。这有助于创建可以快速且精确的通知操作者任务发生的问题的应用程序。它还可以提供用于帮助开发人员和支持服务解决问题的有价值的信息。
异常处理过程和异常处理应用程序块
图 1 图示说明了应用程序将完成处理异常的基本步骤。
图 1 :异常处理过程 |
在应用程序代码中的每个方法和过程都将按照这个过程,以确保异常在适当的上下文环境中被处理,环境由当前方法的范围所定义。这个过程在异常顺着调用栈向上传播时会继续发生。
何时捕获异常
方法仅在它已执行下列活动的一个或多个时捕获异常:
- 为记录日志收集信息。
- 添加任何相关信息到异常中。
- 执行清除代码。
- 尝试恢复。
如果特定的方法没有执行这些活动中的一个或多个,方法将不捕获异常。代替的是它将沿调用栈向上传播。它保持了代码干净和清楚,因为每个方法仅捕获了那些在特定方法范围内必须处理的异常,而允许所有其他异常继续传播。
异常传播
在此有三种主要的方法来传播异常:
- 让异常自动传播。使这种方法,你可以不用做任何事情,并且故意忽略异常。这导致控制立刻从当前代码沿调用栈向上移动,直到使用配置的异常类型被找到的过滤器的捕获块。
- 捕获并重新抛出异常。在此方法中,捕获并作用于异常,并在当前方法的范围内清理或者完成任何其他必要的处理。如果不从异常中恢复,将重新抛出同样的异常给调用者。
- 捕获、包装,然后抛出包装的异常。像异常沿调用栈向上传播,异常类型的相关性将变少。当异常被包装时,更多相关的异常将被返回给调用者。这被图示在图2中。
图 2 :传播异常 |
使用这种方法,可以捕获异常。这让你作用于异常、清理或者执行在当前方法范围中需要的任何其他处理。如果没有恢复,将异常包装到一个新的异常中,然后抛出新的异常给调用者。Exception 类的 InnerException 属性显式的让你保留前一个捕获的异常。这允许原始异常被包装在一个或多个相关外部异常内做为内部异常。InnerException 属性在异常类的构造函数中设置。
在使用异常处理应用程序块时,通过使用配置的策略传播异常。关于配置策略的更多信息,请参见输入配置信息。
隐藏异常信息
对于暴露给外部公司和系统的 Web 服务,可能不想所有的异常信息都暴露给客户端。异常将抛出给客户端以让他们能处理,但可能不想让每个异常的细节都发送给公司的外部。可能想要抛出仅指出服务当前出现了问题的异常。
在 这种情况下,可以创建一个包含想传送的信息的通用应用程序异常。在 Web 服务器捕获未处理的异常时,将记录异常日志,完成任何必要的处理,然后构造一个通用应用程序异常的新实例。然后可以在通用应用程序异常中设置任何想要的信 息,并抛出它给客户端。这让你在 Web 服务中记录详细的日志信息,并抛出没有细节的异常给客户端。
在使用异常处理应用程序块时,使用替换处理程序隐藏异常消息。关于隐藏异常信息的更多信息,请参见替换异常的场景。
通知
通知是任何异常管理系统中的一个重要组件。日志在帮助你理解什么出错以及修正问题必须做什么中是重要的,但是通知中通知你的相关条件放在首位。没有正确的通知,异常会无法被检测。
由应用程序适配的通知处理必须与应用程序代码解耦。在修改通知机制时不用每次都修改代码,例如在修改接收者列表时。应用程序必须联系错误条件并依赖解耦的监控系统,以标识那些错误并采取适当的行动。
如果应用程序在使用监控系统的环境中将不运行,有几个选择可以从应用程序中创建通知。一种方法是,不得不与任何操作人员或者监控系统的开发人员紧密合作以定义监控和发送通知的正常过程。
在使用异常处理应用程块时,使用定制处理程序来通知用户。关于通知用户的更多信息,请参见通知用户场景。
用于异常处理的计划
在定义应用程序的架构后,将确定由应用程序产生的异常如何处理。策略将满足所有的安全、秘密和性能需求。下面是用于异常处理策略的通用方针:
- 不要捕获异常,除非可以被添加某些值类型。换句话说,如果关于异常的知识对用户、对你、或者对应用程序都没有用,就不要捕获它。
- 如果要重新尝试操作,添加信息到异常、隐藏包含在异常中的敏感信息,或者显示格式化的信息,就捕获异常。
- 通常,仅在应用程序边界(如逻辑层的顶部、层间、服务的边界或者 UI 层)上处理异常。还有,用包含可以安全的在当前边界外被使用的信息的异常替换包含敏感信息的异常。
- 不要将敏感信息跨信任边界传播。这是标准的安全考虑,但这在处理异常信息时经常被忽视。做为代替的,用包含可以在当前边界之外可以被安全的使用的信息的新的异常替换包含敏感信息的异常。
- 尽可能准确的生成异常,将允许在异常触发时进行指定的行为。某些时候,这可能要编写使用所要求的属性的定制异常。
- 显示给用户的错误消息应该是相关的,并且提供了进行修正活动的建议。绝大多数情况下,显示给用户的错误信息将不包括敏感信息,如栈跟踪和服务器名称。
3.2 - 指定基于异常类型和策略的不同处理活动
异常处理应用程序块将异常如何处理(就是异常策略)的定义与使用应用程序块处理异常的应用程序代码分离。使用配置控制台创建和命名策略。通过使用异常策略,应用程序在响应异常所发生的行为中可以在不修改应用程序代码的情况下改变。
配置异常策略
可以使用配置控制台创建异常处理策略。异常策略有一个名称,并构造了一个被策略处理的异常类型的集合。每个异常类型都有一个顺序执行的处理程序的列表。
一 个应用程序可以有多个策略。它让应用程序的不同部分以不同的方式处理同样的异常类型。例如,可以是用于异常处理架构顶层的一个策略,它指定了将被定制的数 据异常类型包装的异常。可以是用于 Web 用户接口层的另一个策略,它指定了将被记录日志的异常,清理任何敏感或不必要的信息,然后显示给用户。
也可以使用配置控制台重命名和删除异常策略。应用程序代码用名称来引用策略。因此,如果删除或者重命名它们,确保应用程序引用了有效的策略名称。
配置异常类型
每 个异常策略都可以被配置为处理特定的异常类型。应用程序传递要处理的异常和将被用于处理异常的策略的名称到异常处理应用程序块。应用程序块查找策略以看是 否它已被配置为处理那种类型的异常。如果它找到一个匹配,应用程序块就执行一系列的异常处理程序。应用程序块尝试匹配异常类型到列出在配置文件中的尽可能 多的异常类型。如果没有找到一个匹配,应用程序块将查找异常类型的基类。
在这种方法中,应用程序可以被配置用于特定异常类型(例如,它可以用一个定制的应用程序异常类型包装或替换 System.SecurityException 异常类型)的特定行为,但是它可以有用于更多通用异常类型的不同活动(例如,它可以记录 System.Exception 异常类型的日志)。每个异常类型都可以被配置为指定用于异常类型的应用程序块将在异常处理程序链运行后执行的行为。
理解异常处理程序
使用配置控制台,可以配置必须为每个异常类型运行的处理程序(例如,记录日志信息、包装异常、或者替换异常的处理程序)。还可以为每个异常类型配置多个处理程序。它们按被列出在配置文件中的顺序运行。
在 链中的每个异常处理程序都接收当前的异常。对于第一个处理程序,这是原始异常。这是应用程序传递给应用程序块的异常。然后,在链中的任何处理程序都可以修 改异常(例如,通过用另一个异常包装或者替换它)。后面的处理程序接收从前一个处理程序返回的异常。如果特定的异常类型被配置为在运行最后一个处理程序后 抛出异常,将抛出从最后的处理程序中返回的异常。在为应用程序定义了异常处理策略后,可以在适当的位置修改应用程序代码以应用策略。更多信息,请参见发送 异常到异常处理应用程序块。
3.3 - 发送异常到异常处理应用程序块
应用程序代码和异常处理应用程序块的相互作用发生在应用程序捕获异常然后发送它到应用程序块以处理时。应用程序开发人员不需要知道异常将被如何处理,因为他们仅需要指定相关异常策略的名称。下面的代码展示了如何传递 DataAccessException 类型的异常到异常处理应用程序块。在示例中,它导致了一个名为 Data Access Policy 的策略被应用。代码检查由 HandleException 方法返回的值是 true 还是 false 。如果值是 true ,原始异常将被重新抛出。所有其他的异常类型都被传播回调用代码。
C#
try
{
// Run code.
}
catch(DataAccessException ex)
{
bool rethrow = ExceptionPolicy.HandleException(ex, "Data Access Policy");
if (rethrow)
{
throw;
}
}
Visual Basic
Try
' Run code.
Catch ex As DataAccessException
Dim rethrow As Boolean = ExceptionPolicy.HandleException(ex, "Data Access Policy")
If (rethrow) Then
Throw
End If
3.4 - 处理和抛出异常
在异常发生时,它沿栈向上传递,每个捕获块都有可能处理它。捕获语句的顺序是很重要的。将目标是特定异常的捕获块放在通用捕获块前。否则,编译器可能会报 告一个错误。通用语言运行时(CLR)通过将异常的类型与指定在捕获块中的异常类型名称来决定正确的捕获块。如果没有特定的捕获块,通用捕获块如果存在的 话,将处理异常。调试和异常传播
CLR 捕获没有被捕获块捕获的异常(这些异常被认为是没有处理的异常)。在使用 Visual Studio 调试器调试应用程序时,调试器在跟踪栈中未处理的异常发生的位置停止执行。这允许你检查局部变量并导航整个运行栈。
注意:应该避免编写除重新抛出原始异常外不做其他事的捕获块。在捕获块重新抛出异常时,在调用栈中重新抛出发生前的一些调试信息会丢失。例
如,使用迷你转储文件调试应用程序,文件包含包含在一个灾难转储文件中信息子集,或者如果在处理异常后附加调试器到一个进程,将无法询问如局部变量这样的
堆栈元素,它们被放置在重新抛出发生之前的调用栈中。只有在需要进行其他的一些活动时才捕获异常,或者做为替换的,重新抛出原始异常。
避免捕获捕获块不处理的异常,将编写专用于异常类型的捕获块。如果使用 Visual Basic 或者托管 C++ 进行开发,还可以使用用户过滤的异常。
避免捕获捕获块不处理的异常,将编写专用于异常类型的捕获块。如果使用 Visual Basic 或者托管 C++ 进行开发,还可以使用用户过滤的异常。
处理特定异常
尽可能多的,尝试使用特定的异常类型匹配捕获块。可取的是有一个通用捕获块重新抛出捕获块没有处理的异常类型。此方法达到了捕获块干净的目的,并且调用栈在调试器下的其他异常类型依然可用。
下列代码展示了如何处理 DataAccessException 类型的异常。在示例中,此异常导致了名为 Data Access Policy 的策略被应用。
C#
try
{
// Run code.
}
catch(DataAccessException ex)
{
bool rethrow = ExceptionPolicy.HandleException(ex, "Data Access Policy");
if (rethrow)
{
throw;
}
}
Visual Basic
Try
' Run code.
Catch ex As DataAccessException
Dim rethrow As Boolean = ExceptionPolicy.HandleException(ex, "Data Access Policy")
If (rethrow) Then
Throw
End If
用户过滤异常
Visual Basic 和托管 C++ 支持用户过滤的异常。用户过滤的异常处理程序基于异常类型之外的其他信息捕获并处理异常。附加信息允许你创建比仅使用异常类型的捕获块更精确的捕获块。
Visual Basic 用户过滤的异常处理程序使用 Catch 语句与 When 关键字。附加信息被包含在 When 子句中,意味着如果子句是 true ,捕获块仅处理它设计使用的特定异常。在 When 子句为 false 的情况下,不会进入捕获块。这意味着异常不会被捕获,并且在使用调试器时,调用栈是被禁止的。
这种技术在单个异常对象对应多个错误时非常有用。在这种情况下,异常对象通常有一个包含与错误相关的特定错误代码的属性。可以在表达式中使用错误代码属性选择仅在 Catch 语句中要处理的特殊错误。
下列 Visual Basic 示例说明了 Catch/When 语句。
Visual Basic
Try
' Execute code.
Catch ex As SqlException When ex.Number = 1204
Dim rethrow As Boolean = ExceptionPolicy.HandleException(ex, "Data Access Policy")
If (rethrow) Then
Throw
End If
End Try
要查看如何在托管 C++ 中使用用户过滤的异常,请参见 Using User-Filtered Exceptions。
对于更多的异常处理的建议,请参见 Best Practices for Exception Handling。