AdviceWith
用户手册
AdviceWith
AdviceWith 用于测试 Camel 路由,你可以在路由被测试之前对其增强(advice)。
adviceWith 允许在运行测试之前更改路由中的某些要素,例如:
- 拦截发送到端点的消息
- 用另一个端点替换入站端点
- 移除或替换路由中的节点
- 向路由中插入新节点
- 模拟(mock)端点
所有这些功能都可从 AdviceWithRouteBuilder 获得,它是一个特化的 RouteBuilder。
AdviceWithRouteBuilder API
AdviceWithRouteBuilder 继承了常规的 RouteBuilder,并添加了用于增强路由的特化方法。下表列出了最常用的方法:
| 方法 | 说明 |
|---|---|
mockEndpoints | 模拟路由中的所有端点。 |
mockEndpoints(patterns) | 模拟路由中与给定模式相匹配的所有端点。可以在给定的模式中使用通配符和正则表达式来匹配多个端点。 |
mockEndpointsAndSkip(patterns) | 模拟所有端点,并跳过向路由中与给定模式相匹配的端点发送消息。可以在给定的模式中使用通配符和正则表达式来匹配多个端点。 |
replaceFromWith(uri) | 用新的端点替换路由的输入。 |
weaveByUri(pattern) | 在发送到与该模式匹配的端点的节点处操作路由。 |
weaveById(pattern) | 在与该模式匹配的节点 ID 处操作路由。 |
weaveByToString(pattern) | 在与该模式匹配的节点字符串表示(toString 方法的输出)处操作路由。 |
weaveByType(Class) | 在与该模式匹配的节点类型(类名)处操作路由。 |
weaveAddFirst | 轻松在路由的开头织入新节点。 |
weaveAddLast | 轻松在路由的末尾织入新节点。 |
模式匹配
pattern 选项用于匹配。它使用与 Intercept 相同的规则,按以下顺序应用:
- 精确匹配
- 通配符匹配(
*) - 正则表达式匹配
例如,要进行精确匹配,可以使用 weaveById("foo"),它只会匹配路由中 id 值为 foo 的位置。通配符匹配是指模式以 * 字符结尾,例如:weaveById("foo*") 会匹配所有以 foo 开头的 id,如 foo、foobar、foobie 等。正则表达式匹配更高级,允许匹配多个 id,例如 weaveById("(foo|bar)") 会同时匹配 foo 和 bar。
如果尝试对精确的端点 URI 进行模式匹配,请注意 URI 选项的顺序可能会影响匹配结果,因此最好使用通配符进行匹配。
例如,使用 mockEndpointsAndSkip("activemq:queue:foo?*") 来匹配 foo 队列并忽略任何选项。
使用 AdviceWith
要进行 advice,需要使用 AdviceWithRouteBuilder 来操作路由。但首先,需要选择要操作的路由,可以通过路由 ID 或路由索引来完成。
仅限 Java:使用 AdviceWithRouteBuilder 对路由进行 advice
AdviceWith.adviceWith("myRoute", context, new AdviceWithRouteBuilder() {
@Override
public void configure() throws Exception {
weaveAddLast().to("mock:result");
}
});我们引入了一种更现代的 API,使用 Java lambda 风格来对路由进行 advice(增强/修改)。
下面,我们对 ID 为 myRoute 的路由进行 advice:
纯 Java:使用 lambda 风格对路由进行 advice
AdviceWith.adviceWith(context, "myRoute", a ->
a.weaveAddLast().to("mock:result")
);变量 a 是 AdviceWithRouteBuilder 的 lambda 风格写法,可用作内联编写路由操作的简写形式。
在使用 adviceWith 之前,最好先告诉 Camel 正在使用 advice,这一点将在下文介绍。 |
|---|
在测试中启用 advice
使用 adviceWith 时,Camel 会重新启动经过 advice 处理的路由。这是因为路由被修改了,Camel 需要:
- 停止现有路由
- 移除现有路由
- 添加经过 advice 处理的新路由
- 启动新路由
在单元测试启动期间,每个经过 advice 处理的路由都会经历这一过程。这个过程很快:一个路由被启动后,随即又被停止并移除。这不是我们期望的行为,应当避免经过 advice 处理的路由被重新启动。解决方法如下:
- 告诉 Camel 路由正在被 advice,这样可以防止 Apache Camel 自动启动这些路由。
- 在单元测试方法中对路由进行 advice
- 在完成对路由的 advice 之后再启动 Camel
使用 camel-test-junit5 进行单元测试时,可以通过重写 CamelTestSupport 的 isUsedAdviceWith 方法来告知 Camel 正在使用 advice,如下所示:
仅 Java:在 CamelTestSupport 中启用 AdviceWith
public class MyAdviceWithTest extends CamelTestSupport {
@Override
public boolean isUseAdviceWith() {
return true; // turn on advice with
}
}或者在使用 camel-test-spring-junit5 进行单元测试时,你可以使用 @UseAdviceWith 注解,如下所示:
仅 Java:使用 @UseAdviceWith 注解启用 AdviceWith
@UseAdviceWith
public class MyAdviceWithTest extends CamelSpringTestSupport {
}然后按如下方式通知路由并启动 Camel:
纯 Java 方式:先通知路由,再启动 CamelContext
@Test
public void testMockEndpoints() throws Exception {
AdviceWith.adviceWith(context, "myRoute", a ->
a.mockEndpoints();
);
context.start();在上面的单元测试方法中,我们首先按 ID 对路由进行增强(advise),并在此过程中自动模拟(mock)所有端点。之后再启动 Camel。
记录路由增强前后的日志
使用 adviceWith 时,Camel 会自动以 XML 格式记录每条被增强路由的前后状态。
不过,这需要将 camel-xml-jaxb 作为依赖项;如果使用 Maven,可以将其添加为 test 作用域:
<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 格式的日志记录,如下所示:
仅限 Java:在为路由添加增强时禁用 XML 日志记录
AdviceWith.adviceWith(context, "myRoute", false, a ->
a.mockEndpoints();
);替换路由端点
你可能构建过这样的 Camel 路由:它们从消费数据库、消息中间件、云系统或其他外部系统的端点开始。
为了使这类路由的单元测试更加容易,可以将路由的输入端点替换为内部端点,例如 direct、seda、stub。
下面演示了如何做到这一点:
仅使用 Java:为测试替换路由输入端点
@Test
public void testReplaceFrom() throws Exception {
AdviceWith.adviceWith(context, "myRoute", a ->
a.replaceFromWith("direct:start");
);
context.start();这会将 ID 为 myRoute 的路由中的输入端点(from)替换为 direct 端点,从而在单元测试中可以方便地向该路由发送消息。
模拟端点
在对路由进行 advice 时使用 mockEndpoints 方法,Camel 会在启动期间记录哪些端点被 advice 了,以及它们对应的 mock 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 对两个端点进行了 advice(替换):
- seda:camel -→ mockcamel
- seda:other -→ mockother
这样就可以在单元测试中使用 mock 端点进行测试,例如:
纯 Java 方式:在单元测试中使用自动 mock 的端点
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);
}使用 adviceWith 替换输入端点或在 Camel 路由中模拟端点,仅仅是路由操作能力的开始。下面的章节将介绍如何更进一步。
使用 weave 修改路由
在测试 Camel 路由时,你可以在测试之前使用 adviceWith 对路由进行编织(weave)。
weave 方法提供以下操作:
| 方法 | 说明 |
|---|---|
remove | 移除选中的节点。 |
replace | 用后续节点替换选中的节点。 |
before | 在选中的节点之前添加后续节点。 |
after | 在选中的节点之后添加后续节点。 |
例如,给定如下的路由:
仅 Java:用于演示 weave 的示例路由
from("direct:start")
.to("mock:foo")
.to("mock:bar").id("bar")
.to("mock:result");接下来我们逐一介绍这四种方法,看看如何在单元测试中使用它们。
替换(Replace)
仅限 Java:通过 ID 织入某个节点并进行替换
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");
}
});在这个示例中,我们将 .to("mock:bar").id("bar") 替换为 .multicast().to("mock:a").to("mock:b")。这意味着消息不再发送到 mock:bar 端点,而是改为多播(Multicast)到 mock:a 和 mock:b 端点。
移除
在下面的示例中,我们直接从路由中移除 .to("mock:bar").id("bar"):
仅 Java:按 ID 织入节点并将其移除
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"))。
仅限 Java:在特定节点之前织入节点
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 之前,消息已经被转换为常量消息 Bye World
之后(After)
在下面的示例中,我们在 id 为 bar 的节点之后添加了以下节点 to("mock:a").transform(constant("Bye World"))。
仅限 Java:在特定节点之后织入节点
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 之后发送的消息已经被转换成常量消息 Bye World。
不使用 ID 进行织入
在织入路由时,你需要使用某个 weaveBy 方法作为条件,以便在路由图中选择一个或多个节点。
假设你在路由中使用了 Split EIP,那么就可以使用 weaveByType 来选择这个 EIP。给定如下路由:
纯 Java 实现:用于 weaveByType 示例的、包含 Split EIP 的路由
from("file:inbox").routeId("inbox")
.split(body())
.transform(simple("${body.toLowerCase()}"))
.to("mock:line")
.end()
.to("mock:combined");由于该路由中只有一个 Split EIP,你可以使用 weaveByType 来定位路由中的这个唯一切分器。使用 weaveByType 需要传入该 EIP 的模型类型,模型类型的名称遵循 _name_Definition 这一命名模式。
仅限 Java:使用 weaveByType 在 Split EIP 之前插入一个节点
weaveByType(SplitDefinition.class)
.before()
.transform(simple("${body},Camel is awesome"));在此我们编织并选择 Split EIP,并织入一个消息转换,该转换在调用拆分器之前执行。这意味着消息体将追加 Camel is awesome。
weaveByToUri
weaveByToUri 是一个便捷方法,可轻松编织发送消息到给定端点 URI 或模式的 Camel 路由。
给定以下在 Content Based Router EIP 中包含两个分支的路由:
纯 Java 实现:用于 weaveByToUri 示例的基于内容的路由器路由
from("direct:start")
.choice()
.when(header("foo")).to("direct:branch-1")
.otherwise()
.to("direct:branch-2");然后我们希望能够轻松地对该路由进行单元测试,验证消息是发送到 branch-1 还是 branch-2。这可以通过 weaveByToUri 来完成,如下所示:
仅 Java:使用 weaveByToUri 进行通配符匹配
weaveByToUri("direct:branch*").replace().to("mock:cheese");注意 weaveByToUri 方法使用通配符(*)来匹配两个分支。
你也可以在上面的示例中使用 mockEndpoints 来自动模拟,而不是使用 weaveByToUri。weave 方法拥有更强大的功能来操作路由,例如消息转换、消息路由等等。 |
|---|
weaveAddFirst 和 weaveAddLast
weaveAddFirst 和 weaveAddLast 是用于轻松向路由添加节点的简写方法。这些方法只能向已存在的路由中添加节点。如果你想操作路由本身,请使用前面已介绍的其他 weave 方法。
weaveAddFirst 方法在路由的开头添加节点,而 weaveAddLast 在路由的末尾添加节点。它们的用法与其他 weaveBy 方法相同,示例请参见上文。
使用节点选择的 weave 方法
weaveBy 方法会选中所有匹配的节点,匹配结果可能为零个、一个、两个或更多节点。在某些情况下,你可能需要将选择范围缩小到特定的某个节点。这可以通过使用 select 方法来实现:
selectFirst只选中第一个节点。selectLast只选中最后一个节点。selectIndex(index)只选中第 n 个节点。索引从 0 开始。selectRange(from, to)选中给定范围内的节点。索引从 0 开始。maxDeep(level)将选择范围限制在 Camel 路由树中最多 N 层深度。第一层的编号为 1,因此编号 2 表示第一层节点的子节点。
假设存在以下包含多个 Filter EIP 的路由,我们只想对第二个过滤器进行 advice。
仅 Java:包含多个 Filter EIP 的路由,用于 selectIndex 示例
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 匹配 Filter EIP,并用 selectIndex 匹配找到的第二个节点:
仅 Java:使用 selectIndex 定位到特定的匹配节点
weaveByType(FilterDefinition.class).selectIndex(1).replace().to("mock:changed");评论
登录后参与评论
KnowForge