这是用户在 2024-3-28 21:42 为 https://camel.apache.org/manual/exception-clause.html 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

 例外条款


您可以使用 Java DSL 中的异常条款(Exception Clause),使用 onException() 方法按异常类型指定所需的错误处理。在深入探讨其工作原理之前,我们先提供一个快速示例,以便您快速入门。


例如,如果您想在出现特定异常时执行特定处理,您可以通过

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");


在此,如果 seda:inputAseda:inputB 的处理导致抛出 ValidationException (例如由于验证组件的 XSD 验证),则消息将被发送到 activemq:validationFailed 队列。


您可以为不同的行为定义多个 onException 子句:

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

onException(ShipOrderException.class)
    .to("activemq:shipFailed");

from("seda:order")
    .to("bean:processOrder");

 标准


例外条款的作用域为


  • 全局(对于 Java DSL,即每个 RouteBuilder 实例,可重复使用,见下文注释)

  •  或特定路线


其中全球部分最为简单易懂。在高级部分,我们将深入研究特定路线,甚至将它们结合起来。但是


Java DSL 的全局作用域是每个 RouteBuilder 实例,因此如果要在多个 RouteBuilder 类中共享,则需要创建一个基础抽象 RouteBuilder 类,并将错误处理逻辑放在其 configure 方法中。然后扩展该类,并确保类中 super.configure() .我们只是在使用 Java 的继承技术。


camel 如何选择哪个子句来处理给定的抛出异常?


Camel 使用 DefaultExceptionPolicyStrategy 来确定由哪个 onException 子句处理异常的策略。策略如下


  • 的顺序优先。骆驼将从定义的第一个......最后一个开始测试。


  • Camel 将从底层(嵌套引起的)开始,在异常层次结构中向上递归,找到第一个匹配的 onException 子句。


  • instanceof 测试用于用 onException 子句定义的异常列表测试给定异常。总是使用完全匹配的 instanceof 子句,否则将选择具有被抛出异常的closets super 的异常的 onException 子句(在异常层次结构中向上递归)。


一个特例最能说明这一点:

onException(IOException.class)
    .maximumRedeliveries(3);

onException(OrderFailedException.class)
    .maximumRedeliveries(2);


在上面的示例中,我们定义了两个异常,其中 IOException 排在第一位,因此如果有匹配的异常,Camel 就会选择该异常。然后再选择更一般的 IOException


因此,如果在这种层次结构下出现异常:

+ RuntimeCamelException (wrapper exception by Camel)
    + OrderFailedException
        + IOException
            + FileNotFoundException


然后,Camel 将按照以下顺序尝试测试异常: FileNotFoundException , IOExceptionOrderFailedExceptionRuntimeCamelException 。.由于我们定义了 onException(IOException.class) 骆驼会选择它,因为它是最接近的匹配项。


如果我们用 FileNotFoundException 添加第三个 onException 子句

onException(IOException.class)
    .maximumRedeliveries(3);

onException(OrderFailedException.class)
    .maximumRedeliveries(2);

onException(FileNotFoundException.class)
    .handled(true)
    .to("log:nofile");


然后,在前面的示例中,Camel 将使用最后一个 onException(FileNotFoundException.class) 作为精确匹配。由于是完全匹配,因此它将覆盖之前用于处理同一异常的一般 IOException


如果抛出的是这个异常,就会出现新的情况:

+ RuntimeCamelException (wrapper exception by Camel)
    + OrderFailedException
        + OrderNotFoundException


然后就会选择 onException(OrderFailedException.class) --这一点也不奇怪。


最后一个示例演示了 instanceof 测试方面,如果异常是 onException 子句中定义的异常的实例,Camel就会选择该异常。示例如下

+ RuntimeCamelException (wrapper exception by Camel)
    + SocketException


由于 SocketException 是一个 instanceof IOException 子句,Camel 将选择 onException(IOException.class) 子句。,Camel 将选择 onException(IOException.class) 子句。


配置再投递策略(再投递选项)


RedeliveryPolicy 要求使用死信通道作为错误处理程序。死信通道支持在将消息发送到死信端点之前多次尝试重新交付消息交换。请参阅 "死信通道",了解有关重新交付和哪些重新交付选项的更多信息。


例外情况下默认不重新交付


默认情况下,任何例外条款都不会重新发送!(因为它会将 maximumRedeliveries 选项设置为 0)。


有时,您需要根据异常类型配置重新交付策略。在顶部示例中,默认情况下,如果出现 org.apache.camel.ValidationException ,则不会重新交付邮件;但如果出现其他异常,例如 IOException 或其他异常,则会根据死信通道的设置重试路由。


不过,如果您想自定义 RedeliveryPolicy 对象的任何方法,可以通过 Fluent API 来实现。因此,在 org.apache.camel.ValidationException 的情况下,让我们重试两次,直到 org.apache.camel.ValidationException 为止。

 Java DSL:

onException(ValidationException.class)
    .maximumRedeliveries(2);

 XML DSL:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <redeliveryPolicy maximumRedeliveries="2"/>
</onException>


您可以自定义任何 RedeliveryPolicy,例如,我们可以设置不同的 5000 毫秒延迟:

<onException>
    <exception>com.mycompany.ValidationException</exception>
    <redeliveryPolicy maximumRedeliveries="2" delay="5000"/>
</onException>


重新交付尝试的入口点


所有重新发送的尝试都从故障点开始。因此,路由

onException(ConnectException.class)
    .from("direct:start")
    .process("processor1")
    .process("processor2") // <--- throws a ConnectException
    .to("mock:theEnd")


将从 processor2 重试- 而非完整路径。


重复使用重新交付政策


您可以引用 RedeliveryPolicy ,这样就可以重用现有配置,并使用支持属性占位符的标准 Spring Bean 风格配置。

<bean id="myRedeliveryPolicy" class="org.apache.camel.processor.RedeliveryPolicy">
    <property name="maximumRedeliveries" value="${myprop.max}"/>
</bean>

<!-- here we reference our redelivery policy defined above -->
<onException redeliveryPolicyRef="myRedeliveryPolicy">
    <!-- you can define multiple exceptions just adding more exception elements as show below -->
    <exception>com.mycompany.MyFirstException</exception>
    <exception>com.mycompany.MySecondException</exception>
</onException>


异步延迟再交付


Camel 有一项功能,在等待延迟重新交付发生时不会阻塞。不过,如果使用事务路由,Camel 就会阻塞,因为事务管理器要求在同一线程上下文中执行所有工作。您可以使用 asyncDelayedRedelivery 选项启用非阻塞异步行为。该选项可在 errorHandleronException 或 JavaScript 中设置。、 onException 或重新交付策略中设置。


默认情况下,错误处理程序将创建并使用一个计划线程池,以便在未来触发重新交付。你也可以配置错误处理程序上的 executorServiceRef 以指示对共享线程池的引用,你可以在注册表中加入共享线程池,或者在你希望控制线程池设置时加入线程池配置文件。


捕获多个异常


如图所示,可以捕获多个异常:

onException(MyBusinessException.class, MyOtherBusinessException.class)
    .maximumRedeliveries(2)
    .to("activemq:businessFailed");


在 XML DSL 中,您只需添加另一个异常元素:

<onException>
    <exception>com.mycompany.MyBusinessException</exception>
    <exception>com.mycompany.MyOtherBusinessException</exception>
    <redeliveryPolicy maximumRedeliveries="2"/>
    <to uri="activemq:businessFailed"/>
</onException>


使用处理器作为故障处理程序


我们希望以特定方式处理某些异常,因此要为特定异常添加一个 onException 子句。

// here we register exception cause for MyFunctionException
// when this exception occurs we want it to be processed by our
// processor
onException(MyFunctionalException.class)
  .process(new MyFunctionFailureHandler())
  .stop();


因此,每当 MyFunctionalException 被抛出,它就会被传送到我们的处理器 MyFunctionFailureHandler 。.因此可以说,当处理过程中出现 MyFunctionalException 时,交换就会被转移。这一点非常重要,因为它是完全有效的。死信通道的默认重新交付策略不会生效,因此我们的处理器会直接接收交换,而不会尝试任何重新交付。在我们的处理器中,我们需要确定如何处理。Camel 将 Exchange 视为失败处理。因此,我们的处理器是路由的终点。让我们看看处理器的代码。

    public static class MyFunctionFailureHandler implements Processor {

        @Override
        public void process(Exchange exchange) throws Exception {
            // the caused by exception is stored in a property on the exchange
            Throwable caused = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Throwable.class);
            assertNotNull(caused);
            // here you can do what you want, but Camel regards this exception as
            // handled, and this processor as a failure handler, so it won't do redeliveries.
            // So this is the end of this route.
        }
    }


请注意,我们是如何通过 Exchange 上的一个属性来获取由异常引起的信息的。在处理过程中,Camel 会将捕获到的异常存储在这里。因此,你可以获取该属性并检查异常信息,然后做你想做的事。


将异常标记为已处理


另请参阅下面的 "处理和继续异常 "部分。


在 Camel 中,使用 onException 处理已知异常是一项非常强大的功能。您可以使用句柄 DSL 将异常标记为已处理,这样调用者就不会收到导致异常的响应。句柄是一个谓词,通过重载可以接受三种类型的参数:

  •  布尔型

  •  谓词


  • 使用此规则集作为谓词进行评估的表达式:如果表达式返回布尔值,则直接使用。对于其他任何回复,如果回复为 not null 则视为 true ..


例如,要将所有 ValidationException 标记为已处理,我们可以这样做:

onException(ValidationException)
    .handled(true);

 使用处理的示例


在下面的路由中,我们要对所有 OrderFailedException 进行特殊处理,因为我们要向调用者返回定制的响应。首先,我们将路由设置为

    // we do special error handling for when OrderFailedException is
    // thrown
    onException(OrderFailedException.class)
        // we mark the exchange as handled so the caller doesn't
        // receive the
        // OrderFailedException but whatever we want to return
        // instead
        .handled(true)
        // this bean handles the error handling where we can
        // customize the error
        // response using java code
        .bean(OrderService.class, "orderFailed")
        // and since this is an unit test we use mocks for testing
        .to("mock:error");

    // this is just the generic error handler where we set the
    // destination
    // and the number of redeliveries we want to try
    errorHandler(deadLetterChannel("mock:error").maximumRedeliveries(1));

    // this is our route where we handle orders
    from("direct:start")
        // this bean is our order service
        .bean(OrderService.class, "handleOrder")
        // this is the destination if the order is OK
        .to("mock:result");


然后是我们的服务 Bean,它只是一个普通的 POJO,展示了如何在 Camel 中使用 Bean Integration 来避免与 Camel API 绑定:

    /**
     * Order service as a plain POJO class
     */
    public static class OrderService {

        /**
         * This method handle our order input and return the order
         */
        public Object handleOrder(@Headers Map headers, @Body String payload) throws OrderFailedException {
            headers.put("customerid", headers.get("customerid"));
            if ("Order: kaboom".equals(payload)) {
                throw new OrderFailedException("Cannot order: kaboom");
            } else {
                headers.put("orderid", "123");
                return "Order OK";
            }
        }

        /**
         * This method creates the response to the caller if the order could not
         * be processed
         */
        public Object orderFailed(@Headers Map headers, @Body String payload) {
            headers.put("customerid", headers.get("customerid"));
            headers.put("orderid", "failed");
            return "Order ERROR";
        }
    }


最后,抛出的异常只是一个普通异常:

    public static class OrderFailedException extends Exception {

        private static final long serialVersionUID = 1L;

        public OrderFailedException(String message) {
            super(message);
        }

    }

 那么会发生什么呢?


如果我们发送的订单处理正常,那么呼叫方将收到一个 Exchange 作为回复,其中包含 Order OK 作为有效载荷和 orderid=123 作为标头。


如果订单无法处理,从而抛出了一个 OrderFailedException 异常,那么调用者将不会收到该异常,而是收到我们在 OrderService 中的 orderFailed 方法中创建的自定义响应。.因此,调用者将收到一个包含有效载荷 Order ERROR 和头部 orderid=failed 的 Exchange。


使用处理过的 Spring XML DSL


与上述 Spring XML DSL 中的路径相同:

 <!-- setup our error handler as the deal letter channel -->
<bean id="errorHandler" class="org.apache.camel.builder.DeadLetterChannelBuilder">
    <property name="deadLetterUri" value="mock:error"/>
</bean>

<!-- this is our POJO bean with our business logic defined as a plain spring bean -->
<bean id="orderService" class="org.apache.camel.spring.processor.onexception.OrderService" />

<!-- this is the camel context where we define the routes -->
<!-- define our error handler as a global error handler -->
<camelContext errorHandlerRef="errorHandler" xmlns="http://camel.apache.org/schema/spring">

  <onException>
    <!-- the exception is full qualified names as plain strings -->
    <!-- there can be more just add a 2nd, 3rd exception element (unbounded) -->
    <exception>org.apache.camel.spring.processor.onexception.OrderFailedException</exception>
    <!-- we can set the redelivery policy here as well -->
    <redeliveryPolicy maximumRedeliveries="1" />
    <!-- mark this as handled -->
    <handled>
      <constant>true</constant>
    </handled>
    <!-- let our order service handle this exception, call the orderFailed method -->
    <bean ref="orderService" method="orderFailed" />
    <!-- and since this is a unit test we use mock for assertions -->
    <to uri="mock:error" />
  </onException>

  <route>
    <!-- the route -->
    <from uri="direct:start" />
    <!-- in the normal route then route to our order service and call handleOrder method -->
    <bean ref="orderService" method="handleOrder" />
    <!-- and since this is a unit test we use mock for assertions -->
    <to uri="mock:result" />
  </route>

</camelContext>


处理并向客户端发送固定回复


在上述路由中,我们处理了异常,但将其路由到了不同的端点。如果需要更改响应并将固定的响应发送回原始调用者(客户端),该怎么办呢?这没有什么秘密,只需像在普通 Camel 路由中一样,使用转换来设置响应,如下示例所示:

// 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");


我们对示例稍作修改,以返回最初导致的异常信息,而不是固定文本 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 and return the exception message
onException(MyFunctionalException.class)
  .handled(true)
  .transform(exceptionMessage());


我们还可以使用简单语言来设置可读的错误信息,并将其与引起的异常信息结合起来:

// 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.");


处理和继续异常


使用选项 continued 可以在原始路径中同时设置 handlecontinue 路由,就像没有发生异常情况一样。


例如:当 IDontCareException 被抛出时,如果要忽略并继续,我们可以这样做:

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


也许您可以将 continued 与在每一步周围添加 try …​ catch 块进行比较,然后忽略异常。在 Camel 中,使用 continued 会更容易,否则就必须使用 Try Catch Finally 样式来处理这种用例。

 继续使用的示例


在下面的路由中,我们要对所有 IllegalArgumentException 进行特殊处理,因为我们只想继续路由。

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

from("direct:start")
  .to("mock:start")
  .throwException(new IllegalArgumentException("Forced"))
  .to("mock:result");


在 Spring XML DSL 中也有同样的例子:

 <camelContext xmlns="http://camel.apache.org/schema/spring">

        <onException>
            <exception>java.lang.IllegalArgumentException</exception>
            <!-- tell Camel to handle and continue when this exception was thrown -->
            <continued><constant>true</constant></continued>
        </onException>

        <route>
            <from uri="direct:start"/>
            <to uri="mock:start"/>
            <throwException message="Forced" exceptionType="java.lang.IllegalArgumentException"/>
            <to uri="mock:result"/>
        </route>

    </camelContext>


处理 "和 "继续 "有什么区别?


如果 "处理 "为 "true",则抛出的异常将被处理,Camel 不会继续沿原路由路由,而是中断路由。不过,您可以在 onException 中配置一个路由,它将被替代使用。如果您需要创建一些自定义的响应消息返回给调用者,或因异常抛出而进行其他处理,则可以使用此路由。


如果 continued 为 true,那么 Camel 会捕捉到异常,并忽略该异常,继续在原路由中进行路由。但是,如果您在 onException 中配置了路由,它将首先路由该路由,然后才会继续路由原始路由。

 使用 useOriginalMessage


选项 useOriginalMessage 用于路由原始输入报文,而不是路由过程中可能被修改的当前报文。


例如:如果您有这样一条路线:

from("jms:queue:order:input")
    .to("bean:validateOrder");
    .to("bean:transformOrder")
    .to("bean:handleOrder");


路由会监听 JMS 消息,并对其进行验证、转换和处理。在此过程中,Exchange 有效载荷会被转换/修改。因此,如果出了差错,我们想将信息转移到另一个 JMS 目的地,那么我们可以添加 onException .但是,当我们将 Exchange 转移到该目的地时,我们并不知道报文处于哪种状态。错误发生在 transformOrder 之前还是之后?因此,为了确保万无一失,我们需要移动从 jms:queue:order:input 收到的原始输入报文。.因此,我们可以启用 useOriginalMessage 选项,如下图所示:

// will use original input message (body and headers)
onException(MyOrderException.class)
    .useOriginalMessage()
    .handled(true)
    .to("jms:queue:order:failed");


那么路由到 jms:queue:order:failed 的报文就是原始输入。如果我们想手动重试,可以将 JMS 消息从失败队列移至输入队列,这不会有任何问题,因为该消息与我们收到的原始消息是一样的。

  useOriginalMessage 使用 Spring DSL


在 Spring DSL 中, useOriginalMessage 选项被定义为 <onException> 的布尔属性。XML 标签上的布尔属性。因此,上述定义如下

<onException useOriginalMessage="true">
    <exception>com.mycompany.MyOrderException</exception>
    <handled><constant>true</constant></handled>
    <to uri="jms:queue:order:failed"/>
</onException>


原信息边界


原始输入指的是当前工作单元范围内的输入信息。一个工作单元通常跨越一条路由,如果使用内部端点(如 direct 或 seda)连接,则跨越多条路由。当信息通过 JMS 或 HTTP 等外部端点传递时,消费者将创建一个新的工作单元,并将收到的信息作为原始输入。此外,一些 EIP 模式(如 splitter、multicast)会为其子路由中的消息(即 split 消息)创建一个新的工作单元边界;不过,这些 EIP 有一个名为 shareUnitOfWork 的选项,允许在错误处理方面与父工作单元相结合,因此可以使用父原始消息。

 使用 useOriginalBody


useOriginalBody 与上述的 useOriginalMessage 类似。如果您希望在发送到错误处理程序或死信通道之前,用自定义报文头丰富报文内容并保留原始报文正文,则可能需要使用 useOriginalBody。


例如:如果您有这样一条路线:

// will use original input body
onException(MyOrderException.class)
    .useOriginalBody()
    .handled(true)
    .to("jms:queue:order:failed");

from("jms:queue:order:input")
    .setHeader("application", constant("OrderApp"))
    .to("bean:validateOrder");
    .to("bean:transformOrder")
    .to("bean:handleOrder");


然后,在 JMS 端点接收到原始报文后,用名为 "应用程序 "的报文头丰富报文内容。如果出现错误, onException 将处理异常,并按原样使用原始报文正文和当前报文的标头,这意味着标头将包括应用程序标头。


例外条款的高级用法


Camel 支持对例外条款进行高级配置。


使用全局和每个路由例外条款


您可以将异常子句定义为

  •  全球

  •  或特定路线


我们从随时间变化的样本开始。首先,我们只使用全局例外条款:

// 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() 路由特定例外策略


[重要提示] 这就要求在 onException 路线的末尾加上 .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");


现在,当我们在样本中引入第二条路由时,将全局异常策略和路由特定异常策略结合起来,情况就变得复杂了:

// 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 个路由:

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 进行精细选择谓词


您可以将表达式附加到异常子句,以便对子句是否应被选中进行精细控制。由于这是一个表达式,因此可以使用任何类型的代码来执行测试。下面是一个示例:

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 定义。第一个子句附加了一个 onWhen 表达式,只有当报文头部的关键字 user 不为空时才会触发。如果有,则选择该子句并处理抛出的异常。第二个子句是一个 for 粗糙增益选择,用于选择正在抛出的相同异常,但当表达式求值为 false 时。


这不是必需的,如果省略第二个子句,则默认错误处理程序将启动。


使用再交付处理器


死信通道支持 onRedelivery ,允许在重新发送之前对信息进行自定义处理。它可用于添加客户标题或其他内容。在 Camel 2.0 中,我们将此功能也添加到了异常条款(Exception Clause)中,因此您可以在重新交付时使用每个异常范围。如果例外条款中不存在死信通道,Camel 将使用死信通道中定义的死信通道。有关 onRedelivery 的更多详情,请参见死信通道。.


在下面的代码中,我们希望在重新交付任何 IOException 之前执行一些自定义代码。.因此,我们为 IOException 配置了一个 onException ,并将 onRedelivery 设置为使用自定义处理器:

// when we redeliver caused by an IOException we want to do some
// special code before the redeliver 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());


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

// 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);
    }
}


在 spring xml dsl 中使用 onredelivery


在 Spring DSL 中,您需要使用 onRedeliveryRef 属性来引用 Spring Bean id,即您的自定义处理器:

<onException onRedeliveryRef="myIORedeliverProcessor">
    <exception>java.io.IOException</exception>
</onException>


而我们的处理器只是一个普通的 Spring Bean(我们使用 $ 作为内部类,因为本代码基于单元测试):

 <bean id="myRedeliveryProcessor"
          class="org.apache.camel.processor.DeadLetterChannelOnExceptionOnRedeliveryTest$MyRedeliverProcessor"/>


使用发生异常处理程序


死信通道支持 onExceptionOccurred ,允许在异常抛出后对消息进行自定义处理。它可用于进行自定义日志记录或其他操作。 onRedelivery 处理器和 onExceptionOccurred 处理器的区别在于,前者是在尝试重新交付之前进行处理,这意味着它不会在异常抛出后立即发生。例如,如果错误处理程序被配置为在尝试重新交付之间延迟 5 秒,那么重新交付处理器就会在异常抛出 5 秒后被调用。另一方面, onExceptionOccurred 处理器总是在异常抛出后立即调用,如果重发已被禁用,也是如此。


onExceptionOccurred 处理器抛出的任何新异常都会被记录为 WARN 并忽略,以避免覆盖现有异常。


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

errorHandler(defaultErrorHandler()
    .maximumRedeliveries(3)
    .redeliveryDelay(5000)
    .onExceptionOccurred(myProcessor));


在 spring xml dsl 中使用 onredelivery


在 Spring DSL 中,您需要使用 onExceptionOccurredRef 属性来引用 Spring Bean id,即您的自定义处理器:

<bean id="myProcessor" class="com.foo.MyExceptionLoggingProcessor"/>

<camelContext errorHandlerRef="eh" xmlns="http://camel.apache.org/schema/spring">
    <errorHandler id="eh" type="DefaultErrorHandler" onExceptionOccurredRef="myProcessor">
        <redeliveryPolicy maximumRedeliveries="3" redeliveryDelay="5000"/>
    </errorHandler>
    ...
</camelContext>


使用细粒度重试,使用重试while谓词


当您需要精细控制以确定是否应重试交换时,可以使用 retryWhile 谓词。在谓词返回 false 之前,Camel 会重新发送。

 例如

// 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");


其中,豆子 myRetryHandler 正在计算我们是否应该重试:

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


使用自定义异常政策策略


Camel 中的默认异常策略(ExceptionPolicyStrategy)在几乎所有用例中都足够了。不过,如果需要使用自己的异常策略(仅用于罕见的高级用例),可以按下面的示例进行配置:

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


我们可以使用自己的策略 MyPolicy 来改变 Camel 的默认行为,用我们自己的代码来解析上述哪种异常类型应该处理给定的抛出异常。

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);
    }
}


在 Spring XML DSL 中使用异常子句


您也可以在 Spring XML DSL 中使用上述所有异常子句功能。下面是几个示例:

  •  全球范围

<!-- setup our error handler as the deal letter channel -->
<bean id="errorHandler" class="org.apache.camel.builder.DeadLetterChannelBuilder">
    <property name="deadLetterUri" value="mock:error"/>
</bean>

<!-- this is our POJO bean with our business logic defined as a plain spring bean -->
<bean id="orderService" class="org.apache.camel.spring.processor.onexception.OrderService" />

<!-- this is the camel context where we define the routes -->
<!-- define our error handler as a global error handler -->
<camelContext errorHandlerRef="errorHandler" xmlns="http://camel.apache.org/schema/spring">

  <onException>
    <!-- the exception is full qualified names as plain strings -->
    <!-- there can be more just add a 2nd, 3rd exception element (unbounded) -->
    <exception>org.apache.camel.spring.processor.onexception.OrderFailedException</exception>
    <!-- we can set the redelivery policy here as well -->
    <redeliveryPolicy maximumRedeliveries="1" />
    <!-- mark this as handled -->
    <handled>
      <constant>true</constant>
    </handled>
    <!-- let our order service handle this exception, call the orderFailed method -->
    <bean ref="orderService" method="orderFailed" />
    <!-- and since this is a unit test we use mock for assertions -->
    <to uri="mock:error" />
  </onException>

  <route>
    <!-- the route -->
    <from uri="direct:start" />
    <!-- in the normal route then route to our order service and call handleOrder method -->
    <bean ref="orderService" method="handleOrder" />
    <!-- and since this is a unit test we use mock for assertions -->
    <to uri="mock:result" />
  </route>

</camelContext>
  •  路由特定范围

<!-- setup our error handler as the deal letter channel -->
<bean id="deadLetter" class="org.apache.camel.builder.DeadLetterChannelBuilder">
    <property name="deadLetterUri" value="mock:dead"/>
</bean>

<!-- the default error handler used in the 2nd route -->
<bean id="defaultErrorHandler" class="org.apache.camel.builder.DefaultErrorHandlerBuilder"/>

<!-- this is our POJO bean with our business logic defined as a plain spring bean -->
<bean id="orderService" class="org.apache.camel.spring.processor.onexception.OrderService"/>

<!-- this is the camel context where we define the routes -->
<camelContext xmlns="http://camel.apache.org/schema/spring">

    <route errorHandlerRef="deadLetter">
        <from uri="direct:start"/>
        <onException>
            <exception>org.apache.camel.spring.processor.onexception.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>

    <!-- The exception clause specified in the first route will not be used in this route -->
    <route errorHandlerRef="defaultErrorHandler">
        <from uri="direct:start_with_no_handler"/>
        <bean ref="orderService" method="handleOrder"/>
        <to uri="mock:result"/>
    </route>

</camelContext>