异常子句

异常子句高级用法

师成师成· 更新于 2026-09-28· 阅读 29 分钟· 0 次阅读

登录后可跨设备保存划线和私人笔记登录

异常子句 - 高级用法

返回异常子句

异常子句的高级用法

Camel 支持对异常子句进行高级配置。

使用全局和按路由的异常子句

你可以按以下方式定义异常子句:

  • 全局
  • 或针对特定路由

我们从一个会随时间不断修改的示例开始。首先,我们只使用全局异常子句:

纯 Java 方式:全局异常策略

// default should errors go to mock:error
errorHandler(deadLetterChannel("mock:error").redeliveryDelay(0));

// if a MyTechnicalException is thrown we will not try to
// redeliver and we mark it as handled
// so the caller does not get a failure
// since we have no to then the exchange will continue to be
// routed to the normal error handler
// destination that is mock:error as defined above
onException(MyTechnicalException.class).maximumRedeliveries(0).handled(true);

// if a MyFunctionalException is thrown we do not want Camel to
// redelivery but handle it our self using
// our bean myOwnHandler, then the exchange is not routed to the
// default error (mock:error)
onException(MyFunctionalException.class).maximumRedeliveries(0).handled(true).to("bean:myOwnHandler");

// here we route message to our service bean
from("direct:start").choice().when().xpath("//type = 'myType'").to("bean:myServiceBean").end().to("mock:result");

在下一个示例中,我们将全局异常策略改为完全针对特定路由。

针对特定路由的异常策略必须使用 .end()

[重要] 这要求以 .end() 结束 onException 路由,以指明它在何处终止,以及常规路由在何时继续执行。

仅 Java:使用 .end() 的针对特定路由的异常策略

// default should errors go to mock:error
errorHandler(deadLetterChannel("mock:error"));

// here we start the routing with the consumer
from("direct:start")

    // if a MyTechnicalException is thrown we will not try to
    // redeliver and we mark it as handled
    // so the caller does not get a failure
    // since we have no to then the exchange will continue to be
    // routed to the normal error handler
    // destination that is mock:error as defined above
    // we MUST use .end() to indicate that this sub block is
    // ended
    .onException(MyTechnicalException.class).maximumRedeliveries(0).handled(true).end()

    // if a MyFunctionalException is thrown we do not want Camel
    // to redelivery but handle it our self using
    // our bean myOwnHandler, then the exchange is not routed to
    // the default error (mock:error)
    // we MUST use .end() to indicate that this sub block is
    // ended
    .onException(MyFunctionalException.class).maximumRedeliveries(0).handled(true).to("bean:myOwnHandler").end()

    // here we have the regular routing
    .choice().when().xpath("//type = 'myType'").to("bean:myServiceBean").end().to("mock:result");

现在情况变得复杂了,因为在示例中引入第二条路由后,我们同时结合了全局异常策略与路由级异常策略:

仅 Java 方式:组合全局与路由级异常策略

// global error handler
// as its based on a unit test we do not have any delays between
// and do not log the stack trace
errorHandler(deadLetterChannel("mock:error").redeliveryDelay(0).logStackTrace(false));

// shared for both routes
onException(MyTechnicalException.class).handled(true).maximumRedeliveries(2).to("mock:tech.error");

from("direct:start")
    // route specific on exception for MyFunctionalException
    // we MUST use .end() to indicate that this sub block is
    // ended
    .onException(MyFunctionalException.class).maximumRedeliveries(0).end().to("bean:myServiceBean").to("mock:result");

from("direct:start2")
    // route specific on exception for MyFunctionalException
    // that is different than the previous route
    // here we marked it as handled and send it to a different
    // destination mock:handled
    // we MUST use .end() to indicate that this sub block is
    // ended
    .onException(MyFunctionalException.class).handled(true).maximumRedeliveries(0).to("mock:handled").end().to("bean:myServiceBean").to("mock:result");

注意,我们可以在两条路由中定义同一个异常 MyFunctionalException,但它们的配置不同,因此处理方式也会因路由而异。当然,你也可以为其中一条路由再添加一个新的 onException,使其拥有一条额外的异常策略。

最后,我们再加入一个嵌套的错误处理器作为收尾,即添加下面所示的第 3 条路由:

纯 Java 写法:带路由专属异常的嵌套错误处理器

from("direct:start3")
    // route specific error handler that is different than the
    // global error handler
    // here we do not redeliver and send errors to mock:error3
    // instead of the global endpoint
    .errorHandler(deadLetterChannel("mock:error3").maximumRedeliveries(0))

    // route specific on exception to mark MyFunctionalException
    // as being handled
    .onException(MyFunctionalException.class).handled(true).end()
    // however we want the IO exceptions to redeliver at most 3
    // times
    .onException(IOException.class).maximumRedeliveries(3).end().to("bean:myServiceBean").to("mock:result");

全局异常策略与嵌套错误处理器

上面同时包含嵌套错误处理器以及全局和按路由的异常子句的示例稍微有些复杂。需要明确的一点是:全局异常子句是真正的全局,因此它同样适用于嵌套错误处理器。所以如果抛出 MyTechnicalException,选中的将是全局异常策略。

使用 onWhen 谓词进行细粒度选择

你可以为异常子句附加一个 Expression,以便对某个子句是否应被选中进行细粒度控制。由于它是一个 Expression,你可以使用任意类型的代码来执行该判断。以下是示例:

  • Java
  • XML
  • YAML
errorHandler(deadLetterChannel("mock:error").redeliveryDelay(0).maximumRedeliveries(3));

// here we define our 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(1)
    // setting delay to zero is just to make unit testing faster
    .redeliveryDelay(0).to(ERROR_USER_QUEUE);

// here we define onException to catch MyUserException as a kind
// of fallback when the above did not match.
// Notice: 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)
    // setting delay to zero is just to make unit testing faster
    .redeliveryDelay(0).to(ERROR_QUEUE);
<onException>
    <exception>com.foo.MyUserException</exception>
    <onWhen>
        <simple>${header.user} != null</simple>
    </onWhen>
    <redeliveryPolicy maximumRedeliveries="1" redeliveryDelay="0"/>
    <to uri="mock:error"/>
</onException>

<onException>
    <exception>com.foo.MyUserException</exception>
    <redeliveryPolicy maximumRedeliveries="2" redeliveryDelay="0"/>
    <to uri="mock:error"/>
</onException>
- onException:
    exception:
      - com.foo.MyUserException
    redeliveryPolicy:
      maximumRedeliveries: 1
      redeliveryDelay: 0
    onWhen:
      simple: "${header.user} != null"
    steps:
      - to:
          uri: mock:error
- onException:
    exception:
      - com.foo.MyUserException
    redeliveryPolicy:
      maximumRedeliveries: 2
      redeliveryDelay: 0
    steps:
      - to:
          uri: mock:error

在上面的示例中,我们定义了两个 onException。第一个挂载了 onWhen 表达式,仅当消息中存在一个键为 user 且不为 null 的 header 时才触发。如果条件满足,就会选中该子句并处理抛出的异常。第二个子句用于粗粒度选择,即抛出相同异常、但表达式求值结果为 false 的情况。

这并非必需,如果省略第二个子句,则会启用默认的错误处理器。

使用 onRedelivery 处理器

死信通道支持 onRedelivery,允许在消息重新投递之前对其进行自定义处理。它可以用来添加一些自定义 header 或执行其他操作。在 Camel 2.0 中,我们也把这个特性加入到了 Exception Clause 中,因此你可以针对每个异常设置作用域级别的重新投递处理。如果 Exception Clause 上没有定义,Camel 会回退使用 死信通道上定义的(如果有)。有关 onRedelivery 的更多细节,请参阅死信通道。

在下面的代码中,我们希望在重新投递任何 IOException 之前执行一些自定义逻辑。因此我们为 IOException 配置一个 onException,并将其 onRedelivery 设置为我们自定义的处理器:

  • Java
  • XML
  • YAML
// when we redeliver caused by an IOException we want to do some
// special code before the redelivery attempt
onException(IOException.class)
    // try to redeliver at most 3 times
    .maximumRedeliveries(3)
    // setting delay to zero is just to make unit testing faster
    .redeliveryDelay(0).onRedelivery(new MyIORedeliverProcessor());

在 XML 中,您可以通过 onException 上配置的 onRedeliveryRef 来引用重投递流程。

<onException onRedeliveryRef="myIORedeliverProcessor">
    <exception>java.io.IOException</exception>
    <redeliveryPolicy maximumRedeliveries="3" redeliveryDelay="0"/>
</onException>

在 YAML 中,你可以通过配置在 onException 上的 onRedeliveryRef 来引用重新投递过程。

- onException:
    exception:
      - java.io.IOException
    onRedeliveryRef: myIORedeliverProcessor
    redeliveryPolicy:
      maximumRedeliveries: 3
      redeliveryDelay: 0

在我们的自定义处理器中,我们为消息设置了一个特殊的超时头部。当然,你可以在代码中做任何你想做的事情。

仅限 Java:自定义重投处理器

// This is our processor that is executed before every redelivery attempt
// here we can do what we want in the java code, such as altering the
// message
public static class MyRedeliverProcessor implements Processor {

    @Override
    public void process(Exchange exchange) throws Exception {
        // the message is being redelivered so we can alter it

        // we just append the redelivery counter to the body
        // you can of course do all kind of stuff instead
        String body = exchange.getIn().getBody(String.class);
        int count = exchange.getIn().getHeader("CamelRedeliveryCounter", Integer.class);

        exchange.getIn().setBody(body + count);
    }
}

使用 onExceptionOccurred 处理器

死信通道支持 onExceptionOccurred,允许在异常抛出之后立即对消息进行自定义处理。它可以用于执行一些自定义日志记录之类的操作。onRedelivery 处理器与 onExceptionOccurred 处理器的区别在于:前者在执行重新投递尝试之前才被处理,也就是说它不会在异常刚抛出时立即执行。例如,如果错误处理器被配置为在两次重新投递尝试之间延迟 5 秒,那么重新投递处理器将在异常抛出 5 秒后才被调用。另一方面,onExceptionOccurred 处理器总是在异常刚抛出时立即被调用,即使重新投递功能已被禁用也是如此。

从 onExceptionOccurred 处理器中抛出的任何新异常都会以 WARN 级别记录并被忽略,以免覆盖原有的异常。

在下面的代码中,我们希望在发生异常时执行一些自定义的日志记录。因此,我们配置一个 onExceptionOccurred 来使用我们的自定义处理器:

  • Java
  • XML
  • YAML
errorHandler(defaultErrorHandler()
    .maximumRedeliveries(3)
    .redeliveryDelay(5000)
    .onExceptionOccurred(myProcessor));
<errorHandler>
    <defaultErrorHandler onExceptionOccurredRef="myProcessor">
        <redeliveryPolicy maximumRedeliveries="3" redeliveryDelay="5000"/>
    </defaultErrorHandler>
</errorHandler>
- errorHandler:
    defaultErrorHandler:
      onExceptionOccurredRef: myProcessor
      redeliveryPolicy:
        maximumRedeliveries: 3
        redeliveryDelay: 5000

使用 retryWhile 谓词进行细粒度重试

当你需要对是否重试某个 Exchange 进行细粒度控制时,可以使用 retryWhile 谓词。Camel 会持续重新投递,直到该谓词返回 false 为止。

示例:

仅 Java:使用 retryWhile 谓词进行细粒度重试

// we want to use a predicate for retries so we can determine in
// our bean when retry should stop, notice it will overrule the global
// error handler where we defined at most 1 redelivery attempt. Here we will
// continue until the predicate returns false
onException(MyFunctionalException.class).retryWhile(method("myRetryHandler")).handled(true).transform().constant("Sorry");

其中由 Bean myRetryHandler 来判断是否需要重试:

仅限 Java:使用 Bean 绑定的重试处理器 Bean

public class MyRetryBean {

    // using bean binding we can bind the information from the exchange to
    // the types we have in our method signature
    public boolean retry(@Header(Exchange.REDELIVERY_COUNTER) Integer counter) {
        // NOTE: counter is the redelivery attempt, will start from 1
        // we can of course do what ever we want to determine the result but
        // this is a unit test so we end after 3 attempts
        return counter < 3;
    }
}

使用自定义 ExceptionPolicyStrategy

Camel 中默认的 org.apache.camel.processor.errorhandler.ExceptionPolicyStrategy 在几乎所有场景下都已足够。不过,如果你确实需要使用自己的实现(仅适用于少数高级场景),可以参照下面的示例进行配置:

仅 Java:配置自定义 ExceptionPolicyStrategy

// configure the error handler to use my policy instead of the default from Camel
errorHandler(deadLetterChannel("mock:error").exceptionPolicyStrategy(new MyPolicy()));

通过自定义策略 MyPolicy,我们可以用自己的代码替换 Camel 的默认行为,从而决定上方列出的哪一种异常类型应当处理当前抛出的异常。

仅限 Java:自定义 ExceptionPolicyStrategy 实现

public static class MyPolicy implements ExceptionPolicyStrategy {

    @Override
    public ExceptionPolicyKey getExceptionPolicy(Set<ExceptionPolicyKey> exceptionPolicies, Exchange exchange, Throwable exception) {
        // This is just an example that always forces the exception type configured
        // with MyPolicyException to win.
        return new ExceptionPolicyKey(null, MyPolicyException.class, null);
    }
}

评论

登录后参与评论

正在加载评论…