2.3. 异常处理
摘要
Apache Camel 提供了几种不同的机制,它可让您以不同的粒度级别处理异常:您可以使用 doTry
、doCatch
来处理路由内的异常,并最终
执行;或者,您可以指定每个异常类型执行哪些操作,并将这些规则应用于使用 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
子句定义了 ValidationException
、IOException
和 Exception
的不同死信目的地:
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
子句,直到找到匹配项为止。每个匹配尝试都会受以下算法的管控:
如果引发异常是 链的异常 (即,捕获异常并作为不同的异常),则最常见的异常类型最初是匹配的基础。这个例外测试如下:
-
如果 exception-to-test 正好在
onException
子句中指定的类型(使用instanceof
测试),则会触发匹配项。 -
如果 exception-to-test 是
onException
子句中指定的类型的子类型,则会触发匹配项。
-
如果 exception-to-test 正好在
- 如果最嵌套的异常无法获得匹配项,则将测试链(嵌套异常)中的下一个异常。测试会继续链,直到触发匹配或链用尽。
通过 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
选项,可以对ExceptiononException
子句中指定 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>