2.3. 异常处理

摘要

Apache Camel 提供了几种不同的机制,您可以在不同的粒度级别处理异常:您可以使用 doTrydoCatch最后执行 来处理路由中的异常异常,也可以使用 Exception 来指定将这个规则应用到 RouteBuilder 中的所有路由。 或者,您可以指定 所有 异常类型要执行的操作,并使用 errorHandler 将这个规则应用到 RouteBuilder 中的所有路由。

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

2.3.1. onException Clause

概述

onException 子句是一种在一个或多个路由中发生陷入异常的强大机制:它特定于类型,允许您定义处理不同异常类型的不同操作;它允许您定义使用相同(实际、稍扩展)语法作为路由作为路由的操作,您可以以处理异常的方式提供了相当的灵活性;它基于陷入模型,从而让出现异常情况的出现。

使用 onException 的 Trapping 例外

onException 子句 是用于捕获 异常的机制。即,当您定义 onException 子句后,它会在路由的任意时间点捕获异常。这与 Java try/catch 机制相反,只有在一个异常被发现时,才会在试块中 明确 包含特定的代码片段。

当您定义 onException 子句时实际上会出现什么情况,即 Apache Camel 运行时会隐式将每个路由节点包含在 try 块中。这就是为什么 onException 子句可以在路由中的任何点捕获异常。但是,这个嵌套式是自动进行的,它无法在路由定义中可见。

Java DSL 示例

在以下 Java DSL 示例中,在Exception 子句中,将应用到 RouteBuilder 类中定义的所有路由。如果在处理任何路由(从("seda:inputA")或 ("seda:inputB") )时发生 ValidationException 异常,而 onException 子句会捕获异常并将当前交换重定向到 验证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 多例外

您可以在 Exception 子定义多个 来陷入 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 中定义与 Exception 子句相同的系列,如下所示:

<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 子句的引发异常。如果 first 子句无法匹配,则尝试 Exception 子句上的 下一个,直到找到匹配项为止。尝试的每个匹配都受到以下算法的约束:

  1. 如果抛出的异常是 链的异常 (即,一个异常已捕获并将其除以不同异常),则最嵌套的异常类型最初充当匹配的基础。这个例外被测试,如下所示:

    1. 如果 exception-to-test 具有 在Exception 子句(使用 实例测试)中指定的类型,则将触发匹配项。
    2. 如果 exception-to-test 是 onException 子句中指定的类型子类型,则会触发匹配项。
  2. 如果最嵌套的例外无法产生匹配项,则通过测试链中的下一个异常。测试会继续进行链,直到触发匹配项或链用尽为止。
注意

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

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

Deadletter 频道

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

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

使用原始消息

在路由中引发异常的时间,交换中的消息可能会显著修改(而且可能被人类读取)。通常,如果死信队列中可见的消息是 原始消息,管理员更容易决定要采取的正确操作。useOriginalMessage 选项默认为 false,但如果它是在错误处理程序上配置,则会自动启用。

注意

在应用到将消息发送到多个端点的 Camel 路由时,useOriginalMessage 选项可能会导致意外行为。原始消息可能不会保留在多播、Splitter 或 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 会在路由开始时制作原始消息的副本,这样可确保当您调用 useOriginalMessage() 时,它会保证原始消息可用。但是,如果 Camel 上下文上的 setAllowUseOriginalMessage() 选项被设置为 false (这是默认值),则不会 访问原始消息,您无法调用 useOriginalMessage()

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

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

重新传送策略

Apache Camel 允许您在出现异常时立即中断消息处理并立即放弃,Apache Camel 为您提供在发生异常情况时尝试 出消息的选项。在网络系统中,超时可能会出现并临时故障时,在原始异常出现后马上被处理失败的消息通常会被成功处理。

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

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

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

此选项优先于 maximumRedeliveries() 选项。

在 Java DSL 中,使用 onException 子句中的 DSL 命令来指定重新传送策略选项。例如,您可以指定最大 6 个红色大小,然后该交换将发送到 验证Failed 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>

在重新传送选项后,重新传送选项的后一部分在最后一次重新传送尝试失败后才会被处理。有关所有重新传送选项的详情,请参考 第 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 的方法很有用。

条件捕获

通过指定 onWhen 选项,可以进行带有 onException 时出现异常捕获的问题。如果您在 onException 子句中指定 onWhen 选项,则仅在引发异常与 子句匹配时才会触发匹配项,并在 当前交换上评估为 true

例如,在以下 Java DSL 片段中,first onException 子句触发,只有在引发n 异常与 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);

Exception 子句的前一个可以在 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 提供了各种选项来修改异常处理行为,如下所示:

  • 抑制异常 rethtion rethrow -abrtyou 在 onException 子句完成后可以阻止 rethrown 异常。换句话说,在这种情况下,异常 不会在 路由开始时传播到消费者端点。
  • 继续处理 时间为:包含从最初发生异常的点恢复正常处理交换的选择。隐式,这种方法也会阻止循环异常。
  • 在路由开头的消费者端点时发送一个响应 (即使用 InOut MEP),您可以更愿意构建自定义错误回复消息,而不是将异常请求回消费者端点。

抑制异常行

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

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

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

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

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

继续处理

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

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

在 Java DSL 中,continue() 选项的参数可以是布尔值类型、Predicate 类型或 Expression 类型(其中任何非布尔值的表达式)解释为 true

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

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

发送响应

当启动路由的消费者端点需要回复时,您可能更愿意构建自定义错误回复消息,而不是直接让引发异常传播到消费者。在这种情况下,您需要执行两个重要步骤: 使用 处理 的选项禁止放减异常;然后,使用自定义错误消息填充交换的 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.");

Exception 子句的前一个 可以在 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 对象上设置新的例外。

简单策略可避免复杂的故障场景,否则可能会以 a onException 子句结束,使锁定为死循环。

范围

onException 子句可在以下任何范围中有效:

  • RouteBuilder 范围 InventoryService- onException 子句定义为 RouteBuilder.configure() 方法内的单机语句,它们会影响该 RouteBuilder 实例中定义的所有路由。另一方面,这些 onException 子句 对任何其他 RouteBuilder 实例中定义的路由没有影响onException 子句 必须在 路由定义之前显示。

    所有最多的示例都是使用 RouteBuilder 范围来定义的。

  • 路由范围 InventoryService- onException 子句也可以直接嵌入路由内。这些 onException 子句 仅影响 定义它们的路由。

路由范围

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

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

// 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 中定义嵌入 Exception 子句,如下所示:

<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>