2.3. 异常处理


摘要

Apache Camel 提供了几种不同的机制,它可让您以不同的粒度级别处理异常:您可以使用 doTrydoCatch 来处理路由内的异常,并最终 执行;或者,您可以指定每个异常类型执行哪些操作,并将这些规则应用于使用 Exception 的 RouteBuilder 中所有路由中的所有路由 ; 或者,您可以指定 所有 异常类型执行的操作,并使用 错误处理程序 将这一规则应用于 RouteBuilder 中的所有路由。

有关异常处理的详情,请参考 第 6.3 节 “死信频道”

2.3.1. onException Clause

概述

onException 子句是捕获异常的一种强大的机制,可在一个或多个路由中发生:它特定于类型,使您能够定义不同的操作来处理不同的异常类型;它允许您定义使用相同(实际上,稍有的扩展)语法的操作,作为路由,从而在您处理异常情况下,以方便您处理异常的方式;它基于一个捕获模型,这基于一个结果。

使用 onException 的 Trapping 异常

onException 子句是捕获异常的机制。也就是说,一旦定义了 onException 子句,它将陷阱在路由中的任何点上发生异常。这与捕获异常的 Java 试用/分散机制不同,只有在将特定的代码片段 明确 包含在 try 块中时。

当您定义 onException 子句时,Apache Camel 运行时将每个路由节点隐式包含在 try 块中时,会出现什么情况。这就是为什么 onException 子句可以在路由中的任何时间点捕捉异常。但此换行可自动为您完成;路由定义中不可见。

Java DSL 示例

在以下 Java DSL 示例中,onException 子句应用到 RouteBuilder 类中定义的所有路由。如果在处理其中任一路由(来自"seda:inputA")来自("seda:inputB")时出现 ValidationException 异常,则 onException 子句将当前的交换重定向到 verify Failed JMS 队列(充当死信队列)。

// Java
public class MyRouteBuilder extends RouteBuilder {

  public void configure() {
    onException(ValidationException.class)
      .to("activemq:validationFailed");

    from("seda:inputA")
      .to("validation:foo/bar.xsd", "activemq:someQueue");

    from("seda:inputB").to("direct:foo")
      .to("rnc:mySchema.rnc", "activemq:anotherQueue");
  }
}

XML DSL 示例

前面的示例也可以在 XML DSL 中表达,使用 onException 元素来定义 exception 子句,如下所示:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:camel="http://camel.apache.org/schema/spring"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

    <camelContext xmlns="http://camel.apache.org/schema/spring">
        <onException>
            <exception>com.mycompany.ValidationException</exception>
            <to uri="activemq:validationFailed"/>
        </onException>
        <route>
            <from uri="seda:inputA"/>
            <to uri="validation:foo/bar.xsd"/>
            <to uri="activemq:someQueue"/>
        </route>
        <route>
            <from uri="seda:inputB"/>
            <to uri="rnc:mySchema.rnc"/>
            <to uri="activemq:anotherQueue"/>
        </route>
    </camelContext>

</beans>

Trapping 多个例外

您可以定义多个 onException 子句,以在 RouteBuilder 范围内捕获异常。这可让您采取不同的操作来响应不同的例外。例如,Java DSL 中定义的以下一系列 onException 子句定义了 ValidationExceptionIOExceptionException 的不同死信目的地:

onException(ValidationException.class).to("activemq:validationFailed");
onException(java.io.IOException.class).to("activemq:ioExceptions");
onException(Exception.class).to("activemq:exceptions");

您可以在 XML DSL 中定义相同系列的 onException 子句,如下所示:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <to uri="activemq:validationFailed"/>
</onException>
<onException>
    <exception>java.io.IOException</exception>
    <to uri="activemq:ioExceptions"/>
</onException>
<onException>
    <exception>java.lang.Exception</exception>
    <to uri="activemq:exceptions"/>
</onException>

您也可以将多个例外分组为由相同的 onException 子句进行捕获。在 Java DSL 中,您可以对多个例外进行分组,如下所示:

onException(ValidationException.class, BuesinessException.class)
  .to("activemq:validationFailed");

在 XML DSL 中,您可以通过定义 onException 元素中的多个 异常 元素来组合多个例外,如下所示:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <exception>com.mycompany.BuesinessException</exception>
    <to uri="activemq:validationFailed"/>
</onException>

当捕获多个异常时,在 Exception 子句的次序上 非常重要。Apache Camel 最初会尝试将引发异常与 first 子句匹配。如果第一个子句无法匹配,则尝试下一个 onException 子句,直到找到匹配项为止。每个匹配尝试都会受以下算法的管控:

  1. 如果引发异常是 链的异常 (即,捕获异常并作为不同的异常),则最常见的异常类型最初是匹配的基础。这个例外测试如下:

    1. 如果 exception-to-test 正好在 onException 子句中指定的类型(使用 instanceof测试),则会触发匹配项。
    2. 如果 exception-to-test 是 onException 子句中指定的类型的子类型,则会触发匹配项。
  2. 如果最嵌套的异常无法获得匹配项,则将测试链(嵌套异常)中的下一个异常。测试会继续链,直到触发匹配或链用尽。
注意

通过 throwException EIP,您可以从简单语言表达式创建新异常实例。您可以根据当前交换中的可用信息使其动态使用。例如,

<throwException exceptionType="java.lang.IllegalArgumentException" message="${body}"/>

字母频道

目前,在使用 onException 的基本示例都利用了 死信频道 模式。也就是说,当 onException 子句捕获异常时,当前交换将路由到特殊目的地( deadletter 频道)。deadletter 频道充当未处理的失败消息的 存放 区域。管理员可以稍后检查消息,并决定需要采取什么操作。

有关死信频道模式的详情,请参考 第 6.3 节 “死信频道”

使用原始消息

当路由期间出现异常时,交换中的消息可能已被大量修改(甚至不能被人读取)。通常,管理员更容易决定要执行哪些正确性操作,如果死信队列中可见的消息是 原始消息,如路由开始时收到的消息。useOriginalMessage 选项默认为 false,但如果在错误处理程序上配置,则会自动启用。

注意

使用OriginalMessage 选项可导致应用到 Camel 路由时出现意外行为,该路由将消息发送到多个端点,或将消息拆分为部分。原始消息可能无法在多广播、斯plitter 或 RecipientList 路由中保留,其中中间处理步骤会修改原始消息。

在 Java DSL 中,您可以将交换中的消息替换为原始消息。将 setAllowUseOriginalMessage () 设置为 true,然后使用 useOriginalMessage () DSL 命令,如下所示:

onException(ValidationException.class)
  .useOriginalMessage()
  .to("activemq:validationFailed");

在 XML DSL 中,您可以通过设置 onException 元素上的 useOriginalMessage 属性来检索原始消息,如下所示:

<onException useOriginalMessage="true">
    <exception>com.mycompany.ValidationException</exception>
    <to uri="activemq:validationFailed"/>
</onException>
注意

如果 setAllowUseOriginalMessage () 选项被设置为 true,Camel 会在路由开始时复制原始消息,以确保在 使用OriginalMessage () 时原始消息可用。但是,如果在 Camel 上下文上将 setAllowUseOriginalMessage () 选项设为 false (这是默认设置 ),则原始消息将无法访问,您无法调用 useOriginalMessage ()

利用默认行为的一个原因是,在处理大型消息时优化性能。

在 2.18 之前的 Camel 版本中,allowUseOriginalMessage 的默认设置是 true。

重新传送策略

Apache Camel 为您提供尝试 除发生异常的情况,而不是中断消息处理,并在出现异常时立即放弃信息。在网络系统中,超时发生且临时故障发生时,通常可能会成功处理失败的消息,如果在引发原始异常后马上进行恢复。

Apache Camel 重新发送支持在异常发生后保存消息的各种策略。配置重新传送的一些最重要的选项如下:

maximumRedeliveries()
指定可尝试重新传送的次数(默认为 0)。负值意味着始终尝试重新发送(等同于无限值)。
retryWhile()

指定谓词( Predicate )类型,它决定 Apache Camel 是否存在继续恢复。如果 predicate 在当前交换上评估为 true,则尝试重新发送;否则,重新传送将停止,且不会进行进一步重新发送尝试。

这个选项优先于 maximumRedeliveries () 选项。

在 Java DSL 中,重新传送策略选项使用 onException 子句中的 DSL 命令来指定。例如,您可以指定最多 6 个回收,之后该交换会发送到 validationFailed deadletter 队列,如下所示:

onException(ValidationException.class)
  .maximumRedeliveries(6)
  .retryAttemptedLogLevel(org.apache.camel.LogginLevel.WARN)
  .to("activemq:validationFailed");

在 XML DSL 中,通过设置 redeliveryPolicy 元素上的属性来指定重新传送策略选项。例如,前面的路由可以在 XML DSL 中表示,如下所示:

<onException useOriginalMessage="true">
    <exception>com.mycompany.ValidationException</exception>
    <redeliveryPolicy maximumRedeliveries="6"/>
    <to uri="activemq:validationFailed"/>
</onException>

重新传送选项的后,redelivery 选项的后后,才会被处理,直到最后一次重新传送尝试失败后才会被处理。有关所有重新传送选项的详情,请参考 第 6.3 节 “死信频道”

另外,您可以在 redeliveryPolicyProfile 实例中指定重新传送策略选项。然后,您可以使用 onException 元素的 redeliverPolicyRef 属性引用 redeliveryPolicyProfile 实例。例如,前面的路由可以表达如下:

<redeliveryPolicyProfile id="redelivPolicy" maximumRedeliveries="6" retryAttemptedLogLevel="WARN"/>

<onException useOriginalMessage="true" redeliveryPolicyRef="redelivPolicy">
    <exception>com.mycompany.ValidationException</exception>
    <to uri="activemq:validationFailed"/>
</onException>
注意

如果要使用多个 onException 子句中的重新传送策略,则使用 redeliveryPolicyProfile 的方法很有用。

条件 trapping

通过指定 onWhen 选项,可以对Exception 进行异常捕获。如果您在 onException 子句中指定 onWhen 选项,则仅在引发异常与 子句匹配时触发匹配,而 onWhen predicate 在当前交换上评估为 true

例如,在以下 Java DSL 片段中,第一个 onException 子句触发器(仅当引发异常与 MyUserException 匹配并且当前交换 中的用户 标头为非null)时:

// Java

// Here we define onException() to catch MyUserException when
// there is a header[user] on the exchange that is not null
onException(MyUserException.class)
    .onWhen(header("user").isNotNull())
    .maximumRedeliveries(2)
    .to(ERROR_USER_QUEUE);

// Here we define onException to catch MyUserException as a kind
// of fallback when the above did not match.
// Noitce: The order how we have defined these onException is
// important as Camel will resolve in the same order as they
// have been defined
onException(MyUserException.class)
    .maximumRedeliveries(2)
    .to(ERROR_QUEUE);

前面的 onException 子句可以在 XML DSL 中表示,如下所示:

<redeliveryPolicyProfile id="twoRedeliveries" maximumRedeliveries="2"/>

<onException redeliveryPolicyRef="twoRedeliveries">
    <exception>com.mycompany.MyUserException</exception>
    <onWhen>
        <simple>${header.user} != null</simple>
    </onWhen>
    <to uri="activemq:error_user_queue"/>
</onException>

<onException redeliveryPolicyRef="twoRedeliveries">
    <exception>com.mycompany.MyUserException</exception>
    <to uri="activemq:error_queue"/>
</onException>

处理异常

默认情况下,当在路由期间引发异常时,会中断当前交换的处理,并在路由开始时将引发异常传播到消费者端点。触发了 onException 子句时,其行为基本上是相同的,但 onException 子句在请求引发异常前执行一些处理。

但这种 默认行为是 处理异常的唯一方法。onException 提供了各种选项来修改异常处理行为,如下所示:

  • 抑制异常 rethrow cnf-you 选项,您可以选择在 Exception 子句后禁止重新增长异常。换句话说,在这种情况下,异常 不会 传播到路由开始时的使用者端点。
  • 继续处理 WWN-you 选项可以从最初发生异常时恢复正常处理交换。隐式,这种方法也会阻止重新浏览异常。
  • 如果路由开始时的消费者端点需要回复(即使用 InOut MEP),您可能更倾向于构建自定义故障回复消息,而不是传播异常到消费者端点。???

抑制异常重新箭头

要防止当前异常重新浏览并传播到消费者端点,您可以在 Java DSL 中将 handled () 选项设置为 true,如下所示:

onException(ValidationException.class)
  .handled(true)
  .to("activemq:validationFailed");

在 Java DSL 中,ded () 选项的参数可以是布尔值类型、predicate 类型或 Expression 类型(其中任何非布尔值表达式被解释为 true,如果它被评估为非空值)。

同一路由可以配置为禁止使用 处理 元素的 XML DSL 中的 rerown 异常,如下所示:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <handled>
        <constant>true</constant>
    </handled>
    <to uri="activemq:validationFailed"/>
</onException>

继续处理

要从最初引发异常的路由中处理当前消息,您可以在 Java DSL 中将 continued 选项设为 true,如下所示:

onException(ValidationException.class)
  .continued(true);

在 Java DSL 中,continued () 选项的参数可以是布尔值类型、predicate 类型或 Expression 类型(其中任何非布尔值表达式被解释为 true,如果它被评估为非空值)。

同一路由可以在 XML DSL 中配置,使用 继续 元素,如下所示:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <continued>
        <constant>true</constant>
    </continued>
</onException>

发送响应

当启动路由的消费者端点需要回复时,您可能更愿意构建自定义故障回复消息,而不是简单地将引发异常传播回消费者。在这种情况下,您需要执行两个基本步骤: 禁止 rethrown exception 使用 handled 选项;并使用自定义故障消息填充交换的 Out 消息插槽。

例如,以下 Java DSL 片段演示了如何在出现 MyFunctionalException 异常时发送包含文本字符串 Sorry 的回复消息:

// we catch MyFunctionalException and want to mark it as handled (= no failure returned to client)
// but we want to return a fixed text response, so we transform OUT body as Sorry.
onException(MyFunctionalException.class)
    .handled(true)
    .transform().constant("Sorry");

如果您要向客户端发送错误响应,通常希望在响应中纳入异常消息的文本。您可以使用 exceptionMessage () builder 方法访问当前异常消息的文本。例如,您可以在发生 MyFunctionalException 异常时发送包含异常消息文本的回复,如下所示:

// we catch MyFunctionalException and want to mark it as handled (= no failure returned to client)
// but we want to return a fixed text response, so we transform OUT body and return the exception message
onException(MyFunctionalException.class)
    .handled(true)
    .transform(exceptionMessage());

例外消息文本也可以通过 exception.message 变量从 Simple 语言访问。例如,您可以在回复消息中嵌入当前的异常文本,如下所示:

// we catch MyFunctionalException and want to mark it as handled (= no failure returned to client)
// but we want to return a fixed text response, so we transform OUT body and return a nice message
// using the simple language where we want insert the exception message
onException(MyFunctionalException.class)
    .handled(true)
    .transform().simple("Error reported: ${exception.message} - cannot process this message.");

前面的 onException 子句可以在 XML DSL 中表示,如下所示:

<onException>
    <exception>com.mycompany.MyFunctionalException</exception>
    <handled>
        <constant>true</constant>
    </handled>
    <transform>
        <simple>Error reported: ${exception.message} - cannot process this message.</simple>
    </transform>
</onException>

处理异常时的异常

在处理现有异常时(换句话说,一个在处理 onException 子句的过程中会引发异常)会以特殊方式处理。这种异常由特殊回退异常处理程序处理,它处理异常,如下所示:

  • 所有现有的异常处理程序将被忽略,并立即处理失败。
  • 已记录新的异常。
  • 在 Exchange 对象上设置了新的异常。

简单策略避免了复杂的故障情景,否则可能会出现 对Exception 子句被锁定在无限循环中。

范围

onException 子句可在以下任一范围内有效:

  • RouteBuilder 范围 TOKEN- onException 子句定义为 RouteBuilder.configure () 方法内的独立声明,会影响该 RouteBuilder 实例中定义的所有路由。另一方面,这些 onException 子句 对任何其他 RouteBuilder 实例中定义的路由没有影响。在路由定义之前,必须 出现 onException 子句。

    所有指向此点的示例都使用 RouteBuilder 范围来定义。

  • 路由范围 TOKEN- onException 子句也可以直接嵌入到路由中。这些 onException 子句 仅影响 其中定义的路由。

路由范围

您可以在路由定义的任意位置嵌入 onException 子句,但您必须使用 end () DSL 命令终止内嵌 onException 子句。

例如,您可以在 Java DSL 中定义嵌入式 onException 子句,如下所示:

// Java
from("direct:start")
  .onException(OrderFailedException.class)
    .maximumRedeliveries(1)
    .handled(true)
    .beanRef("orderService", "orderFailed")
    .to("mock:error")
  .end()
  .beanRef("orderService", "handleOrder")
  .to("mock:result");

您可以在 XML DSL 中定义嵌入式 onException 子句,如下所示:

<route errorHandlerRef="deadLetter">
    <from uri="direct:start"/>
    <onException>
        <exception>com.mycompany.OrderFailedException</exception>
        <redeliveryPolicy maximumRedeliveries="1"/>
        <handled>
            <constant>true</constant>
        </handled>
        <bean ref="orderService" method="orderFailed"/>
        <to uri="mock:error"/>
    </onException>
    <bean ref="orderService" method="handleOrder"/>
    <to uri="mock:result"/>
</route>
Red Hat logoGithubRedditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

© 2024 Red Hat, Inc.