路线模板
路由模板顾名思义就是路由的模板,用于根据一组输入参数创建路由。换句话说,路由模板就是参数化的路由。
路线模板 + 输入参数 ⇒ 路线
通过路由模板,您可以创建一个或多个路由。
在 DSL 中定义路由模板
路由模板应在 DSL 中定义(就像路由一样),如下所示:
public class MyRouteTemplates extends RouteBuilder {
@Override
public void configure() throws Exception {
// create a route template with the given name
routeTemplate("myTemplate")
// here we define the required input parameters (can have default values)
.templateParameter("name")
.templateParameter("greeting")
.templateParameter("myPeriod", "3s")
// here comes the route in the template
// notice how we use {{name}} to refer to the template parameters
// we can also use {{propertyName}} to refer to property placeholders
.from("timer:{{name}}?period={{myPeriod}}")
.setBody(simple("{{greeting}} ${body}"))
.log("${body}");
}
}
而在 Spring XML DSL 中
<camelContext>
<routeTemplate id="myTemplate">
<templateParameter name="name"/>
<templateParameter name="greeting"/>
<templateParameter name="myPeriod" defaultValue="3s"/>
<route>
<from uri="timer:{{name}}?period={{myPeriod}}"/>
<setBody><simple>{{greeting}} ${body}</simple></setBody>
<log message="${body}"/>
</route>
</routeTemplate>
</camelContext>
而在 XML DSL 中
<routeTemplates xmlns="http://camel.apache.org/schema/spring">
<routeTemplate id="myTemplate">
<templateParameter name="name"/>
<templateParameter name="greeting"/>
<templateParameter name="myPeriod" defaultValue="3s"/>
<route>
<from uri="timer:{{name}}?period={{myPeriod}}"/>
<setBody><simple>{{greeting}} ${body}</simple></setBody>
<log message="${body}"/>
</route>
</routeTemplate>
</routeTemplates>
在上面的示例中,只有一个路由模板,但你可以定义任意多个。每个模板都必须有一个唯一的 id。模板参数用于定义模板接受的参数。如图所示,有 3 个参数:姓名、问候语和 myPeriod。前两个参数是必须的,而 myPeriod 是可选的,因为它的默认值是 3s。
然后,模板参数将在路由中使用 {{ }}
语法作为常规属性占位符。请注意我们是如何在计时器端点和简单语言中使用 {{name}}
和 {{greeting}}
的。
当然,路由也可以使用普通的属性占位符。假设有一个名称为 "greeting "的属性占位符:
greeting = Davs
那么在创建路由时,Camel 通常会使用这个值 Davs
。但是,由于路由模板定义了一个同名的模板参数 greeting
,因此从模板创建路由时必须提供一个值。
模板参数优先于常规属性占位符。
从路由模板创建路由
要通过路由模板创建路由,则应使用 org.apache.camel.builder.TemplatedRouteBuilder
。.
在下面的代码片段中,您可以看到如何通过生成器实现这一功能:
// create two routes from the template
TemplatedRouteBuilder.builder(context, "myTemplate")
.parameter("name", "one")
.parameter("greeting", "Hello")
.add();
TemplatedRouteBuilder.builder(context, "myTemplate")
.parameter("name", "two")
.parameter("greeting", "Bonjour")
.parameter("myPeriod", "5s")
.add();
而在 Java DSL 中
templatedRoute("myTemplate")
.parameter("name", "one")
.parameter("greeting", "Hello");
templatedRoute("myTemplate")
.parameter("name", "two")
.parameter("greeting", "Bonjour")
.parameter("myPeriod", "5s");
而在 Spring XML DSL 中
<camelContext>
<templatedRoute routeTemplateRef="myTemplate">
<parameter name="name" value="one"/>
<parameter name="greeting" value="Hello"/>
</templatedRoute>
<templatedRoute routeTemplateRef="myTemplate">
<parameter name="name" value="two"/>
<parameter name="greeting" value="Bonjour"/>
<parameter name="myPeriod" value="5s"/>
</templatedRoute>
</camelContext>
而在 XML DSL 中
<templatedRoutes xmlns="http://camel.apache.org/schema/spring">
<templatedRoute routeTemplateRef="myTemplate">
<parameter name="name" value="one"/>
<parameter name="greeting" value="Hello"/>
</templatedRoute>
<templatedRoute routeTemplateRef="myTemplate">
<parameter name="name" value="two"/>
<parameter name="greeting" value="Bonjour"/>
<parameter name="myPeriod" value="5s"/>
</templatedRoute>
</templatedRoutes>
而在 YAML DSL 中
- templated-route:
route-template-ref: "myTemplate"
parameters:
- name: "name"
value: "one"
- name: "greeting"
value: "Hello"
- templated-route:
route-template-ref: "myTemplate"
parameters:
- name: "name"
value: "two"
- name: "greeting"
value: "Bonjour"
- name: "myPeriod"
value: "5s"
add
返回的值是已添加的新路由的路由 ID。但如果路由尚未创建和添加, null
则会返回,如果 CamelContext
尚未启动,则可能出现这种情况。
如果没有提供路由 ID,Camel 将自动分配一个路由 ID。在上面的示例中,Camel 将为这些路由分配路由 id,如 route1
、 route2
等路由id。
如果要指定路由 id,请使用 routeId
,如下所示,其中 id 设置为 myCoolRoute:
TemplatedRouteBuilder.builder(context, "myTemplate")
.routeId("myCoolRoute")
.parameter("name", "one")
.parameter("greeting", "hello")
.parameter("myPeriod", "5s")
.add();
而在 Java DSL 中
templatedRoute("myTemplate")
.routeId("myCoolRoute")
.parameter("name", "one")
.parameter("greeting", "hello")
.parameter("myPeriod", "5s");
而在 Spring XML DSL 中
<camelContext>
<templatedRoute routeTemplateRef="myTemplate" routeId="myCoolRoute">
<parameter name="name" value="one"/>
<parameter name="greeting" value="hello"/>
<parameter name="myPeriod" value="5s"/>
</templatedRoute>
</camelContext>
而在 XML DSL 中
<templatedRoutes xmlns="http://camel.apache.org/schema/spring">
<templatedRoute routeTemplateRef="myTemplate" routeId="myCoolRoute">
<parameter name="name" value="one"/>
<parameter name="greeting" value="hello"/>
<parameter name="myPeriod" value="5s"/>
</templatedRoute>
</templatedRoutes>
而在 YAML DSL 中
- templated-route:
route-template-ref: "myTemplate"
route-id: "myCoolRoute"
parameters:
- name: "name"
value: "one"
- name: "greeting"
value: "hello"
- name: "myPeriod"
value: "5s"
在 Java DSL 简单生成器中使用模板参数
在使用 Java DSL 和简单语言时,请注意在定义简单表达式/谓词时不要使用简单流畅构建器。
例如,在 Java DSL 中给出了以下路由模板:
public class MyRouteTemplates extends RouteBuilder {
@Override
public void configure() throws Exception {
routeTemplate("myTemplate")
.templateParameter("name")
.templateParameter("color")
.from("direct:{{name}}")
.choice()
.when(simple("{{color}}").isEqualTo("red"))
.to("direct:red")
.otherwise()
.to("color:other")
.end();
}
}
然后,请注意简单谓词是如何使用简单流畅构建器 simple("{{color}}").isEqualTo("red")
的。.路由模板不支持这种方法,而且在使用模板创建多个路由时也无法使用。
取而代之的是,简单表达式应该是一个字面字符串值,如下所示:
.when(simple("'{{color}}' == 'red'")
在路由模板中使用硬编码节点 ID
如果路由模板包含硬编码的节点 ID,那么从模板创建的路由将使用相同的 ID,因此如果从同一模板创建了 2 个或多个路由,就会出现检测到重复 ID 的错误。
给定下面的路由模板,然后在调用 http 服务的节点中硬编码 ID(new-order)。
public class MyRouteTemplates extends RouteBuilder {
@Override
public void configure() throws Exception {
routeTemplate("orderTemplate")
.templateParameter("queue")
.from("jms:{{queue}}")
.to("http:orderserver.acme.com/neworder").id("new-order")
.log("Processing order");
}
}
从模板创建路由时,可以提供一个前缀,用于所有节点 ID。这样就可以创建 2 个或多个路由,而不会出现 ID 重复的错误。
例如,在下文中,我们从 myTemplate 模板中创建一个新路由 myCoolRoute,并使用 web 作为前缀。
而在 Java DSL 中
templatedRoute("orderTemplate")
.routeId("webOrder")
.prefixId("web")
.parameter("queue", "order.web");
然后,我们就可以创建第二条路线:
templatedRoute("orderTemplate")
.routeId("ftpOrder")
.prefixId("ftp")
.parameter("queue", "order.ftp");
而在 Spring XML DSL 中
<camelContext>
<templatedRoute routeTemplateRef="orderTemplate" routeId="webOrder" prefixId="web">
<parameter name="queue" value="web"/>
</templatedRoute>
</camelContext>
而在 XML DSL 中
<templatedRoutes xmlns="http://camel.apache.org/schema/spring">
<templatedRoute routeTemplateRef="orderTemplate" routeId="webOrder" prefixId="web">
<parameter name="queue" value="web"/>
</templatedRoute>
</templatedRoutes>
而在 YAML DSL 中
- templated-route:
route-template-ref: "orderTemplate"
route-id: "webOrder"
prefix-id: "web"
parameters:
- name: "queue"
value: "web"
将 bean 绑定到路由模板
路由模板允许绑定本地范围内的 Bean,这些 Bean 仅用于从模板创建路由。这样就可以使用同一个模板创建多个路由,其中每个创建的路由都有本地(私有)的豆子。
例如,在下面的路由模板中,我们使用 templateBean
来设置本地 Bean,如图所示:
routeTemplate("s3template")
.templateParameter("region")
.templateParameter("bucket")
.templateBean("myClient", S3Client.class, rtc ->
S3Client.builder().region(rtc.getProperty("region", Region.class)).build();
)
.from("direct:s3-store")
// must refer to the bean with {{myClient}}
.to("aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}")
该模板有两个参数,用于指定 AWS 区域和 S3 存储桶。要连接到 S3,则需要一个 software.amazon.awssdk.services.s3.S3Client
Bean。
要创建此 Bean,我们可以使用 templateBean
DSL 指定 Bean id 为 myClient
。.可以指定 Bean 的类型( S3Client.class
),但这是可选项(如果需要通过类型而不是名称来发现 Bean,则可以使用该选项)。
这样可以确保创建 Bean 的代码稍后执行(Camel 从模板创建路由时),然后必须将代码指定为供应商。由于我们希望在创建 Bean 时访问模板参数,因此我们使用了 Camel BeanSupplier
来访问上面代码中的 rtc 变量 RouteTemplateContext
。
必须使用 Camel 的属性占位符语法(例如路由模板中的 {{myClient}} )来引用 id 为 myClient 的本地 Bean,如上图所示,并使用 to 端点。这是因为本地 Bean 必须是唯一的,Camel 将在内部重新分配 Bean id,使用唯一 id 而不是 myClient .这就是借助属性占位符功能实现的。 |
如果从该模板创建了多个路由,那么创建的每个路由都会创建自己的 S3Client Bean。
从模板创建器绑定 Bean 到路由模板
从现有模板创建路由时, TemplatedRouteBuilder
还可以绑定本地 Bean(可以指定这些 Bean)。
假设下面的路由模板是用 XML 定义的:
<camelContext>
<routeTemplate id="s3template">
<templateParameter name="region"/>
<templateParameter name="bucket"/>
<route>
<from uri="direct:s3-store"/>
<to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
</route>
</routeTemplate>
</camelContext>
该模板没有创建模板所需的 #{{myClient}}
的 Bean 绑定。
通过 TemplatedRouteBuilder
从模板创建路由时,如果希望 Bean 为本地作用域(不与他人共享),则可以提供 Bean 绑定:
TemplatedRouteBuilder.builder(context, "s3template")
.parameter("region", "US-EAST-1")
.parameter("bucket", "myBucket")
.bean("myClient", S3Client.class,
S3Client.builder()
.region(rtc.getProperty("region", Region.class))
.build())
.routeId("mys3route")
.add();
可以看到,绑定方式与直接在路由模板中使用 templateBean
时类似。
而在 Java DSL 中
templatedRoute("s3template")
.parameter("region", "US-EAST-1")
.parameter("bucket", "myBucket")
.bean("myClient", S3Client.class,
rtc -> S3Client.builder() (1)
.region(rtc.getProperty("region", Region.class))
.build())
.routeId("mys3route");
1 | 请注意, bean 方法的第三个参数并不是直接的 Bean,而是用于创建 Bean 的工厂方法,这里我们使用 lambda 表达式作为工厂方法。 |
而在 XML DSL 中
<templatedRoute routeTemplateRef="s3template" routeId="mys3route">
<parameter name="region" value="US-EAST-1"/>
<parameter name="bucket" value="myBucket"/>
<bean name="myClient" type="software.amazon.awssdk.services.s3.S3Client"
scriptLanguage="groovy"> (1)
<script>
import software.amazon.awssdk.services.s3.S3Client
S3Client.builder()
.region(rtc.getProperty("region", Region.class))
.build()
</script>
</bean>
</templatedRoute>
1 | 对于非 Java DSL,如果是复杂的 Bean 工厂,您仍然可以使用 groovy 这样的语言在 script 元素中定义 Bean 工厂。 |
而在 YAML DSL 中
- templated-route:
route-template-ref: "s3template"
route-id: "mys3route"
parameters:
- name: "region"
value: "US-EAST-1"
- name: "bucket"
value: "myBucket"
beans:
- name: "myClient"
type: "software.amazon.awssdk.services.s3.S3Client"
scriptLanguage: "groovy"
script: | (1)
import software.amazon.awssdk.services.s3.S3Client
S3Client.builder()
.region(rtc.getProperty("region", Region.class))
.build()
1 | 对于非 Java DSL,如果是复杂的 Bean 工厂,您仍然可以使用 groovy 这样的语言将 Bean 工厂定义为 script 键的值。 |
您也可以在模板之外创建 Bean,并通过引用将其绑定,而不是通过模板生成器绑定 Bean。
final S3Client myClient = S3Client.builder().region(Region.US_EAST_1).build();
TemplatedRouteBuilder.builder(context, "s3template")
.parameter("region", Region.US_EAST_1)
.parameter("bucket", "myBucket")
.bean("myClient", myClient)
.routeId("mys3route")
.add();
而在 Java DSL 中
templatedRoute("s3template")
.parameter("region", "US-EAST-1")
.parameter("bucket", "myBucket")
.bean("myClient", S3Client.class, rtc -> myClient)
.routeId("mys3route");
如果可能的话,您最好直接在模板中创建本地 Bean,因为这样可以确保路由模板开箱即用。否则,每次从路由模板创建新路由时,都必须创建或提供 bean。不过,后者允许以任何其他自定义方式创建 Bean。
使用 Bean 类型将 Bean 绑定到路由模板
您可以通过引用一个完全限定的类名来创建本地 Bean,Camel 将使用该类名来创建一个新的本地 Bean 实例。使用这种方法时,创建的 Bean 将通过类的默认构造函数创建。
可以通过 getter/setter 样式配置 bean 实例的属性。之前创建 AWS S3Client 的示例不支持这种方式,因为它使用的是流畅构建器模式(而不是 getter/setter)。
因此,假设我们有这样一个类
public class MyBar {
private String name;
private String address;
// getter/setter omitted
public String location() {
return "The bar " + name + " is located at " + address;
}
}
然后,我们就可以在路由模板中使用 MyBar
类作为本地 Bean,如下所示:
routeTemplate("barTemplate")
.templateParameter("bar")
.templateParameter("street")
.templateBean("myBar")
.typeClass("com.foo.MyBar")
.property("name", "{{bar}}")
.property("address", "{{street}}")
.end()
.from("direct:going-out")
.to("bean:{{myBar}}")
通过 Java DSL,您还可以使用类型安全的方式引用 bean 类:
.templateBean("myBar")
.typeClass(MyBar.class)
.property("name", "{{bar}}")
.property("address", "{{street}}")
.end()
在 XML DSL 中,您可以这样做:
<camelContext xmlns="http://camel.apache.org/schema/spring">
<routeTemplate id="myBar">
<templateParameter name="bar"/>
<templateParameter name="street"/>
<templateBean name="myBean" type="#class:com.foo.MyBar">
<property key="name" value="{{bar}}"/>
<property key="address" value="{{street}}"/>
</templateBean>
<route>
<from uri="direct:going-out"/>
<to uri="bean:{{myBar}}"/>
</route>
</routeTemplate>
</camelContext>
使用脚本语言将 bean 与路由模板绑定
您可以使用 groovy、java、mvel 等脚本语言创建 bean。这样就可以使用内置的脚本语言(如 groovy)定义路由模板。
例如,创建 AWS S3 客户端的方法如 Java 所示(内嵌 groovy 代码):
routeTemplate("s3template")
.templateParameter("region")
.templateParameter("bucket")
.templateBean("myClient", "groovy",
"software.amazon.awssdk.services.s3.S3Client.S3Client.builder()
.region(rtc.getProperty("region", Region.class))
.build()"
)
.from("direct:s3-store")
// must refer to the bean with {{myClient}}
.to("aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}")
可以使用 resource:
作为前缀,将 groovy 代码外部化为 classpath 或文件系统中的一个文件,例如
routeTemplate("s3template")
.templateParameter("region")
.templateParameter("bucket")
.templateBean("myClient", "groovy", "resource:classpath:s3bean.groovy")
.from("direct:s3-store")
// must refer to the bean with {{myClient}}
.to("aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}")
然后在 classpath 根目录下创建文件 s3bean.groovy
:
import software.amazon.awssdk.services.s3.S3Client
S3Client.builder()
.region(rtc.getProperty("region", Region.class))
.build()
XML DSL 中的路由模板还可以使用 groovy 语言创建 Bean,如下所示:
<camelContext>
<routeTemplate id="s3template">
<templateParameter name="region"/>
<templateParameter name="bucket"/>
<templateBean name="myClient" type="groovy">
<script>
import software.amazon.awssdk.services.s3.S3Client
S3Client.builder()
.region(rtc.getProperty("region", Region.class))
.build()
</script>
</templateBean>
<route>
<from uri="direct:s3-store"/>
<to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
</route>
</routeTemplate>
</camelContext>
请注意,groovy 代码也可以直接内联到 XML 中的路由模板中。当然,您也可以使用 resource:
作为前缀,将 Bean 创建代码外部化到外部文件中:
<camelContext>
<routeTemplate id="s3template">
<templateParameter name="region"/>
<templateParameter name="bucket"/>
<templateBean name="myClient" type="groovy">
<script>resource:classpath:s3bean.groovy</script>
</templateBean>
<route>
<from uri="direct:s3-store"/>
<to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
</route>
</routeTemplate>
</camelContext>
支持的语言有
类型 | 说明 |
---|---|
豆子 |
|
时髦 |
|
java |
|
mvel |
|
ognl |
|
名字 |
|
在评估脚本时,Camel 会将 RouteTemplateContext
绑定为名称为 rtc
的根对象。这意味着您可以通过 rtc
访问 RouteTemplateContext
和 CamelContext
中的所有信息。.
这就是我们在前面示例的脚本中使用模板参数时的做法:
rtc.getProperty('region', String.class)
要访问 CamelContext
,您可以这样做:
var cn = rtc.getCamelContext().getName()
功能最强大的语言是 groovy 和 Java。其他语言的灵活性有限,因为它们不是完整的编程语言,但更适合模板需求。
如果创建本地 Bean 需要编码,且路由模板不是用 Java 代码定义的,建议使用 groovy 或 java。
从现有 Java 方法(静态或非静态方法)创建本地 Bean 时可以使用 bean 语言,路由模板不使用 Java 代码定义。
例如,假设有一个名为 com.foo.MyAwsHelper
的类,其中有一个名为 createS3Client
的方法,那么您就可以通过 XML DSL 中的路由模板调用该方法:
<camelContext>
<routeTemplate id="s3template">
<templateParameter name="region"/>
<templateParameter name="bucket"/>
<templateBean name="myClient" type="bean">
<script>com.foo.MyAwsHelper?method=createS3Client</script>
</templateBean>
<route>
<from uri="direct:s3-store"/>
<to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
</route>
</routeTemplate>
</camelContext>
如图所示,createS3Client 的方法签名中必须有一个 RouteTemplateContext
参数:
public static S3Client createS3Client(RouteTemplateContext rtc) {
return S3Client.builder()
.region(rtc.getProperty("region", Region.class))
.build();
}
如果您使用的是纯 Java 代码(包括模板和创建本地 Bean),那么您可以使用 Java lambda 样式创建本地 Bean,如前所述。
配置已创建 Bean 的类型
必须设置 type
以定义所创建 Bean 的 FQN 类别。
<camelContext>
<routeTemplate id="s3template">
<templateParameter name="region"/>
<templateParameter name="bucket"/>
<templateBean name="myClient" scriptLanguage="bean" type="software.amazon.awssdk.services.s3.S3Client">
<script>com.foo.MyAwsHelper?method=createS3Client</script>
</templateBean>
<route>
<from uri="direct:s3-store"/>
<to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
</route>
</routeTemplate>
</camelContext>
而在 Java DSL 中,您可以这样做:
routeTemplate("s3template")
.templateParameter("region")
.templateParameter("bucket")
.templateBean("myClient", S3Client.class, "bean", "com.foo.MyAwsHelper?method=createS3Client")
.from("direct:s3-store")
// must refer to the bean with {{myClient}}
.to("aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}")
创建路由时配置路由模板
在某些特殊情况下,当路由模板即将创建路由时,您可能需要执行一些自定义配置/代码。为此,您可以在路由模板 DSL 中使用 configure
,指定要执行的代码:
routeTemplate("myTemplate")
.templateParameter("myTopic")
.configure((RouteTemplateContext rtc) ->
// do some custom code here
)
.from("direct:to-topic")
.to("kafka:{{myTopic}}");
JMX 管理
路由模板可以通过 ManagedCamelContextMBean
操作以 XML 格式从 ManagedCamelContextMBean
转储。MBean 通过 dumpRouteTemplatesAsXml
操作转储为 XML 格式。
从属性文件创建路由
使用 camel-main
时,您可以在 application.properties
文件中指定路由模板的参数。
例如,下面的路由模板(来自 RouteBuilder
类):
routeTemplate("mytemplate")
.templateParameter("input")
.templateParameter("result")
.from("direct:{{input}}")
.to("mock:{{result}}");
然后,我们可以通过配置 application.properties
文件中的值,从该模板创建两条路由:
camel.route-template[0].template-id=mytemplate
camel.route-template[0].input=foo
camel.route-template[0].result=cheese
camel.route-template[1].template-id=mytemplate
camel.route-template[1].input=bar
camel.route-template[1].result=cheese