概述

Apache Camel 是一个强大的开源集成框架,实现了众多已知的企业集成模式

在使用Camel进行消息路由时,通常需要有效处理错误。为此,Camel提供了几种处理异常的方法。

在这篇教程中,我们将探讨两种在我们的Camel应用程序内部使用的异常处理策略。

依赖项

要开始,我们只需要将camel-spring-boot-starter添加到我们的pom.xml中:

<dependency>
    <groupId>org.apache.camel.springboot</groupId>
    <artifactId>camel-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>

创建路由

让我们首先定义一个故意抛出异常的相当基础的路由:

@Component
public class ExceptionThrowingRoute extends RouteBuilder {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionThrowingRoute.class);

    @Override
    public void configure() throws Exception {
        
        from("direct:start-exception")
          .routeId("exception-handling-route")
          .process(new Processor() {
              
              @Override
              public void process(Exchange exchange) throws Exception {
                  LOGGER.error("Exception Thrown");
                  throw new IllegalArgumentException("An exception happened on purpose");
                  
              }
          }).to("mock:received");
    }
}

简单回顾一下,Camel中的一个路由是基本构建块,通常由Camel按顺序执行的一系列步骤组成,用于消费和处理消息。

在我们的示例中,我们配置路由从名为start直接端点消费消息。

然后,在新创建的Java DSL内线程的处理器中,我们故意抛出一个IllegalArgumentException

目前,我们的路由没有包含任何类型的异常处理,因此运行时,我们在应用的输出中会看到一些不太友好的内容:

...
10:21:57.087 [main] ERROR c.b.c.e.ExceptionThrowingRoute - Exception Thrown
10:21:57.094 [main] ERROR o.a.c.p.e.DefaultErrorHandler - Failed delivery for (MessageId: 50979CFF47E7816-0000000000000000 on ExchangeId: 50979CFF47E7816-0000000000000000). 
Exhausted after delivery attempt: 1 caught: java.lang.IllegalArgumentException: An exception happened on purpose

Message History (source location and message history is disabled)
---------------------------------------------------------------------------------------------------------------------------------------
Source                                   ID                             Processor                                          Elapsed (ms)
                                         exception-handling-route/excep from[direct://start-exception]                               11
    ...
                                         exception-handling-route/proce Processor@0x3e28af44                                          0

Stacktrace
---------------------------------------------------------------------------------------------------------------------------------------
java.lang.IllegalArgumentException: An exception happened on purpose
...

使用doTry()

现在,让我们为我们的路由添加一些异常处理。在这个部分,我们将研究Camel的doTry()块,可以将其视为Java中的等价于try...catch finally,但它直接嵌入在DSL中。

首先,为了简化代码,我们将定义一个专门的处理器类,该类抛出一个IllegalArgumentException——这将使我们的代码更易于阅读,并且我们可以在其他路由中重用它:

@Component
public class IllegalArgumentExceptionThrowingProcessor implements Processor {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionLoggingProcessor.class);

    @Override
    public void process(Exchange exchange) throws Exception {
        LOGGER.error("Exception Thrown");
        throw new IllegalArgumentException("An exception happened on purpose");
    }
}

有了新的处理器,让我们将其用于我们的第一个异常处理路由:

@Component
public class ExceptionHandlingWithDoTryRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        
        from("direct:start-handling-exception")
          .routeId("exception-handling-route")
          .doTry()
            .process(new IllegalArgumentExceptionThrowingProcessor())
            .to("mock:received")
          .doCatch(IOException.class, IllegalArgumentException.class)
            .to("mock:caught")
          .doFinally()
            .to("mock:finally")
          .end();
    }
}

如您所见,路由中的代码相当直观。我们基本上使用了Camel的等效物模拟了常规的Java try...catch finally语句。

然而,让我们逐步解释路由的关键部分:

  1. 我们使用doTry()方法包围希望立即捕获的异常部分的路由。
  2. 接下来,我们使用doCatch方法关闭这个块。注意,我们可以传递我们希望捕获的不同异常类型列表。
  3. 最后,我们调用doFinally(),定义始终在doTry()和任何doCatch()块之后运行的代码。

此外,我们应注意到在Java DSL中,调用end()方法表示块的结束非常重要。

Camel还提供了一个功能,允许我们在使用doCatch()块时与谓词一起工作:

...
.doCatch(IOException.class, IllegalArgumentException.class).onWhen(exceptionMessage().contains("Hello"))
   .to("mock:catch")
...

在这里,我们在运行时添加了一个谓词来决定是否触发catch块。在这种情况下,只有当引发的异常消息包含单词"Hello"时,我们才会触发它。很酷!

处理异常条款

不幸的是,先前方法的一个限制是它仅适用于单个路由。

随着应用程序的增长,当我们添加越来越多的路由时,我们可能不想逐个路由处理异常。这可能会导致代码重复,我们可能希望为应用程序有一个通用的错误处理策略。

幸运的是,Camel通过Java DSL提供了异常条款机制,可以根据异常类型或全局范围指定所需的错误处理:

假设我们想为我们的应用程序实现一个异常处理策略。对于我们的简单示例,我们假设只有一个路由:

@Component
public class ExceptionHandlingWithExceptionClauseRoute extends RouteBuilder {
    
    @Autowired
    private ExceptionLoggingProcessor exceptionLogger;
    
    @Override
    public void configure() throws Exception {
        onException(IllegalArgumentException.class).process(exceptionLogger)
          .handled(true)
          .to("mock:handled")
        
        from("direct:start-exception-clause")
          .routeId("exception-clause-route")
          .process(new IllegalArgumentExceptionThrowingProcessor())
          .to("mock:received");
    }
}

如您所见,我们使用onException方法来处理IllegalArgumentException的发生,并应用特定的处理过程。在我们的例子中,我们将处理传递给自定义的ExceptionLoggingProcessor类,该类仅记录消息头。最后,我们使用handled(true)方法标记消息交换为已处理,然后将结果发送到名为handled的模拟端点。

然而,我们应该注意,在Camel中,我们的代码全局范围限于每个RouteBuilder实例。因此,如果我们想通过多个RouteBuilder类共享这种错误处理代码,可以使用以下技术。

只需创建一个基类的抽象RouteBuilder,并将错误处理逻辑放在其configure方法中。

然后,我们只需继承这个类并确保调用super.configure()方法。本质上,我们只是在使用Java的继承技术。

总结

在这篇文章中,我们学习了如何在我们的路由中处理异常。首先,我们创建了一个简单的Camel应用程序,带有几个路由,以了解异常。

然后,我们学习了使用doTry()doCatch()块语法的两种具体方法,以及随后的onException()条款。

如往常一样,文章的完整源代码可在GitHub上找到。