第三章 数据转换
本章包括:
使用EIPs和Java两种方式装换数据
转换XML格式的数据
通用数据格式装换
编写转换器
理解Camel的类型装换机制
在现实生活中,人们说不用的语言,在IT世界中,有不同的协议。当进行系统集成时,软件工程师经常需要在各种协议之间充当调解人。为了解决这个问题,使用的数据模型必须从一种形式转换到另一个协议,以适应任何协议发的接收者都能够理解。中介和数据转换是一个关键的特性在camel中。
camel提供了许多数据转换技术,不久,我们将介绍他们。 但首先我们首先概述介绍camel数据转换。
数据转换包括两方面的内容:1.数据格式装换---消息体的格式从一种形式转换为另一种形式:XML---->json。2、数据类型的转换---消息体的格式从一种类型转换为另一种类型:java.lang.Stringis----> javax.jms.TextMessage.
图3.1说明了消息体转换从一种形式到另中形式的原则。这种转换原则适用于任何格式转换和类型转换。在Camel应用中你面临的数据转换大多数情况下都是格式转换,此时你需要在两个协议之间做格式调解。Camel内置了类型转换机制,可以自动在两种类型之间转换,这样就大大减轻了终端用户处理类型转换的负担。
3.1.1 Camel的数据转换
在Camel中,典型的数据转换有以下六种形式,见列表3.1:
转换形式
路由中的数据转换
描述
在路由中,你可以显示的进行数据转换,使用Message Translator或者Content Enricher企业集成模式。这样你可以使用java代码进行数据映射。
转换形式
使用组件进行数据转换
描述
Camel提供了一些组件用于数据转换,例如XSLT组件用于XML转换。
转换形式
数据格式转换
描述
Camel中的数据格式转换都是成对格式出现的,一种格式转换为另一种格式,反之亦然。
转换形式
使用模板进行数据转换
描述
Camel提供了一些组件用于模板转换,例如 Apache Velocity组件。
转换形式
使用Camel的类型转换机制进行数据类型转换
描述
Camel有一个复杂的类型转换程序机制,此机制按需激活。这样方便了常见类型间的转换,如java.lang.Integer类型到java.lang.String类型的换行,java.io.File类型到java.lang.String类型间的转换。
转换形式
组件适配器中的消息转换
描述
Camel的许多组件适应各种常用的协议,因此,需要能够对这些协议的消息进行转换。通常这些组件结合使用自定义数据转换和类型转换器。这种情况无缝地发生,只有组件创建着需要考虑。第十一章详解。
3.2 使用EIP(企业集成模式)和java代码进行数据转换
数据映射是指两个不同的数据模型之间的映射的过程,数据映射是数据集成的一个关键因素。目前关于数据模型的标准有很多,这些标准由不同的机构或者委员会制定。因此,你会经常发现自己需要从公司的自定义数据模型映射到一个标准的数据模型。
Camel中的数据映射给用户提供了很大的自由,因为它允许你使用java代码,并不局限于使用特定数据映射工具,使用工具,起初可能看起来优雅,但结果可能出现不可能解决的问题。
在本节中,我们将会看到如何使用Processor进行数据映射,Processor是一个Camel API。Camel也可以使用bean进行数据映射,这是一个好的做法,因为它允许你的映射逻辑独立于Camel API。
3.2.1 使用消息转换器模式
消息转换器模式如图3.2所示。该模式可以将消息从一种格式转换为另一种格式,类似于设计模式中的适配器模式。
Camel提供了三种方式来使用此模式:
1、使用Processor
2、使用Bean
3、 使用<transform>
使用Processor
Processor是Camel中的一个接口使用Processor,此接口只有一个方法:
public void process(Exchange exchange) throws Exception;
从上述方法可以看出,Processor是一个可以直接操作Camel的Exchange对象的API。它让你可以访问所有在Camel的CamelContext中传输的部分。CamelContext对象可以通过Exchange的getCamelContext方法得到。
让我们看一个例子。在骑士汽车零部件系统中,每天都会把收到的订单输出为CSV格式的文件。公司使用了一个自定义格式的订单实体,但是为了使事情变得简单,他们使用了一个HTTP服务,此服务会根据输入的日期参数返回一个订单列表。你面对的挑战就在于:把HTTP服务返回的数据映射为CSV格式,并写到文件中。
因为你想从一个快速原型开始,您决定使用Camel的Processor.
代码列表3.1 使用Processor将一个自定义格式转换为CSV格式。
首先从exchange中获取自定义格式的内容。它是String类型的,所以你传入了String参数,Exchange返回了String类型的结果。接着你从自定义格式中提取数据到本地变量。自定义格式的内容可以是任意的,但在这个例子中,它是一个定长的自定义格式。接着你通过构建一个以逗号分隔的字符串将自定义格式映射为CSV格式。最后,使用CSV格式的负载替换了自定义格式的负载。
你可以在下列路由中使用上面的OrderToCsvProcessor:
from("quartz://report?cron=0+0+6+*+*+?")
.to(" http://riders.com/orders/cmd=received&date=yesterday")
.process(new OrderToCsvProcessor())
.to("file://riders/orders?fileName=report-${header.Date}.csv");
上述路由使用Quartz组件安排工作在6点每天运行一次。然后它调用返回自定义格式的HTTP服务来检索昨天收到的订单。接下来,它使用OrderToCSVProcessor从自定义格式映射到CSV格式将结果写入文件。
等价的路由在Spring XML中配置如下:
<bean id="csvProcessor" class="camelinaction.OrderToCsvProcessor"/>
<camelContext xmlns=" http://camel.apache.org/schema/spring">
<route>
<from uri="quartz://report?cron=0+0+6+*+*+?"/>
<to uri=" http://riders.com/orders/cmd=received&date=yesterday"/>
<process ref="csvProcessor"/>
<to uri="file://riders/orders?fileName=report-${header.Date}.csv"/>
</route>
</camelContext>
注意:如何使用Exchange的getOut和getIn方法
Exchange定义了两个检索消息的方法:getIn和getOut。getIn方法返回传入的消息,getOut方法访问出站消息。
有两种场景,Camel终端用户需要决定使用哪个方法:
1、只读场景,例如打印传入消息的日志;
2、可写的场景:例如转换消息格式的时候;
在第二种场景中,你可能会使用getOut方法,这在理论上是没错的。但是实践中,这种方式有缺点:传入的消息headers 和 attachments将会丢失。这可能不是你想要的。所以你必须拷贝headers 和 attachments到出站消息中,这个步骤通常是冗长乏味的。变通的方式是使用getIn方法设置消息内容的变化,永远不要使用getOut。
数据映射是指两个不同的数据模型之间的映射的过程,数据映射是数据集成的一个关键因素。目前关于数据模型的标准有很多,这些标准由不同的机构或者委员会制定。因此,你会经常发现自己需要从公司的自定义数据模型映射到一个标准的数据模型。
Camel中的数据映射给用户提供了很大的自由,因为它允许你使用java代码,并不局限于使用特定数据映射工具,使用工具,起初可能看起来优雅,但结果可能出现不可能解决的问题。
在本节中,我们将会看到如何使用Processor进行数据映射,Processor是一个Camel API。Camel也可以使用bean进行数据映射,这是一个好的做法,因为它允许你的映射逻辑独立于Camel API。
3.2.1 使用消息转换器模式
消息转换器模式如图3.2所示。该模式可以将消息从一种格式转换为另一种格式,类似于设计模式中的适配器模式。
Camel提供了三种方式来使用此模式:
1、使用Processor
2、使用Bean
3、 使用<transform>
使用Processor
Processor是Camel中的一个接口使用Processor,此接口只有一个方法:
public void process(Exchange exchange) throws Exception;
从上述方法可以看出,Processor是一个可以直接操作Camel的Exchange对象的API。它让你可以访问所有在Camel的CamelContext中传输的部分。CamelContext对象可以通过Exchange的getCamelContext方法得到。
让我们看一个例子。在骑士汽车零部件系统中,每天都会把收到的订单输出为CSV格式的文件。公司使用了一个自定义格式的订单实体,但是为了使事情变得简单,他们使用了一个HTTP服务,此服务会根据输入的日期参数返回一个订单列表。你面对的挑战就在于:把HTTP服务返回的数据映射为CSV格式,并写到文件中。
因为你想从一个快速原型开始,您决定使用Camel的Processor.
代码列表3.1 使用Processor将一个自定义格式转换为CSV格式。
首先从exchange中获取自定义格式的内容。它是String类型的,所以你传入了String参数,Exchange返回了String类型的结果。接着你从自定义格式中提取数据到本地变量。自定义格式的内容可以是任意的,但在这个例子中,它是一个定长的自定义格式。接着你通过构建一个以逗号分隔的字符串将自定义格式映射为CSV格式。最后,使用CSV格式的负载替换了自定义格式的负载。
你可以在下列路由中使用上面的OrderToCsvProcessor:
from("quartz://report?cron=0+0+6+*+*+?")
.to(" http://riders.com/orders/cmd=received&date=yesterday")
.process(new OrderToCsvProcessor())
.to("file://riders/orders?fileName=report-${header.Date}.csv");
上述路由使用Quartz组件安排工作在6点每天运行一次。然后它调用返回自定义格式的HTTP服务来检索昨天收到的订单。接下来,它使用OrderToCSVProcessor从自定义格式映射到CSV格式将结果写入文件。
等价的路由在Spring XML中配置如下:
<bean id="csvProcessor" class="camelinaction.OrderToCsvProcessor"/>
<camelContext xmlns=" http://camel.apache.org/schema/spring">
<route>
<from uri="quartz://report?cron=0+0+6+*+*+?"/>
<to uri=" http://riders.com/orders/cmd=received&date=yesterday"/>
<process ref="csvProcessor"/>
<to uri="file://riders/orders?fileName=report-${header.Date}.csv"/>
</route>
</camelContext>
注意:如何使用Exchange的getOut和getIn方法
Exchange定义了两个检索消息的方法:getIn和getOut。getIn方法返回传入的消息,getOut方法访问出站消息。
有两种场景,Camel终端用户需要决定使用哪个方法:
1、只读场景,例如打印传入消息的日志;
2、可写的场景:例如转换消息格式的时候;
在第二种场景中,你可能会使用getOut方法,这在理论上是没错的。但是实践中,这种方式有缺点:传入的消息headers 和 attachments将会丢失。这可能不是你想要的。所以你必须拷贝headers 和 attachments到出站消息中,这个步骤通常是冗长乏味的。变通的方式是使用getIn方法设置消息内容的变化,永远不要使用getOut。
使用Processor有一个缺点:必须和Camel的API绑定。
使用bean进行数据转换
使用bean进行数据转换是一个很好的实践,因为这种方式允许你使用任何你需要的java代码或者java类库。Camel对这点没有强加任何限制。Camel可以调用你开发的任意bean,甚至你可以使用已经存在的bean,而不需要重写或者重新编译他们。
让我们使用 bean代替前面的那个Processor。
代码列表3.2
代码列表3.1和3.2的第一个明显的区别是,代码列表3.2没有使用任何Camel的接口。这意味着你的bean彻底独立于Camel API。另一个区别:你可以对方法签名进行命名,在上述代码列表中,方法是一个名为map的静态方法。
方法签名定义了协议,意味着第一个参数String custom,对应将要转换的消息体。方法返回类型为String类型,对应转换后的数据类型为String类型。运行时,Camel会绑定方法签名。这里我们不深究更多的细节,第四章将会详细介绍。相应的DSL和Sping XML如下:
from("quartz://report?cron=0+0+6+*+*+?")
.to(" http://riders.com/orders/cmd=received&date=yesterday")
.bean(new OrderToCsvBean())
.to("file://riders/orders?fileName=report-${header.Date}.csv");
<bean id="csvBean" class="camelinaction.OrderToCsvBean"/>
<camelContext xmlns=" http://camel.apache.org/schema/spring">
<route>
<from uri="quartz://report?cron=0+0+6+*+*+?"/>
<to uri=" http://riders.com/orders/cmd=received&date=yesterday"/>
<bean ref="csvBean"/>
<to uri="file://riders/orders?fileName=report-${header.Date}.csv"/>
</route>
</camelContext>
在DSL中使用TRANSFORM()方法进行数据转换
TRANSFORM()是可以用在路由中的一个方法,可以用来进行消息转换。利用表达式,TRANSFORM()具有极大的灵活性,有时还可以节省时间。例如,假设你需要使用<br/>标记取代所有HTML格式数据中的的换行符。此时可以使用Camel内置的表达式和正则表达式的搜索和替换:
from("direct:start")
.transform(body().regexReplaceAll("\n", "<br/>"))
.to("mock:result");
上述路由使用transform()方法告诉Camel:消息要使用表达式进行转换。Camel利用建造者模式从路由中的表达式创建了整个表达式。这是通过方法调用链接在一起,这是建造者模式的本质。
在这个例子中,你联合使用了body()和regexReplaceAll()表达式,表达式应该这样读:获取body,并执行一个正则表达式,使用<br/>替换所有的\n,两个表达式组成了一个联合Camel表达式。
使用bean进行数据转换是一个很好的实践,因为这种方式允许你使用任何你需要的java代码或者java类库。Camel对这点没有强加任何限制。Camel可以调用你开发的任意bean,甚至你可以使用已经存在的bean,而不需要重写或者重新编译他们。
让我们使用 bean代替前面的那个Processor。
代码列表3.2
代码列表3.1和3.2的第一个明显的区别是,代码列表3.2没有使用任何Camel的接口。这意味着你的bean彻底独立于Camel API。另一个区别:你可以对方法签名进行命名,在上述代码列表中,方法是一个名为map的静态方法。
方法签名定义了协议,意味着第一个参数String custom,对应将要转换的消息体。方法返回类型为String类型,对应转换后的数据类型为String类型。运行时,Camel会绑定方法签名。这里我们不深究更多的细节,第四章将会详细介绍。相应的DSL和Sping XML如下:
from("quartz://report?cron=0+0+6+*+*+?")
.to(" http://riders.com/orders/cmd=received&date=yesterday")
.bean(new OrderToCsvBean())
.to("file://riders/orders?fileName=report-${header.Date}.csv");
<bean id="csvBean" class="camelinaction.OrderToCsvBean"/>
<camelContext xmlns=" http://camel.apache.org/schema/spring">
<route>
<from uri="quartz://report?cron=0+0+6+*+*+?"/>
<to uri=" http://riders.com/orders/cmd=received&date=yesterday"/>
<bean ref="csvBean"/>
<to uri="file://riders/orders?fileName=report-${header.Date}.csv"/>
</route>
</camelContext>
在DSL中使用TRANSFORM()方法进行数据转换
TRANSFORM()是可以用在路由中的一个方法,可以用来进行消息转换。利用表达式,TRANSFORM()具有极大的灵活性,有时还可以节省时间。例如,假设你需要使用<br/>标记取代所有HTML格式数据中的的换行符。此时可以使用Camel内置的表达式和正则表达式的搜索和替换:
from("direct:start")
.transform(body().regexReplaceAll("\n", "<br/>"))
.to("mock:result");
上述路由使用transform()方法告诉Camel:消息要使用表达式进行转换。Camel利用建造者模式从路由中的表达式创建了整个表达式。这是通过方法调用链接在一起,这是建造者模式的本质。
在这个例子中,你联合使用了body()和regexReplaceAll()表达式,表达式应该这样读:获取body,并执行一个正则表达式,使用<br/>替换所有的\n,两个表达式组成了一个联合Camel表达式。
Direct组件
这个例子中使用到了Direct组件作为路由的输入源(from("direct:start"))。Direct组件提供生产者和消费者之间的直接调用。但是调用只在同一个Camel应用中有效,所以外部系统不能直接给direct组件发消息。这个组件经常用于Camel应用中的路由连接或者路由测试。
Camel支持使用自定义表达式。当时想使用java代码对其完全控制时,可以使用自定义表达式。例如:前面的例子可有如下实现方式:
from("direct:start")
.transform(new Expression() {
public <T> T evaluate(Exchange exchange, Class<T> type) {
String body = exchange.getIn().getBody(String.class);
body = body.replaceAll("\n", "<br/>");
body = "<body>" + body + "</body>";
return (T) body;
}
})
.to("mock:result");
在Spring XML中使用<transform>进行数据转换
这种方式不像上一种方式那样强大。在Spring XML中,建造者模式的表达式不可用,因为XML底层没有一种真正的编程语言。你可做的就是调用一个bean的方法或者使用脚本语言。
让我们看下他是如何工作的。下面的路由通过调用一个bean的方法作为表达式:
<bean id="htmlBean" class="camelinaction.HtmlBean"/>
<camelContext id="camel" xmlns=" http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<transform>
<method bean="htmlBean" method="toHtml"/>
</transform>
<to uri="mock:result"/>
</route>
</camelContext>
首先,您声明一个常规的spring bean用于转换消息。然后,在路由中,您使用<transform>和<method>调用了这个Bean。htmlBean的实现非常简单:
public class HtmlBean {
public static String toHtml(String body) {
body = body.replaceAll("\n", "<br/>");
body = "<body>" + body + "</body>";
return body;
}
}
在Camel中,你也可以使用脚本语言作为表达式。例如,你可以使用Groovy,MVEL,JavaScript或者Camel的脚本语言(名为Simple)(见附录A)。在这里我们只演示Simple语言,Simple语言可以只用占位符构建字符串信息:
<transform>
<simple>Hello ${body} how are you?</simple>
</transform>
3.2.2 使用Content Enricher企业集成模式
Content Enricher企业集成模式的阐述见图3.3。这种模式描述了这样一种场景,用来自另一个消息源返回的数据来丰富一个消息的数据。
为了方便理解这个模式,我们再看其实汽车零部件系统。事实证明你在代码清单3.1中所做的数据映射是不能满足需求的。订单被堆积在了FTP服务器上,你的工作就是以某种方式将这些信息合并到现有的报告文件中。图3.4描述了这个场景。
---在图3.4中,使用Quartz在每天6点准时开始路由,
---从HTTP server中获取自定义格式的订单数据
---订单数据被转换为CSV格式
---此时,你必须执行额外的内容丰富步骤,使用从FTP服务器中获得的数据进行丰富。
---接着,最终生成的订单报告别写入文件服务器
在介绍上图路由的实现方式之前,我们需要看一下在Camel中Content Enricher企业集成模式是如何实现的。Camel提供了两个DSL操作用于实现这个模式:
1、pollEnrich---这个操作使用一个消费者合并来自另一个源的数据
2、enrich---这个操作使用一个生产者合并来自另一个源的数据
pollEnrich和enrich两个操作的区别
两者的区别在于pollEnrich使用一个消费者合并来自另一个源的数据,而enrich使用一个生产者合并来自另一个源的数据.了解其中的不同是非常重要的:file组件可以使用这两个操作,但是使用enrich操作,会把信息写入到一个文件中;使用pollEnrich操作,将会读取文件内容作为消息源,这种情况非常类似上面那个场景。HTTP组件只能使用enrich操作,它允许您调用一个外部HTTP服务,使用它的返回结果作为源。
Camel使用org.apache.camel.processor.AggregationStrategy接口来合并原消息和丰富源数据:
Exchange aggregate(Exchange oldExchange, Exchange newExchange);
你需要实现aggregate方法。这个方法有两个参数:第一个参数oldExchange,包含原消息的exchange,第二个参数newExchange,是丰富源数据的exchange。你的工作就是使用java代码合并丰富源数据到原消息,并返回合并结果。听起来可能有些迷惑,让我们看下实际的例子。
为了解决上述骑士汽车零部件系统中的问题,你需要使用pollEnrich操作,因为这个操作是从FTP服务器上轮询获取文件的。
使用pollEnrich丰富消息
代码列表3.3展示了如何使用pollEnrich从远程的FTP服务器上获取额外的订单,并使用AggregationStrategy将这些订单数据合并到已有消息中。
这个路由每天下午六点被Quartz组件触发。调用HTTP服务获取订单,并使用Process把他们转换为CSV格式。此时,你需要使用来自远程FTP服务器上的订单来丰富已有订单数据,这点使用pollEnrich来完成,此操作消费远程文件。
为了合同数据,你说用了AggregationStrategy。首先,判断是否有数据被消费。如果newExchange为null,那么没有远程文件被消费,你只需返回已有数据;如果有远程文件,你通过连接现有的数据与新数据实现合并,并把合并后的数据设置到oldExchange中。接着,返回合并后的数据(oldExchange)。最后,使用file组件生成CSV报告文件。
PollEnrich使用一个轮询消费者来检索消息,这个操作提供了三种超时模式:
---pollEnrich(timeout = -1) 轮询消息并等待,直到一个消息到达。这种模式在消息到达前将阻塞。
---pollEnrich(timeout = 0) 如果消息存在,立即检索消息;否则返回null。这种模式不会等待消息的到达,所以此模式不会阻塞。这是默认选中的模式。
---pollEnrich(timeout > 0) 检索消息,如果没有消息存在,等待,最多等待到超时。这种模式将可能阻塞。
最佳实践方式是使用timeout=0或者给timeout赋一个固定值,避免没有消息到达时一直等待。
注意:
Enrich和pollEnrich不能访问当前的exchange中的信息。也就是说,例如,你不能通过设置当前exchange的filename头部,使pollEnrich选择一个特定的文件,因为pollEnrich无法读取当前exchange的头部信息。这一点在Camel的未来版本中可能会支持。
使用enrich来丰富消息
当你使用请求-响应消息返回的数据来丰富当前消息时,需要使用enrich操作。比如用webService返回的结果来丰富当前消息。来看另一个例子,使用Spring XML,使用TCP传输丰富当前的消息:
<bean id="quoteStrategy"
class="camelinaction.QuoteStrategy"/>
<route>
<from uri="activemq:queue:quotes"/>
<enrich url="mina:tcp://riders.com:9876?textline=true&sync=true"
strategyRef="quoteStrategy"/>
<to uri="log:quotes"/>
</route>
这个例子中使用到了Direct组件作为路由的输入源(from("direct:start"))。Direct组件提供生产者和消费者之间的直接调用。但是调用只在同一个Camel应用中有效,所以外部系统不能直接给direct组件发消息。这个组件经常用于Camel应用中的路由连接或者路由测试。
Camel支持使用自定义表达式。当时想使用java代码对其完全控制时,可以使用自定义表达式。例如:前面的例子可有如下实现方式:
from("direct:start")
.transform(new Expression() {
public <T> T evaluate(Exchange exchange, Class<T> type) {
String body = exchange.getIn().getBody(String.class);
body = body.replaceAll("\n", "<br/>");
body = "<body>" + body + "</body>";
return (T) body;
}
})
.to("mock:result");
在Spring XML中使用<transform>进行数据转换
这种方式不像上一种方式那样强大。在Spring XML中,建造者模式的表达式不可用,因为XML底层没有一种真正的编程语言。你可做的就是调用一个bean的方法或者使用脚本语言。
让我们看下他是如何工作的。下面的路由通过调用一个bean的方法作为表达式:
<bean id="htmlBean" class="camelinaction.HtmlBean"/>
<camelContext id="camel" xmlns=" http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<transform>
<method bean="htmlBean" method="toHtml"/>
</transform>
<to uri="mock:result"/>
</route>
</camelContext>
首先,您声明一个常规的spring bean用于转换消息。然后,在路由中,您使用<transform>和<method>调用了这个Bean。htmlBean的实现非常简单:
public class HtmlBean {
public static String toHtml(String body) {
body = body.replaceAll("\n", "<br/>");
body = "<body>" + body + "</body>";
return body;
}
}
在Camel中,你也可以使用脚本语言作为表达式。例如,你可以使用Groovy,MVEL,JavaScript或者Camel的脚本语言(名为Simple)(见附录A)。在这里我们只演示Simple语言,Simple语言可以只用占位符构建字符串信息:
<transform>
<simple>Hello ${body} how are you?</simple>
</transform>
3.2.2 使用Content Enricher企业集成模式
Content Enricher企业集成模式的阐述见图3.3。这种模式描述了这样一种场景,用来自另一个消息源返回的数据来丰富一个消息的数据。
为了方便理解这个模式,我们再看其实汽车零部件系统。事实证明你在代码清单3.1中所做的数据映射是不能满足需求的。订单被堆积在了FTP服务器上,你的工作就是以某种方式将这些信息合并到现有的报告文件中。图3.4描述了这个场景。
---在图3.4中,使用Quartz在每天6点准时开始路由,
---从HTTP server中获取自定义格式的订单数据
---订单数据被转换为CSV格式
---此时,你必须执行额外的内容丰富步骤,使用从FTP服务器中获得的数据进行丰富。
---接着,最终生成的订单报告别写入文件服务器
在介绍上图路由的实现方式之前,我们需要看一下在Camel中Content Enricher企业集成模式是如何实现的。Camel提供了两个DSL操作用于实现这个模式:
1、pollEnrich---这个操作使用一个消费者合并来自另一个源的数据
2、enrich---这个操作使用一个生产者合并来自另一个源的数据
pollEnrich和enrich两个操作的区别
两者的区别在于pollEnrich使用一个消费者合并来自另一个源的数据,而enrich使用一个生产者合并来自另一个源的数据.了解其中的不同是非常重要的:file组件可以使用这两个操作,但是使用enrich操作,会把信息写入到一个文件中;使用pollEnrich操作,将会读取文件内容作为消息源,这种情况非常类似上面那个场景。HTTP组件只能使用enrich操作,它允许您调用一个外部HTTP服务,使用它的返回结果作为源。
Camel使用org.apache.camel.processor.AggregationStrategy接口来合并原消息和丰富源数据:
Exchange aggregate(Exchange oldExchange, Exchange newExchange);
你需要实现aggregate方法。这个方法有两个参数:第一个参数oldExchange,包含原消息的exchange,第二个参数newExchange,是丰富源数据的exchange。你的工作就是使用java代码合并丰富源数据到原消息,并返回合并结果。听起来可能有些迷惑,让我们看下实际的例子。
为了解决上述骑士汽车零部件系统中的问题,你需要使用pollEnrich操作,因为这个操作是从FTP服务器上轮询获取文件的。
使用pollEnrich丰富消息
代码列表3.3展示了如何使用pollEnrich从远程的FTP服务器上获取额外的订单,并使用AggregationStrategy将这些订单数据合并到已有消息中。
这个路由每天下午六点被Quartz组件触发。调用HTTP服务获取订单,并使用Process把他们转换为CSV格式。此时,你需要使用来自远程FTP服务器上的订单来丰富已有订单数据,这点使用pollEnrich来完成,此操作消费远程文件。
为了合同数据,你说用了AggregationStrategy。首先,判断是否有数据被消费。如果newExchange为null,那么没有远程文件被消费,你只需返回已有数据;如果有远程文件,你通过连接现有的数据与新数据实现合并,并把合并后的数据设置到oldExchange中。接着,返回合并后的数据(oldExchange)。最后,使用file组件生成CSV报告文件。
PollEnrich使用一个轮询消费者来检索消息,这个操作提供了三种超时模式:
---pollEnrich(timeout = -1) 轮询消息并等待,直到一个消息到达。这种模式在消息到达前将阻塞。
---pollEnrich(timeout = 0) 如果消息存在,立即检索消息;否则返回null。这种模式不会等待消息的到达,所以此模式不会阻塞。这是默认选中的模式。
---pollEnrich(timeout > 0) 检索消息,如果没有消息存在,等待,最多等待到超时。这种模式将可能阻塞。
最佳实践方式是使用timeout=0或者给timeout赋一个固定值,避免没有消息到达时一直等待。
注意:
Enrich和pollEnrich不能访问当前的exchange中的信息。也就是说,例如,你不能通过设置当前exchange的filename头部,使pollEnrich选择一个特定的文件,因为pollEnrich无法读取当前exchange的头部信息。这一点在Camel的未来版本中可能会支持。
使用enrich来丰富消息
当你使用请求-响应消息返回的数据来丰富当前消息时,需要使用enrich操作。比如用webService返回的结果来丰富当前消息。来看另一个例子,使用Spring XML,使用TCP传输丰富当前的消息:
<bean id="quoteStrategy"
class="camelinaction.QuoteStrategy"/>
<route>
<from uri="activemq:queue:quotes"/>
<enrich url="mina:tcp://riders.com:9876?textline=true&sync=true"
strategyRef="quoteStrategy"/>
<to uri="log:quotes"/>
</route>
在这里你使用了Camel的mina组件来进行TCP传输,通过使用sync=true配置选项实现了请求--响应消息。要合并原消息和远程返回的数据,<enrich>元素需要引用一个AggregationStrategy对象,代码中通过strategyRef属性实现了这一点。正如你所看到的,strategyRef属性所引用的quoteStrategy是个bean的id,此id就是AggregationStrategy的实现类bean的id,这个地方也是合并发生所在地。
3.3 XML格式的数据转换 (此部分略)
3.4 数据格式转换
在Camel中,数据格式转换时可插拔的数据转换,可将消息从一种形式转换为另一种形式。Camel中,每一种数据格式转换都由接口org.apace.camel.spi.DataFormat代表,此接口包含两个方法:
1、marshal方法:封存一个消息为另一种形式,比如封存java对象为XML, CSV, EDI, HL7等数据模型;即对象--->二进制
2、unmarshal方法:进行一种反向操作,将某种格式的数据转换为消息,即二进制--->对象
见图3.6所示。
在3.3节我们提到了数据格式,我们讨论了XML转换。本节将深度介绍数据格式,这次我们不使用XML数据类型,使用CSV和JSON数据类型。还将学习如何创建自定义的数据格式。我们首先简要看一下Camel提供的开箱即用的数据格式。
3.4.1Camel提供的数据格式
Camel为一些众所周知的数据模型提供了一系列的数据格式,如表3.3所示。
如表中所示,Camel提供了18中开箱即用的数据格式,我们只介绍其中最常用的三种。详细信息参见: http://camel.apache.org/data-format.html.
3.4.2 使用CSV数据格式
3.4.3 使用Bindy数据格式
3.4.4 使用json数据格式
JSON(JavaScript对象表示法)是一种数据交换格式,Camel提供了两个组件,支持JSON数据格式:camel-xstream和camel-jackson。这一节关注camel-jackson。
回到骑士汽车配件系统,你现在必须实现一个新的服务,此服务返回JSON格式的订单摘要。在Camel中实现这个服务是非常容易的。快速构建的一个模型见代码列表3.9
<bean id="orderService" class="camelinaction.OrderServiceBean"/>
<camelContext id="camel" xmlns=" http://camel.apache.org/schema/spring">
<dataFormats>
<json id="json" library="Jackson"/>
</dataFormats>
<route>
<from uri="jetty:// http://0.0.0.0:8080/order"/>
<bean ref="orderService" method="lookup"/>
<marshal ref="json"/>
</route>
</camelContext>
首先你需要设置的JSON数据格式,指定使用Jackson类库。然后你定义一个路由,使用Jetty作为HTTP服务端点。这个服务将请求路由到orderService bean上面,调用bean的lookup方法。此方法返回的结果被marshal为json格式的数据,然后返回给HTTP客户端。
orderService bean可以有这样一个方法:
public PurchaseOrder lookup(@Header(name = "id") String id)
这个签名允许您实现查找逻辑。在4.5.3节中你会了解更多关于@Header注释。
注意这个bean方法返回一个POJO对象,JSON类库可以对这个POJO进行marshal,比如你使用了代码列表3.7中的PurchaseOrder对象,那么会生成下面的json字符串:
{"name":"Camel in Action","amount":1.0,"price":49.95}
HTTP服务本身可以被一个GET请求调用,使用id作为请求参数:
http://0.0.0.0:8080/order/service?id=123.
3.3 XML格式的数据转换 (此部分略)
3.4 数据格式转换
在Camel中,数据格式转换时可插拔的数据转换,可将消息从一种形式转换为另一种形式。Camel中,每一种数据格式转换都由接口org.apace.camel.spi.DataFormat代表,此接口包含两个方法:
1、marshal方法:封存一个消息为另一种形式,比如封存java对象为XML, CSV, EDI, HL7等数据模型;即对象--->二进制
2、unmarshal方法:进行一种反向操作,将某种格式的数据转换为消息,即二进制--->对象
见图3.6所示。
在3.3节我们提到了数据格式,我们讨论了XML转换。本节将深度介绍数据格式,这次我们不使用XML数据类型,使用CSV和JSON数据类型。还将学习如何创建自定义的数据格式。我们首先简要看一下Camel提供的开箱即用的数据格式。
3.4.1Camel提供的数据格式
Camel为一些众所周知的数据模型提供了一系列的数据格式,如表3.3所示。
如表中所示,Camel提供了18中开箱即用的数据格式,我们只介绍其中最常用的三种。详细信息参见: http://camel.apache.org/data-format.html.
3.4.2 使用CSV数据格式
3.4.3 使用Bindy数据格式
3.4.4 使用json数据格式
JSON(JavaScript对象表示法)是一种数据交换格式,Camel提供了两个组件,支持JSON数据格式:camel-xstream和camel-jackson。这一节关注camel-jackson。
回到骑士汽车配件系统,你现在必须实现一个新的服务,此服务返回JSON格式的订单摘要。在Camel中实现这个服务是非常容易的。快速构建的一个模型见代码列表3.9
<bean id="orderService" class="camelinaction.OrderServiceBean"/>
<camelContext id="camel" xmlns=" http://camel.apache.org/schema/spring">
<dataFormats>
<json id="json" library="Jackson"/>
</dataFormats>
<route>
<from uri="jetty:// http://0.0.0.0:8080/order"/>
<bean ref="orderService" method="lookup"/>
<marshal ref="json"/>
</route>
</camelContext>
首先你需要设置的JSON数据格式,指定使用Jackson类库。然后你定义一个路由,使用Jetty作为HTTP服务端点。这个服务将请求路由到orderService bean上面,调用bean的lookup方法。此方法返回的结果被marshal为json格式的数据,然后返回给HTTP客户端。
orderService bean可以有这样一个方法:
public PurchaseOrder lookup(@Header(name = "id") String id)
这个签名允许您实现查找逻辑。在4.5.3节中你会了解更多关于@Header注释。
注意这个bean方法返回一个POJO对象,JSON类库可以对这个POJO进行marshal,比如你使用了代码列表3.7中的PurchaseOrder对象,那么会生成下面的json字符串:
{"name":"Camel in Action","amount":1.0,"price":49.95}
HTTP服务本身可以被一个GET请求调用,使用id作为请求参数:
http://0.0.0.0:8080/order/service?id=123.
3.4.5 配置Camel数据格式
3.4.6 自定义数据格式
在项目中,一个经常要做的事情就是用自定义的数据格式进行数据转换。这一节,我们将看看如何开发一个数据格式,可以翻转字符串
开发自定义数据格式是非常简单的,因为你只需要实现Camel的一个接口:org.apache.camel.spi.DataFormat.让我们看看如何实现一个字符串翻转功能的数据格式。见代码列表3.11.
---实现DataFormat接口:marshal和unmarshal两个方法。
---marshal方法需要向OutputStream中输出结果,所以你需要以byte[]的形式获取消息负载(使用Camel的类型装换器实现),接着在一个工具方法中翻转它;接着将数据写入OutputStream;
---unmarshal方法中:再次使用Camel的类型转换器获取消息负载。unmarshal方法同样对消息负载进行了翻转。在unmarshal方法中直接返回了数据而不是写入IO流中。
在路由中使用这个新数据格式,需要把这个数据格式定义为Spring bean,并且在<marshal>和<unmarshal>元素中引用这个bean:
<bean id="reverse" class="camelinaction.ReverseDataFormat"/>
<camelContext id="camel" xmlns=" http://camel.apache.org/schema/spring">
<route>
<from uri="direct:marshal"/>
<marshal ref="reverse"/>
<to uri="log:marshal"/>
</route>
<route>
<from uri="direct:unmarshal"/>
<unmarshal ref="reverse"/>
<to uri="log:unmarshal"/>
</route>
</camelContext>
3.5 使用模板进行数据转换
3.6 关于camel类型转换器
3.4.6 自定义数据格式
在项目中,一个经常要做的事情就是用自定义的数据格式进行数据转换。这一节,我们将看看如何开发一个数据格式,可以翻转字符串
开发自定义数据格式是非常简单的,因为你只需要实现Camel的一个接口:org.apache.camel.spi.DataFormat.让我们看看如何实现一个字符串翻转功能的数据格式。见代码列表3.11.
---实现DataFormat接口:marshal和unmarshal两个方法。
---marshal方法需要向OutputStream中输出结果,所以你需要以byte[]的形式获取消息负载(使用Camel的类型装换器实现),接着在一个工具方法中翻转它;接着将数据写入OutputStream;
---unmarshal方法中:再次使用Camel的类型转换器获取消息负载。unmarshal方法同样对消息负载进行了翻转。在unmarshal方法中直接返回了数据而不是写入IO流中。
在路由中使用这个新数据格式,需要把这个数据格式定义为Spring bean,并且在<marshal>和<unmarshal>元素中引用这个bean:
<bean id="reverse" class="camelinaction.ReverseDataFormat"/>
<camelContext id="camel" xmlns=" http://camel.apache.org/schema/spring">
<route>
<from uri="direct:marshal"/>
<marshal ref="reverse"/>
<to uri="log:marshal"/>
</route>
<route>
<from uri="direct:unmarshal"/>
<unmarshal ref="reverse"/>
<to uri="log:unmarshal"/>
</route>
</camelContext>
3.5 使用模板进行数据转换
3.6 关于camel类型转换器
Camel提供了一个内置的类型转换系统,对常用类型之间进行自动转换。这个类型转换系统使Camel的各个组件直接可以很容易的协调工作,不会出现类型转换错误。从Camel用户的角度来看,在很多地方类型转换是内置在API中的,没有侵入性。例如,在代码列表3.1中有如下代码:
String custom = exchange.getIn().getBody(String.class);
getBody方法接收了你所期望返回的类型为参数。在底层,类型转换系统将返回的结果转换成了Sring类型(如果需要的话)。
在本节中,我们将看看类型转换程序的内部机制。我们将解释Camel在启动时如何扫描类路径,进行动态注册类型转换器。我们还将向您展示在Camel路由中如何使用它,以及如何建立你自己的类型转换器。
3.6.1 Camel类型转换器的运行机制
理解类型转换器的运行机制,首先需要理解在Camel中类型转换器是什么。图3.7展示了TypeConverterRegistry和TypeConverters之间的关系。
在Camel启动时,所有的类型转换器都会注册到TypeConverterRegistry中。在运行时,Camel使用TypeConverterRegistry的lookup方法查找一个合适的TypeConverter来使用:
TypeConverter lookup(Class<?> toType, Class<?> fromType);
使用TypeConverter的convertTo方法,Camel可以将一种类型转换为另一种类型:<T> T convertTo(Class<T> type, Object value);
注意:Camel提供了150以上的开箱即用的类型转换器,涵盖了常用的类型转换。
加载类型转换器到注册表中
Camel启动时,使用org.apache.camel.impl.converter.AnnotationTypeConverterLoader类通过扫描类路径的方式将所有的类型转换器加载到TypeConverterRegistry中。为了避免扫描数量巨大的类,Camel会读取META-INF文件夹中的一个服务发现文件:META-INF/services/org/apache/camel/TypeConverter.这是一个文本文件,包含一个java包列表,这些包中包含了Camel的类型转换器。利用这个特殊的文件,避免了扫描数量巨大的类,提高了性能。此文件告诉Camel哪个jar包中包含类型转换器。camel-core核心包中有这样一个文件,文件内容包含如下三个条目:
org.apache.camel.converter
org.apache.camel.component.bean
org.apache.camel.component.file
AnnotationTypeConverterLoader类将会扫描这些包以及子包,寻找有@Converter的类以及这样的类中有@Converter的公有方法。每一个这样的方法被称为一个类型转换器。
例如:下面的代码是camel-core包中的IOConverter类的代码片段:
@Converter
public final class IOConverter {
@Converter
public static InputStream toInputStream(URL url) throws IOException {
return url.openStream();
}
}
Camel会看每一个@Converter注解的方法的签名。方法的第一个参数是from(输入消息)的类型,返回的类型是to(输出消息)的类型。在这个例子中,这个转换器可以将URL类型转换为InputStream类型。
3.6.2 使用Camel类型转换器
如前所述,Camel类型转换器被广泛使用,而且常常自动起作用。你可能要在一个路由中使用它们转换一个特定的类型,假设你需要将一些文件路由到JMS队列,并且使用javax.jmx.TextMessage类型的消息。你可以将每一个文件转换为String类型,迫使JMS组件使用TextMessage,Camel是很容易做到的---使用convertBodyTo方法,如下:
from("file://riders/inbox")
.convertBodyTo(String.class)
.to("activemq:queue:inbox");
如果使用Spring XML,如下:
<route>
<from uri="file://riders/inbox"/>
<convertBodyTo type="java.lang.String"/>
<to uri="activemq:queue:inbox"/>
</route>
你可以省略java.lang.前缀:
<convertBodyTo type="String"/>.
如果想使用某一特定的编码读取文件:
from("file://riders/inbox")
.convertBodyTo(String.class, "UTF-8")
.to("activemq:queue:inbox");
提示:如果你对路由中的负载或者负载类型比较迷惑,可以试着在路由开始增加.convertBodyTo(String.class)部分,将负载转换为String类型,如果转换失败,抛出NoTypeConversionAvailableException异常。
这就是在路由中使用Camel类型转换器的方法。
3.6.3 自定义类型转换器
在Camel中编写自己的类型转换器是很容易的。在3.6.1节你已经看到类型转换器是什么样子.假设你想写一个converter用于将一个byte[]转换为PurchaseOrder对象(代码列表3.7中的一个对象)。那么,你需要创建一个@Converter注解的类,类中包含转换方法,代码列表3.12:
代码中,通过Exchange获取CamelContext,进而获取上级TypeConverter,用其进行String和byte转换。
现在你所需要做的是添加服务发现文件,文件名为:TypeConverter,位于META-INF目录中。正如前面所解释的那样,这个文件包含一行内容,用于Camel扫描这个包进而发现@Converter注解类。内容为:
camelinaction