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

 建议


AdviceWith 用于测试 Camel 路由,您可以在测试之前为现有路由提供建议。


adviceWith 允许在测试运行前更改路线上的某些因素,例如


  • 拦截发送到端点


  • 用另一个端点替换传入端点


  • 删除或替换路由节点


  • 在路由中插入新节点

  •  模拟端点


AdviceWithRouteBuilder 提供所有这些功能。是一个专门的 RouteBuilder 。.


建议使用路由生成器 api


AdviceWithRouteBuilder 对常规 RouteBuilder 进行了扩展,增加了用于建议路线的专门方法。下表列出了最常用的方法:

 方法  说明

mockEndpoints


模拟路由中的所有端点。

mockEndpoints(patterns)


模拟路由中与模式匹配的所有端点。您可以在给定模式中使用通配符和正则表达式来匹配多个端点。

mockEndpointsAndSkip(patterns)


模拟所有端点,并跳过发送到路由中与模式匹配的端点。您可以在给定模式中使用通配符和正则表达式来匹配多个端点。

replaceFromWith(uri)


用新端点替换路由输入。

weaveByUri(pattern)


在发送到与模式匹配的端点的节点上操纵路由。

weaveById(pattern)


在与模式匹配的节点 ID 上操纵路由。

weaveByToString(pattern)


在符合模式的节点字符串表示(toString 方法的输出)处操作路由。

weaveByType(Class)


在与模式匹配的节点类型(类名)上操作路由。

weaveAddFirst


在路由的起点轻松编织新节点。

weaveAddLast


在路由末端轻松编织新节点。

 模式匹配


模式选项用于匹配。它使用与截取相同的规则,按以下顺序应用:

  •  匹配精确


  • 通过通配符匹配 ( * )


  • 正则表达式匹配


例如,要精确匹配,可以使用 weaveById("foo") ,这样就只能匹配路由中值为 foo 的 id。通配符是指以 * 字符结尾的模式,例如: weaveById("foo*") 将匹配任何以 foo 开头的 id,如:foo、foobar、foobie 等。正则表达式更高级,可以匹配多个 id,如 weaveById("(foo|bar)") 将同时匹配 foo 和 bar。


如果要在精确的端点 URI 上匹配模式,请注意 URI 选项的排序可能会受到影响,因此最好使用通配符进行匹配。


例如,使用 mockEndpointsAndSkip("activemq:queue:foo?*") 来匹配 foo 队列,并忽略任何选项。

 使用建议


要提供建议,您需要使用 AdviceWithRouteBuilder 来操作路由。但首先您需要选择要操作的路由,这可以通过路由 ID 或路由索引来实现。

AdviceWith.adviceWith(context, "myRoute", new AdviceWithRouteBuilder() {
        @Override
        public void configure() throws Exception {
            weaveAddLast().to("mock:result");
        }
});


我们采用 Java lambda 风格,为路由建议引入了更现代化的应用程序接口。


下面我们将为 ID 为 myRoute 的路线提供建议:

AdviceWith.adviceWith(context, "myRoute", a ->
     a.weaveAddLast().to("mock:result")
);


变量 aAdviceWithRouteBuilder 的 lambda 样式,可作为内联路由操作的快捷方式。


在使用 adviceWith 之前,最好先告知骆驼正在使用建议,这将在下一节中介绍。


在测试期间提供建议


使用 adviceWith 时,Camel 会重新启动建议路由。出现这种情况的原因是路由被操作,Camel 需要:


  1. 停止现有路线


  2. 删除现有路线


  3. 添加新的建议路线


  4. 开始新路线


在单元测试启动过程中,每个建议路由都会发生这种情况。这种情况发生得很快:路由启动后会立即停止并移除。这是我们不希望看到的行为;我们希望避免重新启动建议路由。可以通过以下步骤解决这个问题:


  1. 告诉 Camel 正在建议路线,这将阻止 Camel 自动启动路线。


  2. 建议在单元测试方法中使用路由


  3. 建议路线后启动 Camel


在使用 camel-test-junit5 进行单元测试时,您可以通过覆盖 CamelTestSupport 中的 isUsedAdviceWith 方法来告诉 Camel 正在使用建议,如图所示:

public class MyAdviceWithTest extends CamelTestSupport {
    @Override
    public boolean isUseAdviceWith() {
        return true; // turn on advice with
    }
}


或者,在使用 camel-test-spring-junit5 进行单元测试时,可以使用如图所示的 @UseAdviceWith 注解:

@UseAdviceWith
public class MyAdviceWithTest extends CamelSpringTestSupport {
}


然后,你建议启动骆驼所遵循的路线:

@Test
public void testMockEndpoints() throws Exception {
    AdviceWith.adviceWith(context, "myRoute", a ->
         a.mockEndpoints();
    );

    context.start();


在上面的单元测试方法中,我们首先根据 ID 建议路由,并在其中自动模拟所有端点。然后启动 Camel。


建议路线前后的记录


使用 adviceWith 时,Camel 会以 XML 格式自动记录每条建议路线的前后信息。


不过,这需要将 camel-xml-jaxb 作为依赖项,如果使用 Maven,可以将其添加为测试作用域:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-xml-jaxb</artifactId>
    <version>x.x.x</version>
    <!-- use the same version as your Camel core version -->
    <scope>test</scope>
</dependency>


如图所示,通过将日志记录设置为 false ,可以关闭 XML 日志记录:

AdviceWith.adviceWith(context, "myRoute", false, a ->
     a.mockEndpoints();
);


替换路由端点


您可能已经建立了从数据库、消息代理、云系统或其他外部系统消费的端点开始的 Camel 路由。


为了便于对这类路由进行单元测试,可以用 direct、seda、stub 等内部端点替换路由输入端点。


下面说明了如何做到这一点:

@Test
public void testReplaceFrom() throws Exception {
    AdviceWith.adviceWith(context, "myRoute", a ->
        a.replaceFromWith("direct:start");
    );

    context.start();


这样,ID 为 myRoute 的路由中的输入端点(from)就被直接端点取代了,从而便于在单元测试时向路由发送消息。

 模拟端点


使用 mockEndpoints 方法提示路由时,Camel 会在启动过程中记录已提示的端点及其相应的模拟 uri,例如

INFO  ceptSendToMockEndpointStrategy - Adviced endpoint [seda://camel] with mock endpoint [mock:seda:camel]
INFO  ceptSendToMockEndpointStrategy - Adviced endpoint [seda://other] with mock endpoint [mock:seda:other]


在这里,Camel 建议使用两个端点:

  • seda:camel -→ mock:seda:camel

  • seda:other -→ mock:seda:other


这样就可以在单元测试中使用模拟端点进行测试,例如

public void testMockEndpoints() throws Exception {
    // advice the route goes here
    // start camel after advice

    // use the auto mocked uris during testing
    getMockEndpoint("mock:seda:camel").expectedMessageCount(3);
    getMockEndpoint("mock:seda:other").expectedMessageCount(1);

    // send messages

    MockEndpoint.assertIsSatisfied(context);
}


在 Camel 路由中使用 adviceWith 替换输入端点或模拟端点只是路由操作功能的开始。下一节将介绍更深入的操作方法。


利用编织修改路线


测试 Camel 路由时,可以使用 adviceWith 在测试前编织路由。


以下方法适用于 weave 方法:

 方法  说明

remove


删除选中的节点。

replace


用以下节点替换所选节点

before


在所选节点之前,会添加以下节点。

after


在选定的节点之后,会添加以下节点。


例如,给定以下路线:

from("direct:start")
  .to("mock:foo")
  .to("mock:bar").id("bar")
  .to("mock:result");


然后,让我们来看看如何在单元测试中使用这四种方法

 替换

AdviceWith.adviceWith(context.getRouteDefinitions().get(0), context, new AdviceWithRouteBuilder() {
    @Override
    public void configure() throws Exception {
        // weave the node in the route which has id = bar
        // and replace it with the following route path
        weaveById("bar").replace().multicast().to("mock:a").to("mock:b");
    }
});


在本例中,我们用 .multicast().to("mock:a").to("mock:b") 代替 .to("mock:bar").id("bar") 。.这就意味着,我们不是向 mock:bar 端点发送信息,而是向 mock:a 和 mock:b 端点进行组播。

 删除


在下面的示例中,我们只需将路由中的 .to("mock:bar").id("bar") 删除即可:

AdviceWith.adviceWith(context.getRouteDefinitions().get(0), context, new AdviceWithRouteBuilder() {
    @Override
    public void configure() throws Exception {
        // weave the node in the route which has id = bar and remove it
        weaveById("bar").remove();
    }
});

 


在下面的示例中,我们在id 为 "bar "的节点之前添加了以下节点 to("mock:a").transform(constant("Bye World"))

AdviceWith.adviceWith(context.getRouteDefinitions().get(0), context, new AdviceWithRouteBuilder() {
    @Override
    public void configure() throws Exception {
        // weave the node in the route which has id = bar
        // and insert the following route path before the adviced node
        weaveById("bar").before().to("mock:a").transform(constant("Bye World"));
    }
});


这意味着之前发送到 mock:bar 的信息将被转换为恒定信息 "再见世界"。

 


在下面的示例中,我们在id 为 "bar "的节点后添加了以下节点 to("mock:a").transform(constant("Bye World"))

AdviceWith.adviceWith(context.getRouteDefinitions().get(0), context, new AdviceWithRouteBuilder() {
    @Override
    public void configure() throws Exception {
        // weave the node in the route which has id = bar
        // and insert the following route path after the advice node
        weaveById("bar").after().to("mock:a").transform(constant("Bye World"));
    }
});


这意味着,在 mock:bar 之后发送的信息将转变为恒定信息 "再见世界"。


不使用 ID 进行编织


编织路由时,需要使用 weaveBy 方法中的一种作为标准,在路由图中选择一个或多个节点。


假设您在路由中使用了拆分 EIP,那么您可以使用 weaveByType 来选择该 EIP。给定以下路由:

from("file:inbox").routeId("inbox")
    .split(body())
    .transform(simple("${body.toLowerCase()}"))
        .to("mock:line")
    .end()
    .to("mock:combined");


由于路由中只有一个分路器 EIP,您可以使用 weaveByType 查找路由中的这一个分路器。使用 weaveByType 时,需要输入 EIP 的模型类型。模型类型的名称使用 _name_Definition 模式。

weaveByType(SplitDefinition.class)
    .before()
        .transform(simple("${body},Camel is awesome"));


在这里,我们编织并选择拆分 EIP,然后编织消息转换,在调用拆分器之前对其进行处理。这就意味着,信息正文被附加了 "Camel is awesome"(Camel真棒)。

 旅游编织


weaveByToUri 是一种方便的方法,可轻松编织一条向给定端点 URI 或模式发送消息的 Camel 路由。


下面的路由在基于内容的路由器 EIP 中有两个分支:

from("direct:start")
    .choice()
        .when(header("foo")).to("direct:branch-1")
    .otherwise()
        .to("direct:branch-2");


然后,我们想轻松地对该路由进行单元测试,以确定消息是在分支-1 还是分支-2 发送的。如图所示,我们可以使用 weaveByToUri 进行测试:

weaveByToUri("direct:branch*").replace().to("mock:cheese");


请注意, weaveByToUri 方法使用通配符 ( * ) 来匹配两个分支。


您也可以使用 mockEndpoints 自动模拟,而不是上例中的 weaveByToUriweave 方法可以对路由进行更多操作,如消息转换、消息路由等。


先编织后编织


weaveAddFirstweaveAddLast 是一种速记方法,可以方便地向路由添加节点。这些方法只能向现有路由添加节点。如果要对路由进行操作,请使用前面介绍过的其他 weave 方法。


weaveAddFirst 方法在路径开头添加, weaveAddLast 方法在路径结尾添加。使用它们的效果与其他 weaveBy 方法相同,请参见上文的示例。


利用节点选择进行编织


weaveBy 方法会选择所有匹配的节点,这些节点可以是无节点、一个节点、两个节点或多个节点。在这种情况下,您可能希望将选择范围缩小到某个特定节点。这可以通过使用 select 方法来实现:


  • selectFirst 仅选择第一个节点。


  • selectLast 仅选择最后一个节点。


  • selectIndex(index) 仅选择第 n 个节点。索引为零。


  • selectRange(from, to) 选择给定范围内的节点。索引为零。


  • maxDeep(level) 将选择范围限制在 Camel 路由树最深的 N 层。第一层是编号 1。因此第 2 个节点是第一层节点的子节点。


下面的路由有多个过滤器 EIP,我们只想建议第二个过滤器。

from("file:inbox").routeId("inbox")
    .filter(header("foo"))
        .to("mock:foo")
    .end()
    .to("mock:a")
    .filter(header("bar"))
        .to("mock:bar")
    .end()
    .to("mock:b")
    .filter(header("cheese"))
        .to("mock:cheese")
    .end()
    .to("mock:c")


然后,您可以使用 weaveByType 来匹配过滤 EIP,使用 selectIndex 来匹配找到的第 2 个 EIP:

weaveByType(FilterDefinition.class).selectIndex(1).replace().to("mock:changed");