2.4. Bean 集成
概述
Bean 集成提供了一种通用机制,用于处理使用任意 Java 对象的消息。通过将 bean 引用插入到路由中,您可以在 Java 对象上调用任意方法,然后该对象可以访问和修改传入的交换。将交换内容映射到参数的机制,而 bean 方法的返回值称为 参数绑定。参数绑定可以使用以下任一方法组合来初始化方法的参数:
- 传统的方法签名 wagon-wagon 如果方法签名符合某些约定,则参数绑定可以使用 Java 反映来决定要传递的参数。
- 注解和依赖项注入 HEKETI-wagon 用于更灵活的绑定机制,使用 Java 注解来指定注入方法参数的内容。此依赖项注入机制依赖于 Spring 2.5 组件扫描。通常,如果您要将 Apache Camel 应用程序部署到 Spring 容器中,依赖项注入机制将自动工作。
- 在调用 bean 的点上,您可以明确指定参数(作为常量或使用 Simple 语言)明确指定参数。
Bean registry
Bean 通过 bean 注册表 访问,它是一个服务,可让您使用类名称或 bean ID 作为键查找 Bean。在 bean registry 中创建条目的方式取决于底层的框架是是 plain Java、Spring、Guice 或 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 实例的注册表链:注册表链由 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");
在指定方法时,您可以使用简单的非限定类型名称,如 processBody (Exchange)
- 或完全限定类型 name- 例如 processBody (org.apache.camel.Exchange)
。
在当前实现中,指定的类型名称必须与参数类型完全匹配。类型继承不会考虑。
明确指定参数
当您调用 bean 方法时,您可以明确指定参数值。可以传递以下简单类型值:
-
布尔值:
true
或false
。 -
数字:
123
、7
等等。 -
字符串:
'In single quotes'
或"In double quotes"
. -
null 对象:
null
.
以下示例演示了如何将显式参数值与同一方法调用中的类型指定符混合起来:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(String, 'Sample string value', true, 7)") .to("file:data/outbound");
在前面的示例中,第一个参数的值会假定由参数绑定注解决定(请参阅 “基本注解”一节)。
除了 simple 类型值外,您还可以使用 Simple 语言(第 30 章 简单语言)指定参数值。这意味着,在指定 参数值时,可以使用 Simple 语言的完整功能。例如,将消息正文和 title
标头的值传递给 bean 方法:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBodyAndHeader(${body},${header.title})") .to("file:data/outbound");
您还可以将整个标头哈希映射作为参数传递。例如,在以下示例中,必须将第二个 method 参数声明为 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 的 ID ( bean 元素的 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>
对于 slight 效率,您可以将 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");
或者,您可以使用 @BeanInject
注释来引用 Spring bean,如下所示:
// 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 起的行为。
如果您需要更改此行为(因此,在其他 Bean 之前,Camel 上下文 不会被 强制关闭),您可以将 camelContext
元素上的 shutdownEager
属性设置为 false
。在这种情况下,您可能会使用 Spring dependent-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
组件。
继承注解
参数绑定注解可以从接口或超级类继承。例如,如果您使用 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 调用会返回调用公共 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 集成来调用静态 更改Something
方法,如下所示:
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 容器中部署时,Apache Camel 设置注册表链。首先,它会在 OSGi 服务注册表中查找指定的类名称;如果查询失败,它将返回到本地 Spring DM 或蓝图注册表。