2.4. Bean 集成
概述
Bean 集成提供了一种通用机制,可用于使用任意 Java 对象处理消息。通过将 bean 引用插入到路由中,您可以在 Java 对象上调用任意方法,然后访问和修改传入的交换。将交换内容映射到参数的机制,并且返回 bean 方法的值称为 参数绑定。参数绑定可以使用以下任一方法组合来初始化方法的参数:
- 传统的方法签名 mvapich-DESTINATION 如果方法签名符合某些约定,参数绑定可以使用 Java 反映来确定要传递的参数。
- 注解和依赖项注入 mvapich-DESTINATION 用于更灵活的绑定机制,使用 Java 注解来指定要注入方法的参数的内容。这个依赖注入机制依赖于 Spring 2.5 组件扫描。通常,如果您要将 Apache Camel 应用程序部署到 Spring 容器中,依赖项注入机制将自动正常工作。
- 在调用 bean 时,显式指定参数 categories-busybox 您可以指定参数(可以是常量或使用 Simple 语言)。
Bean registry
Bean 可以通过 bean 注册表 访问,该服务是一种服务,允许您将类名称或 Bean ID 用作密钥来查找 Bean。在 bean 注册表中创建条目的方式取决于底层的 framework,即 Java、Spring、Gusice 或 Blueprint。通常,registry 条目会被隐式创建(例如,当您在 Spring XML 文件中实例化 Spring bean 时)。
registry 插件策略
Apache Camel 为 bean registry 实施插件策略,定义用于访问 Bean 的集成层,使底层 registry 实施透明。因此,可以将 Apache Camel 应用程序与各种不同的 bean registry 集成,如 表 2.2 “registry 插件” 所示。
registry 实施 | 带有 Registry 插件的 Camel 组件 |
---|---|
Spring bean registry |
|
guice bean registry |
|
蓝图 Bean registry |
|
OSGi 服务 registry | 在 OSGi 容器中部署 |
JNDI registry |
通常,您不必担心配置 bean registry,因为会自动为您安装相关的 bean registry。例如,如果您使用 Spring 框架定义路由,则 Spring ApplicationContextRegistry
插件会自动安装在当前的 CamelContext
实例中。
在 OSGi 容器中部署是一个特殊情况。当 Apache Camel 路由部署到 OSGi 容器中时,CamelContext 会自动设置用于解析 bean 实例的 registry 链:registry 链由 OSGi 注册表组成,后跟 Blueprint (或 Spring)注册表。
访问在 Java 中创建的 Bean
要使用 Java bean (即普通旧 Java 对象或 POJO)处理对象,请使用 bean ()
处理器,它将入站交换绑定到 Java 对象上的方法。例如,要使用类 MyBeanProcessor
处理入站交换,请定义如下路由:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody") .to("file:data/outbound");
其中 bean ()
处理器创建 MyBeanProcessor
类型的实例,并调用 processBody ()
方法来处理入站交换。如果您只想从一个路由访问 MyBeanProcessor
实例,则这种方法就足够了。但是,如果您想要从多个路由访问同一 MyBeanProcessor
实例,请使用将对象类型作为其第一个参数的 bean ()
变体。例如:
MyBeanProcessor myBean = new MyBeanProcessor(); from("file:data/inbound") .bean(myBean, "processBody") .to("file:data/outbound"); from("activemq:inboundData") .bean(myBean, "processBody") .to("activemq:outboundData");
访问过载 Bean 方法
如果 bean 定义了超载方法,您可以通过指定方法名称及其参数类型来选择调用的超载方法。例如,如果 MyBeanBrocessor
类有两个过载方法: processBody (String)
和 processBody (String,String)
,您可以按如下方式调用后者的超载方法:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(String,String)") .to("file:data/outbound");
或者,如果要根据其使用的参数数量识别方法,而不是明确指定每个参数的类型,您可以使用通配符字符 *
。例如,要调用名为 processBody
的方法,它采用两个参数,无论参数的确切类型,请按如下所示调用 bean ()
处理器:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(*,*)") .to("file:data/outbound");
在指定方法时,您可以使用简单的非限定类型 name-如 processBody (Exchange)
-或完全限定类型名称,如 processBody (org.apache.camel.Exchange)
。
在当前实现中,指定的类型名称必须与参数类型完全匹配。不考虑类型继承。
明确指定参数
您可以在调用 bean 方法时明确指定参数值。可以传递以下简单类型值:
-
布尔值:
true
或false
。 -
数字:
123
、7
等。 -
字符串:
'In quotes'
或"In double quotes"
。 -
null 对象:
null
.
以下示例演示了如何在同一方法调用中混合带有类型指定符的显式参数值:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(String, 'Sample string value', true, 7)") .to("file:data/outbound");
在上例中,第一个参数的值可能会被参数绑定注解决定(请参阅 “基本注解”一节)。
除了简单类型值外,您还可以使用简单语言(第 30 章 简单语言)指定参数值。这意味着,在指定 参数值时,可以使用简单语言的完整功能。例如,将消息正文和标题标头的值传递给 bean 方法:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBodyAndHeader(${body},${header.title})") .to("file:data/outbound");
您还可以将整个标头哈希映射作为参数传递。例如,在以下示例中,必须声明第二个方法参数,使其类型为 java.util.Map
:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBodyAndAllHeaders(${body},${header})") .to("file:data/outbound");
在 Apache Camel 2.19 版本中,从 bean 方法调用返回 null 现在始终确保消息正文已设置为 null 值。
基本方法签名
要将交换绑定到 bean 方法,您可以定义符合某些约定的方法签名。特别是,方法签名有两个基本惯例:
用于处理消息正文的方法签名
如果要实施访问或修改传入的消息正文的 bean 方法,您必须定义一个使用单个 String
参数的方法签名并返回 String
值。例如:
// Java package com.acme; public class MyBeanProcessor { public String processBody(String body) { // Do whatever you like to 'body'... return newBody; } }
处理交换的方法签名
要获得更大的灵活性,您可以实施一个访问传入交换的 bean 方法。这可让您访问或修改所有标头、正文和交换属性。对于处理交换,方法签名采用单个 org.apache.camel.Exchange
参数,并返回 void
。例如:
// Java package com.acme; public class MyBeanProcessor { public void processExchange(Exchange exchange) { // Do whatever you like to 'exchange'... exchange.getIn().setBody("Here is a new message body!"); } }
从 Spring XML 访问 Spring bean
您可以使用 Spring XML 创建实例,而不是在 Java 中创建 bean 实例。实际上,如果您在 XML 中定义路由,这是唯一可行的方法。要在 XML 中定义 bean,请使用标准 Spring bean
元素。以下示例演示了如何创建 MyBeanProcessor
的实例:
<beans ...> ... <bean id="myBeanId" class="com.acme.MyBeanProcessor"/> </beans>
也可以使用 Spring 语法将数据传递给 bean 的构造器参数。有关如何使用 Spring bean
元素的完整详情,请参阅 Spring 参考指南 中的 IoC 容器。
当使用 bean
元素创建对象实例时,您可以稍后使用 bean 的 ID ( bean
元素的 id
属性的值)引用它。例如,如果 ID 为 myBeanId
的 bean
元素,您可以使用 beanRef ()
处理器在 Java DSL 路由中引用 bean,如下所示:
from("file:data/inbound").beanRef("myBeanId", "processBody").to("file:data/outbound");
其中 beanRef ()
处理器对指定的 bean 实例调用 MyBeanProcessor.processBody ()
方法。
您还可以使用 Camel 模式的 bean 元素从 Spring XML 路由中调用 bean
。例如:
<camelContext id="CamelContextID" xmlns="http://camel.apache.org/schema/spring"> <route> <from uri="file:data/inbound"/> <bean ref="myBeanId" method="processBody"/> <to uri="file:data/outbound"/> </route> </camelContext>
为了提高效率,您可以将 cache
选项设置为 true
,这样可避免在每次使用 bean 时查找 registry。例如,要启用缓存,您可以在 bean
元素上设置 cache
属性,如下所示:
<bean ref="myBeanId" method="processBody" cache="true"/>
从 Java 访问 Spring bean
当使用 Spring bean
元素创建对象实例时,您可以使用 bean
的 ID ( bean 元素的 id
属性的值)从 Java 引用它。例如,如果 ID 为 myBeanId
的 bean
元素,您可以使用 beanRef ()
处理器在 Java DSL 路由中引用 bean,如下所示:
from("file:data/inbound").beanRef("myBeanId", "processBody").to("file:data/outbound");
或者,您可以通过注入引用 Spring bean,使用 @BeanInject
注释,如下所示:
// Java import org.apache.camel.@BeanInject; ... public class MyRouteBuilder extends RouteBuilder { @BeanInject("myBeanId") com.acme.MyBeanProcessor bean; public void configure() throws Exception { .. } }
如果省略了来自 @BeanInject
注释的 bean ID,Camel 按类型查找 registry,但这仅在给定类型只有一个 bean 时才有效。例如,查找并注入 com.acme.MyBeanProcessor
类型的 bean:
@BeanInject com.acme.MyBeanProcessor bean;
Spring XML 中的 bean 关闭顺序
对于 Camel 上下文使用的 Bean,通常正确的关闭顺序是:
-
关闭
camelContext
实例,后跟 ; - 关闭使用的 Bean。
如果这个关闭顺序是反向的,则 Camel 上下文可能会试图访问已销毁的 bean (直接出错);或者 Camel 上下文会尝试在销毁时创建缺少的 bean,这也会导致错误。Spring XML 中的默认关闭顺序取决于 Bean 和 camelContext
的顺序出现在 Spring XML 文件中。为了避免因为关闭顺序不正确而导致随机错误,因此 camelContext
被配置为在 Spring XML 文件中的任何其他 Bean 之前关闭。这是自 Apache Camel 2.13.0 起的默认设置。
如果您需要更改此行为(因此 Camel 上下文不会强制在其他 Bean 前关闭),您可以将 camelContext
元素上的 shutdownEager
属性设置为 false
。在这种情况下,您可以使用 Spring depends-on
属性对关闭顺序进行更精细的控制。
参数绑定注解
“基本方法签名”一节 描述的基本参数绑定可能并不总是方便使用。例如,如果您有一个执行某些数据操作的传统 Java 类,您可能需要从入站交换中提取数据并将其映射到现有方法签名的参数。对于这种参数绑定,Apache Camel 提供以下类型的 Java 注解:
基本注解
表 2.3 “基本 Bean 注解” 显示 org.apache.camel
Java 软件包中的注解,可用于将消息数据注入 bean 方法的参数。
注解 | 含义 | 参数? |
---|---|---|
| 绑定到附加列表。 | |
| 绑定到入站消息正文。 | |
| 绑定到入站消息标头。 | 标头的字符串名称。 |
|
绑定到入站消息标头的 | |
|
绑定到出站消息标头的 | |
| 绑定到命名的 Exchange 属性。 | 属性的字符串名称。 |
|
绑定到交换属性的 |
例如,以下类演示了如何使用基本注解将消息数据注入 processExchange ()
方法参数。
// Java import org.apache.camel.*; public class MyBeanProcessor { public void processExchange( @Header(name="user") String user, @Body String body, Exchange exchange ) { // Do whatever you like to 'exchange'... exchange.getIn().setBody(body + "UserName = " + user); } }
请注意,您如何将注解与默认约定混合。除了注入注释的参数外,参数绑定也会自动将交换对象注入 org.apache.camel.Exchange
参数。
表达式语言注解
表达式语言注解提供了将消息数据注入 bean 方法参数的强大机制。通过使用这些注解,您可以使用您选择的脚本语言调用任意脚本,从入站交换中提取数据并将其注入方法参数。表 2.4 “表达式语言注解” 显示 org.apache.camel.language
软件包(和子软件包,用于非内核注解)的注解,您可以用来将消息数据注入 bean 方法的参数。
注解 | 描述 |
---|---|
| 注入 Bean 表达式。 |
| 注入 Constant 表达式 |
| 注入 EL 表达式。 |
| 注入 Groovy 表达式。 |
| 注入标头表达式。 |
| 注入 JavaScript 表达式。 |
| 注入 OGNL 表达式。 |
| 注入 PHP 表达式。 |
| 注入 Python 表达式。 |
| 注入 Ruby 表达式。 |
| 注入简单表达式。 |
| 注入 XPath 表达式。 |
| 注入 XQuery 表达式。 |
例如,以下类演示了如何使用 @XPath
注释从 XML 格式的传入消息的正文中提取用户名和密码:
// Java import org.apache.camel.language.*; public class MyBeanProcessor { public void checkCredentials( @XPath("/credentials/username/text()") String user, @XPath("/credentials/password/text()") String pass ) { // Check the user/pass credentials... ... } }
@Bean
注释是一个特殊情况,因为它允许您注入调用注册 Bean 的结果。例如,要将关联 ID 注入到方法参数中,您可以使用 @Bean
注释来调用 ID 生成器类,如下所示:
// Java import org.apache.camel.language.*; public class MyBeanProcessor { public void processCorrelatedMsg( @Bean("myCorrIdGenerator") String corrId, @Body String body ) { // Check the user/pass credentials... ... } }
其中字符串 myCorrIdGenerator
是 ID 生成器实例的 bean ID。ID 生成器类可以使用 spring bean
元素实例化,如下所示:
<beans ...> ... <bean id="myCorrIdGenerator" class="com.acme.MyIdGenerator"/> </beans>
其中 MyIdGenerator
类可以定义如下:
// Java package com.acme; public class MyIdGenerator { private UserManager userManager; public String generate( @Header(name = "user") String user, @Body String payload ) throws Exception { User user = userManager.lookupUser(user); String userId = user.getPrimaryId(); String id = userId + generateHashCodeForPayload(payload); return id; } }
请注意,您也可以使用引用的 bean 类 MyIdGenerator
中的注释。generate ()
方法签名的唯一限制是它必须返回正确的类型,才能注入 @Bean
注解的参数。由于 @Bean
注释没有让您指定方法名称,因此注入机制只是调用具有匹配返回类型的引用 bean 中的第一个方法。
某些语言注释在核心组件中可用(@Bean
、@Constant
、@Simple
和 @XPath
)。但是,对于非核心组件,您必须确保加载相关组件。例如,要使用 OGNL 脚本,您必须加载 camel-ognl
组件。
继承的注解
参数绑定注解可以从接口或从 superclass 继承。例如,如果您使用 Header
注解和 Body
注解定义 Java 接口,如下所示:
// Java import org.apache.camel.*; public interface MyBeanProcessorIntf { void processExchange( @Header(name="user") String user, @Body String body, Exchange exchange ); }
实现类 MyBeanProcessor
中定义的过载方法现在继承基本接口中定义的注解,如下所示:
// Java import org.apache.camel.*; public class MyBeanProcessor implements MyBeanProcessorIntf { public void processExchange( String user, // Inherits Header annotation String body, // Inherits Body annotation Exchange exchange ) { ... } }
接口实现
实施 Java 接口的类通常 受到保护
、私有
或仅软件包
的范围。如果您试图调用以这种方式限制的实施类的方法,则 bean 绑定会返回调用对应的接口方法,该方法可以被公开访问。
例如,请考虑以下 public BeanIntf
接口:
// Java public interface BeanIntf { void processBodyAndHeader(String body, String title); }
其中 BeanIntf
接口由以下受保护的 BeanIntfImpl
类实现:
// Java protected class BeanIntfImpl implements BeanIntf { void processBodyAndHeader(String body, String title) { ... } }
以下 bean 调用将回退到调用 public BeanIntf.processBodyAndHeader
方法:
from("file:data/inbound") .bean(BeanIntfImpl.class, "processBodyAndHeader(${body}, ${header.title})") .to("file:data/outbound");
调用静态方法
Bean 集成具有调用静态方法的功能,而无需创建 关联的类实例。例如,请考虑以下定义静态方法的 Java 类,changeSomething ()
:
// Java ... public final class MyStaticClass { private MyStaticClass() { } public static String changeSomething(String s) { if ("Hello World".equals(s)) { return "Bye World"; } return null; } public void doSomething() { // noop } }
您可以使用 bean 集成来调用静态 changeSomething
方法,如下所示:
from("direct:a") *.bean(MyStaticClass.class, "changeSomething")* .to("mock:a");
请注意,虽然此语法与调用普通函数看起来相同,但 bean 集成利用 Java 反映将方法识别为静态,并继续 在不 实例化 MyStaticClass
的情况下调用方法。
调用 OSGi 服务
在将路由部署到红帽 Fuse 容器中的特殊情况下,可以使用 bean 集成直接调用 OSGi 服务。例如,假设 OSGi 容器中的一个捆绑包已导出了服务,org.fusesource.example.HelloWorldOsgiService
,您可以使用以下 bean 集成代码调用 sayHello
方法:
from("file:data/inbound") .bean(org.fusesource.example.HelloWorldOsgiService.class, "sayHello") .to("file:data/outbound");
您还可以使用 bean 组件从 Spring 或蓝图 XML 文件中调用 OSGi 服务,如下所示:
<to uri="bean:org.fusesource.example.HelloWorldOsgiService?method=sayHello"/>
其工作方式是,Apache Camel 在 OSGi 容器中部署时设置一个注册表链。首先,它会在 OSGi 服务 registry 中查找指定的类名称;如果此查找失败,它将回退到本地 Spring DM 或蓝图注册表。