2.4. Bean 集成
概述
Bean 集成提供了使用任意 Java 对象处理消息的一般目的机制。通过将 bean 引用插入到路由上,您可以在 Java 对象上调用任意方法,然后可以访问和修改传入的交换。将交换内容映射到参数的机制,以及 bean 方法的返回值称为 参数绑定。参数绑定可以使用以下方法的任意组合来初始化方法的参数:
- 传统方法签名 TOKEN-lease 如果方法签名符合某些约定,则参数绑定可以使用 Java 反射来确定要传递的参数。
- 注解和依赖项注入 PlacementBinding- summarize 对于更灵活的绑定机制,使用 Java 注解来指定将什么注入方法的参数。这种依赖项注入机制依赖于 Spring 2.5 组件扫描。通常,如果您将 Apache Camel 应用部署到 Spring 容器中,则依赖项注入机制将自动工作。
- 在调用 bean 的点上,显式指定参数 主机上运行的参数可明确指定(可以是常量或使用简单语言)。
Bean registry
Bean 可通过 bean 注册表 (即服务)进行访问,它可让您使用类名称或 bean ID 作为密钥来查找 Bean。您在 bean 注册表中创建条目的方式取决于底层框架的结束,如普通 Java、Spring、Gusice 或 Blueprint。通常会创建 registry 条目(例如,当您在 Spring XML 文件中实例化 Spring bean 时)。
registry 插件策略
Apache Camel 为 bean 注册表实施插件策略,定义用于访问 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 框架定义路由,则在当前 CamelContext
实例中自动安装 Spring ApplicationContextRegistry
插件。
OSGi 容器中部署是一项特殊情形。当 Apache Camel 路由部署到 OSGi 容器中时,CamelContext
会自动设置用于解析 bean 实例的 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");
在指定方法时,您可以使用一个简单的非限定类型名称-例如 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");
在上例中,第一个参数的值可能会假定由参数绑定注解(请参阅 “基本注解”一节)决定。
除了简单类型值外,您还可以使用简单语言(第 30 章 简单语言)指定参数值。这意味着在指定参数值时 提供了简单语言的完整电源。例如,将消息正文和标题 标题
的值传递给 bean 方法:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBodyAndHeader(${body},${header.title})") .to("file:data/outbound");
您也可以将整个标头散列映射作为参数传递。例如,在以下示例中,必须声明第二个方法参数为 type 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 方法,您可以定义符合特定约定的方法签名。特别是,方法签名有两个基本的惯例:
处理消息正文的方法签名
如果要实施访问或修改传入的消息正文的方法,您必须定义一个采用单个 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>
对于略微效率,您可以将 缓存
选项设置为 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 起的默认设置。
如果您需要更改此行为(因此,Camel 上下文 不强制 在其他 Bean 之前关闭),您可以将 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 调用会返回调用 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");
请注意,虽然这种语法与调用 正常 功能的调用相同,但这种集成利用 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 会设置一个 registry 链。首先,它会在 OSGi 服务注册表中查找指定的类名称;如果此查找失败,它将回退到本地 Spring DM 或蓝图 registry。