Camel In Action 第四章 Camel中bean的使用

  • Post author:
  • Post category:其他



第四章 Camel中bean的使用



本章包括:



1、理解Service Activator企业设计模式



2、Camel如何使用注册中心查找bean



3、Camel如何调用bean方法



4、单个参数绑定与多个参数绑定




4.1 使用bean的简单方式和复杂方式



在本节中,我们看一个例子,这个例子展示了使用bean的复杂方式(不建议这样使用),接着看看使用bean的简单方式。



假设你有一个已经存在的bean,这个bean提供了一个操作(服务),此操作在集成应用中使用。例如:HelloBean提供了hello方法作为服务:



public class HelloBean {



public String hello(String name) {



return “Hello ” + name;



}



}



让我们看看在您的应用程序中使用这个bean的一些不同的方式。



4.1.1 纯java调用bean



使用Camel的Processor,可以实现纯java调用bean,见代码列表4.1public class InvokeWithProcessorRoute extends RouteBuilder {



public void configure() throws Exception {



from(“direct:hello”)



.process(new Processor() {



public void process(Exchange exchange) throws Exception {



String name = exchange.getIn().getBody(String.class);



HelloBean hello = new HelloBean();



String answer = hello.hello(name);



exchange.getOut().setBody(answer);



}



});



}




代码列表中展示了一个定义了路由的RouteBuilder类,使用了内联的Processor,可以在Processor的process方法中使用java代码操作消息。首先,从输入消息中获取消息体作为调用bean方法的参数。接着,实例化bean,并调用。最后,把方法的返回结果设置到输出消息中。



现在,你已经使用javaDSL完成了bean的调用,下面看下使用Spring XML的方式。



4.1.2 调用在spring中定义的一个bean



见代码列表4.2 4.3




<bean id=”helloBean” class=”camelinaction.HelloBean”/>



<bean id=”route” class=”camelinaction.InvokeWithProcessorSpringRoute”/>



<camelContext id=”camel” xmlns=”


http://camel.apache.org/schema/spring


“>



<routeBuilder ref=”route”/>



</camelContext>




public class InvokeWithProcessorSpringRoute extends RouteBuilder {



@Autowired



private HelloBean hello;



public void configure() throws Exception {



from(“direct:hello”)



.process(new Processor() {



public void process(Exchange exchange) throws Exception {



String name = exchange.getIn().getBody(String.class);



String answer = hello.hello(name);



exchange.getOut().setBody(answer);



}



});



}



}



到目前为止,您已经看到了两个在路由中使用bean的例子,为什么这是一种使用bean的复杂方式?下面是原因:



—你必须使用java代码调用bean



—你必须使用Processor,和路由耦合在一起,造成难以理解(路由逻辑与业务实现逻辑耦合在了一起)。



—你必须从消息中提取数据,然后传给bean,而且需要将bean的响应设置到Camel消息中。



—你需要手动实例化bean,或者使用依赖注入



下面我们来看下简单方式的bean使用:




4.1.3 使用bean的简单方式



假设你在Spring XML中定义了Camel路由,而不是使用RouteBuilder类。见下面的代码片段:



<bean id=”helloBean” class=”camelinaction.HelloBean”/>



<camelContext id=”camel” xmlns=”


http://camel.apache.org/schema/spring


“>



<route>



<from uri=”direct:start”/>



< What goes here >



</route>



</camelContext>




首先在Spring中定义bean,接着定义路由,使用direct:start作为路由输入。在之后,你准备调用HelloBean,但是你迷茫了,因为这是在XML中,你不能在XML中编写java代码。



在Camel中,使用bean的简单方式是:用<bean>标签:



<bean ref=”helloBean” method=”hello”/>



完整的配置如下:



<camelContext id=”camel” xmlns=”


http://camel.apache.org/schema/spring


“>



<route>



<from uri=”direct:start”/>



<bean ref=”helloBean” method=”hello”/>



</route>



</camelContext>




在java DSL中,Camel提供了相同的解决方式:



public void configure() throws Exception {



from(“direct:hello”).beanRef(“helloBean”, “hello”);



}



代码从8行减为1行。而且这一行代码很容易理解。你甚至可以省略hello方法,因为bean中只有一个方法:



public void configure() throws Exception {



from(“direct:hello”).beanRef(“helloBean”);



}




使用<bean>标签是一个优雅的使用bean的解决方案。不使用<bean>标签,你需要使用Processor调用bean,这是一个乏味的解决方案。




提示:



在java DSL中,你不需要在注册表中预先注册bean。相反,你可以提供bean的类名,camel将在启动时实例化这个bean。前面的例子可以简写为下面这样:



from(“direct:hello”).bean(HelloBean.class);




现在让我们从企业集成模式的视角看看如何在Camel中使用bean。




4.2 企业集成模式—Service Activator模式



此模式的含义是:一个服务,既能通过各种消息传递技术调用,也能通过非消息传递技术调用。图4.1说明了这一原则。




图4.1展示了一个服务激活组件,此组件依据请求调用相应的服务并返回应答。服务激活组件扮演了请求和POJO服务之间的中介角色。请求者发送一个请求到服务激活组件,服务激活组件负责将请求转换为POJO服务理解的格式,然后传递请求到POJO服务上。POJO服务会返回一个应答到服务激活组件,服务激活组件将应答传递给正在等待的请求者。



正如图4.1中所展示的,服务由POJO提供,服务激活器是Camel中的一个组件,可以转换请求和调用服务。这个组件就是Bean组件,此组件实际使用了org.apache.camel.component.bean.BeanProcessor类完成了这个任务。我们在4.4节中学习BeanProcessor。你应该注意的是,在Camel中,Camel使用Bean组件实现了Service Activator模式。




图4.2展示了图4.1与4.1.3节中路由代码之间的对应。



在你使用一个bean之前,你需要知道到哪里去寻找它。这就需要用到注册表。让我们来看看Camel是如何与不同的注册表协调工作的。




4.3 Camel的bean注册表




当Camel与bean一起工作时,Camel会在注册表中找到相应的bean。Camel的哲学是:利用最好的成熟框架,使用一个可插拔的注册体系结构来集成这个框架。Spring就是这样一个框架,图4.3展示了注册表是如何工作的。



图4.3展示了:Camel注册表是调用者和真实注册表之间的一个抽象。当一个请求需要查找一个bean,就会使用Camel的注册表,接着Camel注册表会到真实注册表中查找这个bean,最后bean返回响应。这种结构是一个松耦合、可插拔的结构,可以与多个注册表集成。请求者只需知道如何与Camel注册表交互。



在Camel中,Camel注册表只是一个服务提供接口:



org.apache.camel.spi.Registry



包含以下方法:



Object lookup(String name);



<T> T lookup(String name, Class<T> type)



<T> Map<String, T> lookupByType(Class<T> type)




你经常使用的会是前两个方法,通过bean名称查找bean。例如,查找HelloBean:



HelloBean hello = (HelloBean) context.getRegistry().lookup(“helloBean”);



为了避免类型转换,可以使用第二个方法:



HelloBean hello = context.getRegistry().lookup(“helloBean”, HelloBean.class);



提示:



第二种方法提供类型安全的查找,因为你提供了预期的类作为第二个参数。在内部,Camel使用类型转换机制将bean转换为所需的类型,如果必要的话。




接口中的最后一个方法,lookupType,经常由Camel内部使用,支持约定优于配置。Camel可以直接根据bean类型查找,而不需要知道bean名称。




注册表本身是抽象的,因此只是一个接口。表4.1中列出了Camel自带的四个实现类:



SimpleRegistry



这个简单实现用于单元测试或者在Google App engine中运行Camel时。




JndiRegistry



此实现使用已经存在的JNDI来查找bean





ApplicationContextRegistry



此实现与Spring一起工作,在Spring的ApplicationContext中查找bean。当在Spring环境中使用Camel时,此实现自动被使用。




OsgiServiceRegistry



此实现在OSGi服务注册表中查找bean。在OSGI环境中使用Camel时,此实现自动被使用。



在下面几节中,我们将学习这四个实现。




4.3.1 SimpleRegistry




SimpleRegistry是一个基于Map的注册表,用于单元测试和Camel独立运行的时候。



例如,如果你想对HelloBean进行单元测试,你可以注册HelloBean到SimpleRegistry中,然后在路由中引用它。




public class SimpleRegistryTest extends TestCase {



private CamelContext context;



private ProducerTemplate template;



protected void setUp() throws Exception {



SimpleRegistry registry = new SimpleRegistry();



registry.put(“helloBean”, new HelloBean());



context = new DefaultCamelContext(registry);



template = context.createProducerTemplate();



context.addRoutes(new RouteBuilder() {



public void configure() throws Exception {



from(“direct:hello”).beanRef(“helloBean”);



}



});



context.start();



}



protected void tearDown() throws Exception {



template.stop();



context.stop();



}



public void testHello() throws Exception {



Object reply = template.requestBody(“direct:hello”, “World”);



assertEquals(“Hello World”, reply);



}



}




首先创建一个SimpleRegistry实例,注册HelloBean,注册名为helloBean。接着,注册表与Camel联合使用(将注册表作为一个参数传递给DefaultCamelContext构造函数)。为了方便测试,创建了一个ProducerTemplate,用于在测试方法中向Camel发送消息,最后,当测试完成时,清理资源。在路由中,使用beanRef方法调用HelloBean。





4.3.2 JndiRegistry



JndiRegistry,顾名思义,基于JNDI的注册表。这是Camel集成的第一个注册表,当你创建Camel实例时没有提供注册表,默认注册表就是JndiRegistry:



CamelContext context = new DefaultCamelContext();




JndiRegistry注册表,与SimpleRegistry注册表类似,常用于单元测试或者Camel独立运行的时候。Camel中的许多单元测试都使用了JndiRegistry,因为这个测试用例在SimpleRegistry注册表添加到Camel之前就创建好了。




当Camel和JavaEE应用服务器(提供了一个开箱即用的JNDI注册表)一起使用时,JndiRegistry是非常有用的。假如你想利用WebSphere应用服务器的JNDI注册表,可以使用如下代码片段:



protected CamelContext createCamelContext() throws Exception {



Hashtable env = new Hashtable();



env.put(Context.INITIAL_CONTEXT_FACTORY,



“com.ibm.websphere.naming.WsnInitialContextFactory”);



env.put(Context.PROVIDER_URL,



“corbaloc:iiop:myhost.mycompany.com:2809”);



env.put(Context.SECURITY_PRINCIPAL, “username”);



env.put(Context.SECURITY_CREDENTIALS, “password”);



Context ctx = new InitialContext(env);



JndiRegistry jndi = new JndiRegistry(ctx);



return new DefaultCamelContext(jndi);



}



你需要使用一个Hashtable来存储JNDI注册表信息,然后,创建一个JndiRegistry需要的javax.naming.Context对象。




Camel允许你在Spring XML中使用JndiRegistry。你需要做的就是定义一个bean,Camel会自动加载:



<bean id=”registry” class=”org.apache.camel.impl.JndiRegistry”/>



可以使用Spring依赖注入语法,注入Hashtable到JndiRegistry的构造函数中。




4.3.3 ApplicationContextRegistry



Camel与Spring一起使用时,ApplicationContextRegistry是默认注册表。更准确地说,当你在Spring XML中如下设置Camel时,ApplicationContextRegistry是默认注册表。



<camelContext id=”camel” xmlns=”


http://camel.apache.org/schema/spring


“>



<route>



<from uri=”direct:start”/>



<bean ref=”helloBean” method=”hello”/>



</route>



</camelContext>



使用<camelContext>标签定义Camel,会使Camel使用ApplicationContextRegistry注册表。此注册表允许你在Spring XML文件中定义bean。例如,是可以定义helloBean:



<bean id=”helloBean” class=”camelinaction.HelloBean”/>



没有比这更简单的了。当你使用Camel和Spring时,你可以像平时一样使用Spring bean,Camel会自动引用到这些bean,无需任何配置。




4.3.4 OsgiServiceRegistry



当Camel用于OSGi环境中,Camel将使用两步走的查找策略。第一步,在OSGi服务注册表中查找对应名称的服务,如果没有,第二步,在常规注册表中查找,例如ApplicationContextRegistry.




假设你想将HelloBean暴露为OSGI的一个服务,你可以这样做:



<osgi:service id=”helloService” interface=”camelinaction.HelloBean”



ref=”helloBean”/>



<bean id=”helloBean” class=”camelinaction.HelloBean”/>



在Spring动态模块(Spring DM)提供的osgi:service命名空间的帮助下你将HelloBean注册为OSGI注册表的服务,服务名称为helloService。与4.3.3节中Camel引用HelloBean的方式一样,Camel通过OSGI服务名称来引用服务:



<camelContext id=”camel” xmlns=”


http://camel.apache.org/schema/spring


“>



<route>



<from uri=”direct:start”/>



<bean ref=”helloService” method=”hello”/>



</route>



</camelContext>




它是那么简单。你需要记住的是暴露的服务的名称。Camel会在OSGi服务注册表和Spring bean容器中查找服务。这是约定优于配置




提示:在第十三章中,我们会再次学习OSGI。




有关注册表的旅行到此结束。接下来学习Camel如何选择Bean方法。




4.4 选择Bean方法



你已经从路由的视角看到了Camel与bean如何协调工作的。现在,是时候深入学习一下了。你首先要理解的是Camel选择调用Bean方法的内部机制。




记住,Camel扮演了一个服务激活器的角色(利用BeanProcessor),位于调用者和真实bean之间。在编译阶段,没有直接的绑定,JVM不能链接到bean的调用者–Camel在运行是解决这个问题。




图4.4展示了BeanProcessor如何利用注册表来查找要调用的bean的:




在运行是,Camel的exchange被路由,在路由中的某个点,到达BeanProcessor。BeanProcessor处理exchange,处理步骤如下:



1、在注册表中查找bean



2、选择所调用bean的方法



3、绑定参数到选中的方法上(例如,使用输入消息的body作为一个参数,具体细节在4.5节讨论)



4、调用方法



5、处理调用过程中的错误(从bean中抛出的任何异常都会设置到Camel的exchange中,以备进一步处理)



6、设置方法的响应结果(如果有的话)到输出消息的body中




我们在4.3节讨论了注册表的查找。第二步、第三步比较复杂,将在本章剩下的内容中讲解。比较复杂的原因:Camel必须在运行阶段计算调用哪个bean方法,而java代码的链接是在编译阶段。




Camel为什么需要选择一个方法?



答案是bean中可能会有重载方法,而且在某些情况下,方法名也不明确,意味着Camel必须在所有的方法中选择。



假设bean中包含如下方法:



String echo(String s);



int echo(int number);



void doSomething(String something);



一共有三个方法供Camel选择。如果你明确告诉Camel选择echo方法,仍然有两个要选择。我们看一下Camel是如何解决这一困境的。




我们首先看一下Camel选择方法是的算法。接着看一些例子,看看可能出现的错误,以及如何避免这些错误。




4.4.1 Camel如何选择bean方法

与在编译阶段不同,Java编译器可以将方法调用链接在一起,Camel的BeanProcessor需要在运行阶段选择所调用的方法。假设你有如下一个java类:
public class EchoBean {
String echo(String name) {
return name + ” ” + name;
}
}
在编译阶段,你可以像下面这样调用echo方法:
EchoBean echo = new EchoBean();
String reply = echo.echo(“Camel”);

这样保证了在运行阶段echo方法被调用。
另一方面,假设你在Camel的路由中使用EchoBean:
from(“direct:start”).bean(EchoBean.class, “echo”).to(“log:reply”);
当编译器编译这行代码时,编译器不知道你要调用EchoBean的echo方法。从编译器的视角来看,”EchoBean.class”和”echo”都是bean方法的参数。编译器能检查的是EchoBean类是否存在。如果你将”echo”方法名输错为”ekko”,编译器是不会发现这个错误的。这个错误会在运行阶段被捕获,此时BeanProcessor抛出MethodNotFoundException异常,指出ekko方法不存在。

Camel还允许不明确指定方法名。例如,你可以像下面这样改写上面的路由:
from(“direct:start”).bean(EchoBean.class).to(“log:reply”);

不管你是否显示指定方法名,Camel都必须计算调用哪个方法。让我们看看Camel是如何选择的。


4.4.2 Camel方法选择算法

BeanProcessor使用了一种复杂的算法来选择bean中所调用的方法。你不必理解或者记住算法中的每一步—我们只是想列出Camel内部到底发生了什么,让使用bean尽可能简单。

图4.5展示了算法的第一部分,第二部分在图4.6中。
下面是该算法的选择要调用的方法的步骤:
1、如果Camel的消息包含一个key为CamelBeanMethodName的头部,它的值用于显示指定方法名。调转到步骤5.
2、如果方法显示指定,Camel将使用它,就像本节开头提到的那样。跳转到步骤5.
3、如果所调用的bean可以转换为Processor类型(使用Camel的类型转换机制。),转换后的Processor用来处理消息。这似乎有点奇怪的,但Camel允许把任何bean转换为消息驱动bean。例如,利用这种技术,Camel允许直接调用javax.jms.MessageListener Bean,无需额外的集成操作。Camel的最终用户很少使用这种方式,但它可以是一个有用的技巧。
4、如果Camel的消息体可以被转换为org.apache.camel.component.bean.BeanInvocation对象,此对象用于调用bean方法以及向bean传递参数。这一点也很少被Camel的最终用户使用。
5、算法的第二部分,如图4.6所示。

图4.6有一点复杂,但是它的主要目标是缩小方法选择范围,直至选中一个方法(如果这个方法存在的话)。如果现在你对这个算法没有完全了解,不用担心,在下一节我们来看几个例子,就会使你更容易理解。
让我们继续学习算法的最后几个步骤:
6、如果路由中提供了方法名,但是bean中没有对应的方法名,则抛出异常:MethodNotFoundException。
7、如果只有一个方法使用@Handler注解标记,则选中它。
8、如果只有一个方法使用了Camel的参数绑定注解(例如:@body,@header等等),则选中它(参数绑定将在4.5.3节中介绍)。
9、如果在bean所有的方法中,只有一个方法的参数个数为1,则选中这个方法。例如,这种情况适用于4.1.1节中的EchoBean,EchoBean中只有一个echo方法,echo方法只有一个参数。单个参数的方法被优先选中,原因是,他们直接映射到了Camel中exchange的负载。
10、到这一步,算法变得有点复杂了。存在有多个候选方法,Camel必须确定是否有一个方法是最适合的。策略是过滤掉候选方法中不适合的方法。Camel通过匹配候选方法的第一个参数;如果不是同一类型的参数,而且不能进行强制类型转换,该方法过滤掉。最后,如果只剩下一个方法,该方法被选中。
11、如果最后没有选中方法,抛出AmbigiousMethodCallException异常,异常信息中包含模糊方法的列表。
显然,Camel对bean方法的选择做了很多。随着时间的推移你将学会欣赏这一切—-约定优于配置。

提示: 这本书中提出的算法是基于Apache Camel 2.5版本。这种方法选择的算法在未来可能会改变,以适应新特性。

现在是时候看看这个算法在实践中应用了。

4.4.3 一些方法选择的例子

为了进一步了解算法是如何工作的,我们使用4.4.1节中的EchoBean作为例子,为了更好地解释有多个候选方法时的处理,我们给EchoBean添加一个bar方法。
public class EchoBean {
public String echo(String echo) {
return echo + ” ” + echo;
}
public String bar() {
return “bar”;
}
}
我们将从这条路由开始:
from(“direct:start”).bean(EchoBean.class).to(“log:reply”);

如果你向路由发送消息:”Camel”,响应日志会打印出”Camel Camel”。尽管EchoBean有两个方法:echo和bar,但是只有echo方法有一个参数,符合算法第9步—如果在bean所有的方法中,只有一个方法的参数个数为1,则选中这个方法。

为了使这个例子更具有挑战性,让我们改一下这个bar方法:
public String bar(String name) {
return “bar ” + name;
}
你现在预计将会发生什么?现在有两个方法的参数个数为1.这种情况,Camel无法确定选择哪个,所以,抛出AmbigiousMethodCallException异常—-符合算法第11步。

你如何解决这个问题?一种解决方法是在路由中显示指定方法名,例如显示指定bar方法:
from(“direct:start”).bean(EchoBean.class, “bar”).to(“log:reply”);

还有另一种解决方法,不需要在路由中显示指定方法名。你可以在其中一个方法上使用@Handler注解—符合算法第7步。@Handler注解是一个Camel声明式的注解,你可以在方法上使用它。它只是告诉Camel默认调用这个方法。
@Handler
public String bar(String name) {
return “bar ” + name;
}
现在,AmbigiousMethodCallException异常不会抛出,因为@Handler注解告诉Camel选择bar方法。

提示:无论是在路由中显示指定方法名或者是使用@Handler注解,都是一个好主意。这样可以保证Camel选中你所期望选中的方法。

假设你改变了EchoBeanto的两个方法,并使其有不同的参数类型:
public class EchoBean {
public String echo(String echo) {
return echo + ” ” + echo;
}
public Integer double(Integer num) {
return num.intValue() * num.intValue();
}
}
echo方法参数类型是String,double方法的参数类型是Integer。如果你没有显示指定方法名,BeanProcessor必须在两个方法中选择。
算法第10步显示:允许Camel智能决定选择某个方法。Camel通过检查两个或两个以上的候选方法的消息负载,并且与消息体的类型对比,检查是否有一个精确类型匹配的方法。
假设你发送了一个消息到路由,消息体的类型是String类型,内容为”Camel”。不难猜测,Camel将会选择echo方法,因为echo方法的参数类型为String。另一方面,如果你发送的消息体是一个Integer类型的数字5,Camel将会选择double方法,因为double方法的参数类型是Integer类型。
尽管如此,仍然可能出现问题,让我们来看几个常见的情况。

4.4.4 潜在的方法选择问题


在运行时调用bean,会出现几种错误:


1、Specified method not found—如果Camel找不到显式指定的方法,会抛出MethodNotFoundException。此异常只会在你显式指定方法名时发生。


2、Ambiguous method—如果Camel不能挑出一个方法来调用,会抛出AmbigiousMethodCallException异常,异常中会包含模糊方法信息。即使指定了方法名,也可能抛出这个异常,因为方法可以被重载。


3、Type conversion failure—在Camel调用所选方法之前,Camel需要将消息负载类型转换为方法参数类型,如果类型转换失败,抛出NoTypeConversionAvailableException异常。




让我们看看这三种情况的例子,我们使用下面的这个EchoBean来演示:


public class EchoBean {


public String echo(String name) {


return name + name;


}


public String hello(String name) {


return “Hello ” + name;


}


}


首先,显式指定一个EchoBean中并不存在的方法:


<bean ref=”echoBean” method=”foo”/>


在这里你试着去调用foo方法,但是foo方法并不存在,所以Camel抛出MethodNotFoundException异常。


另一方面,你可以省略显式指定的方法:


<bean ref=”echoBean”/>


在这种情况下,Camel无法选出一个方法,因为echo和hello两个方法是模糊的。此时,Camel会抛出AmbigiousMethodCallException异常,异常信息中会包含echo和hello两个方法的信息。


最后一种情况,假设你有如下类:


public class OrderServiceBean {


public String handleXML(Document xml) {





}


}


而且你需要在如下路由中使用上述bean:


from(“jms:queue:orders”)


.beanRef(“orderService”, “handleXML”)


.to(“jms:queue:handledOrders”);




handleXML方法需要org.w3c.dom.Document类型的参数,但是如果JMS队列发送的是javax.jms.TextMessage类型的消息,并且消息中不含有任何的XML数据,而是一个纯文本消息,如”Camel rocks”。在运行时会出现以下异常:


Caused by: org.apache.camel.NoTypeConversionAvailableException: No type


converter available to convert from type: java.lang.byte[] to the


required type: org.w3c.dom.Document with value [

B@b3e1c9



at


org.apache.camel.impl.converter.DefaultTypeConverter.mandatoryConvertTo


(DefaultTypeConverter.java:115)


at


org.apache.camel.impl.MessageSupport.getMandatoryBody(MessageSupport.java


:101)


… 53 more


Caused by: org.apache.camel.RuntimeCamelException:


org.xml.sax.SAXParseException: Content is not allowed in prolog.


at


org.apache.camel.util.ObjectHelper.invokeMethod(ObjectHelper.java:724)


at


org.apache.camel.impl.converter.InstanceMethodTypeConverter.convertTo


(InstanceMethodTypeConverter.java:58)


at


org.apache.camel.impl.converter.DefaultTypeConverter.doConvertTo


(DefaultTypeConverter.java:158)


at


org.apache.camel.impl.converter.DefaultTypeConverter.mandatoryConvertTo


(DefaultTypeConverter.java:113)


… 54 more




发生异常的原因是Camel试着将javax.jms.TextMessage类型转换为a org.w3c.dom.Document类型,当时类型转换失败。在这种情况下,Camel对错误进行了包装,把它作为一个NoTypeConverterException异常抛出。


通过进一步查看这个异常堆栈,您可能会注意到出现这一问题的原因是,xml解析器无法将数据解析为XML。异常报告指出:”起始内容不允许”,意思是XML的声明(<?xml version=”1.0″?>)丢失了。




如果这种情况发生在运行时,你可能想知道会发生什么。在这种情况下,Camel的错误处理系统将会介入并处理它。错误处理的内容将在第5章讲述。


这是所有你需要知道的关于Camel在运行时选择方法的内容。现在我们需要看看bean参数绑定过程,这一动作发生在Camel选择方法之后。




4.5 Bean参数绑定


在上一节中,我们介绍了Camel选择Bean方法的过程。本节讨论接下来会发生什么—Camel如何将参数适配到方法签名上。任何bean方法都可以有多个参数,Camel必须向参数传递有意义的值。这一过程被称为Bean参数绑定。


到目前为止,我们已经看过参数绑定的许多例子了。这些例子通常的共同点都是使用的单一参数,Camel将输入消息体绑定到参数上。图4.7使用EchoBean展示了一个例子:




BeanProcessor使用输入消息,将输入消息的消息体绑定到方法的第一个参数上,即String name参数。Camel通过创建一个表达式,将消息体转换为String类型。这就保证了在Camel调用echo方法时,参数类型是匹配的。




那么,当一个方法有多个参数时,会发生什么呢?这就是我们将在本章的剩余部分要学习的。


4.5.1 多个参数绑定


图4.8展示了多个参数绑定的原则。


首先,图4.8看起来似乎有些复杂。当处理多个参数时,出现了许多新类型。标题为Bean parameter bindings的大方框包含下面四个小方框:


1、Camel built-in types(Camel内建类型)—Camel提供了一系列特殊绑定的概念。这些内容在4.5.2节中学习。


2、Exchange—这是Camel的Exchange,它允许绑定到输入消息,比如它的body(消息体)和headers(消息头部)。Camel的exchange是参数绑定值的来源地,在下一节中学习。


3、Camel annotations—在处理多个参数时,可以使用注解来区分它们。这些内容在4.5.3节中学习。


4、Camel language annotations— 这是一个不常用的功能,允许你将参数绑定到Camel语言注解。在处理XML消息时使用这个功能是个好主意,可以使用XPath进行参数绑定。这些内容将在4.5.4节中学习。




使用多个参数


用多个参数比使用单一参数更复杂。遵循下面这些法则通常是一个好主意:


1、使用第一个参数作为消息体,可以使用@Body注解;


2、对其他参数使用一个内置类型或Camel注解。


在我们的经验中,多个参数不遵守时这些指导方针会变得复杂,但是Camel会尽力进行参数匹配。


让我们先看如何使用Camel内置类型。




4.5.2 使用Camel内置类型进行参数绑定


Camel为参数绑定提供了一组固定的类型。你所需要做的就是声明一个参数类型为表4.2中列出的类型之一。


理解这一点是非常重要的,因为大部分的bean方法都只有一个参数。第一个参数对应的就是输入消息的消息体,Camel会自动将消息体类型转换为与参数相同的类型。


表4.2 Camel自动绑定的参数类型


类型 Exchange


描述 Camel的exchange,包含将要绑定到方法参数的值。




类型 Message


描述 Camel的输入消息。包含绑定到方法第一个参数的消息体。




类型 CamelContext


描述 CamelContext,可以用在你要访问Camel的所有组件的情况下。




类型 TypeConverter


描述 Camel的类型转换机制。 当您需要进行类型转换时可以使用。在3.6节已经学习过类型转换机制。




类型 Registry


描述 bean注册表。这允许您在注册表中查找bean。




类型 Exception


描述 异常类型。只有在exchange中有错误或异常的时候,Camel参会绑定这个类型。利用此类型可以再bean中进行错误处理。




让我们看看几个使用表4.2中的类型的例子。假设你在echo方法中添加了第二个参数,参数类型为内建类型CamelContext:


public string echo(String echo, CamelContext context)


在这个例子中,你绑定了CamelContext,使你可以访问Camel的所有组件。




如果您需要在注册表总查找一些bean,你可以绑定注册表类型:


public string echo(String echo, Registry registry) {


OtherBean other = registry.lookup(“other”, OtherBean.class);





}


你不会被局限于只有一个额外的参数,你可以使用尽可能多的参数。例如,你可以同时绑定 CamelContext和the registry:


public string echo(String echo, CamelContext context, Registry registry)




到目前为止,你一直绑定到消息体;你将如何绑定到一个消息头呢?下一节将解释。




4.5.3 使用Camel注解进行绑定
Camel提供了一系列的注解,用于exchange和bean参数之间的绑定。如果你想对参数绑定有更多的控制,你应该使用这些注解。例如,如果没有这些注解,Camel总是会将消息体绑定到方法的第一个参数上,但是如果使用@body注解,你可以将消息体绑定到方法的任一参数上。
假设你有如下bean方法:
public String orderStatus(Integer customerId, Integer orderId)
而且你有一个Camel消息,消息包含如下数据:
1、body(消息体),包含订单id,类型为String
2、header(消息头部),包含客户id,类型为Integer

利用Camel注解,你可以像下面这样将Exchange绑定到方法签名上面:
public String orderStatus(@Header(“customerId”) Integer customerId,
@Body Integer orderId)
注意:你使用了@Header注解将消息头部绑定到了第一个参数上,使用@Body注解将消息体绑定到了第二个参数上。
表4.3列出了所有的Camel参数绑定注解。

注解
@Attachments
描述
将参数绑定到消息附件上。此时的参数类型必须是java.util.Map类型。

注解
@Body
描述
将参数绑定到消息体上

注解
@Header(name)
描述
将参数绑定到给定的消息头上。

注解
@Headers
描述
将参数绑定到所有的输入头部上。此时的参数类型必须是java.util.Map类型。

注解
@OutHeaders
描述
将参数绑定到所有的输出头部上。此时的参数类型必须是java.util.Map类型。利用这个注解,你可以向输出消息中添加头部。

注解
@Property(name)
描述
将参数绑定到给定的exchange属性上

注解
@Properties
描述
将参数绑定到所有的exchange属性上。此时的参数类型必须是java.util.Map类型。

让我们再看几个例子。例如,你可以使用@Headers注解将输入头部绑定到Map类型上:
public String orderStatus(@Body Integer orderId, @Headers Map headers) {
Integer customerId = (Integer) headers.get(“customerId”);
String customerType = (String) headers.get(“customerType”);
}
当消息有多个头部时,可以使用此注解,这样你就不需要为每个头部都添加一个对应的参数了。
当你使用的是请求/响应形式的消息(也确定为InOut消息交换模式)时,可以使用@OutHeaders注解。@OutHeaders可以直接操作输出消息的头部,意味着你可以在bean中直接操纵这些消息头部。下面是一个例子:
public String orderStatus(@Body Integer orderId, @OutHeaders Map headers) {
headers.put(“status”, “APPROVED”);
headers.put(“confirmId”, “444556”);
return “OK”;
}

你使用@OutHeaders注解了第二个参数。与@Headers不同, 当方法调用时,@OutHeaders是空的。这里的理念是:你负责把需要保存到输出消息中的头部设置到这个map中。

4.5.4 使用Camel语言注解进行参数绑定

此部分略