2.4. bean Integration

概述

Bean 集成提供了一种通用机制,用于使用任意 Java 对象处理消息。通过将 bean 引用插入到路由中,您可以在 Java 对象上调用任意方法,然后访问和修改传入的交换。将交换内容映射到参数并返回 bean 方法值的机制称为 参数绑定。参数绑定可以使用以下任一方法组合来初始化方法的参数:

  • 传统的方法签名 >_<- the method 签名是否符合某些约定,则参数绑定可以使用 Java 反射来确定要通过哪些参数。
  • 注解和依赖项注入 GCM- the 以获得更灵活的绑定机制,使用 Java 注解指定要注入方法的参数。这种依赖项注入机制依赖于 Spring 2.5 组件扫描。通常,如果您将 Apache Camel 应用程序部署到 Spring 容器中,则依赖项注入机制将自动工作。
  • 在调用 bean 的点上,明确指定参数 InventoryService-latex,可显式指定 参数(可以是恒定常数或使用 Simple 语言)。

bean registry

Bean 是通过 bean registry 进行访问的,该服务可让您将类名称或 bean ID 用作密钥来查找 Bean。您在 bean registry 中创建条目的方式取决于底层的 framework>_<-abrt,如纯 Java、Spring、Spring、Gusice 或 Blueprint。registry 条目通常隐式创建(例如,当您在 Spring XML 文件中实例化 Spring bean 时)。

registry 插件策略

Apache Camel 为 bean 注册表实施插件策略,定义用于访问 Bean 的集成层,从而使底层 registry 的实施透明。因此,可以将 Apache Camel 应用程序与各种不同 Bean registry 集成,如 表 2.2 “registry 插件” 所示。

表 2.2. registry 插件

registry 的实现带有 Registry 插件的 Camel 组件

Spring bean registry

Camel-spring

Guice bean registry

camel-guice

蓝图 bean registry

Camel-blueprint

OSGi 服务 registry

OSGi 容器中部署

JNDI registry

 

通常,您不必担心配置 bean registry,因为会自动为您安装相关的 bean registry。例如,如果您使用 Spring 框架定义路由,则 Spring ApplicationContextRegistry 插件会在当前的 CamelContext 实例中自动安装。

在 OSGi 容器中部署是一个特殊情况。当将 Apache Camel 路由部署到 OSGi 容器时,CamelContext 会自动设置 registry 链以解析 Bean 实例:registry 链包括 OSGi 注册表,后面是 Blueprint(或 Spring) registry。

在 Java 中访问创建的 Bean

要使用 Java bean(一个普通旧的 Java 对象或 OVA)进程交换对象,请使用 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 的方法,它采用两个参数(irrespective of)参数,调用 bean() 处理器,如下所示:

from("file:data/inbound")
.bean(MyBeanProcessor.class, "processBody(*,*)")
.to("file:data/outbound");

在指定方法时,您可以使用简单的非限定类型 name- 例如,processBody(Exchange)- 或完全限定域名(如 processBody(org.apache.camel.Exchange)

注意

在当前实现中,指定的类型名称必须是与参数类型完全匹配。类型继承不会考虑。

明确指定参数

当调用 bean 方法时,可以明确指定参数值。可以传递以下简单类型值:

  • 布尔值: truefalse
  • 数字: 1237 等。
  • 字符串: '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 语言(第 30 章 简单语言)指定参数值。这意味着在指定 参数值时提供了 Simple 语言的完整功能。例如,将邮件正文和标题 标题 的值传递给 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 方法,您可以定义符合特定约定的方法签名。特别是,方法签名有两个基本惯例:

处理消息正文的方法签名

如果要实施访问或修改传入的消息正文的方法,您必须定义一个使用单个 字符串 参数的方法签名,并返回 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 访问 Springan

您可以使用 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(an 元素的 id 属性的值)来引用它。例如,如果将 ID 为等于 myBeanIdbean 元素,您可以使用 beanRef() 处理器来引用 Java DSL 路由中的an,如下所示:

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 访问 Springan

使用 Spring bean 元素创建对象实例时,您可以使用 bean 的 ID( bean 元素的 id 属性的值)从 Java 引用它。例如,如果将 ID 为等于 myBeanIdbean 元素,您可以使用 beanRef() 处理器来引用 Java DSL 路由中的an,如下所示:

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,正确的关闭顺序通常是:

  1. 关闭 camelContext 实例,后接;
  2. 关闭使用的 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 方法的参数中。

表 2.3. 基本 Bean 标注

注解含义参数?

@Attachments

绑定到附加列表。

 

@Body

绑定到入站邮件正文。

 

@Header

绑定到入站邮件标题。

标头的字符串名称。

@Headers

绑定到入站邮件标头的 java.util.Map

 

@OutHeaders

绑定到出站邮件标头的 java.util.Map

 

@property

绑定到指定交换属性。

属性的字符串名称。

@properties

绑定到交换属性的 java.util.Map

 

例如,以下类显示如何使用基本注解将消息数据注入 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);
    }
}

请注意,如何将注解与默认规则混合使用。除了注入注解的参数外,参数绑定也会自动将 exchange 对象注入到 org.apache.camel.Exchange 参数。

表达式语言注解

表达式语言注释提供了一种强大的机制,用于将消息数据注入 bean 方法的参数。通过使用这些注解,您可以调用任意脚本(使用您选择的脚本语言编写)从入站交换中提取数据,并将数据注入方法参数。表 2.4 “表达式语言注释” 显示 org.apache.camel.language 软件包(和子软件包)中的注释,供您用来将消息数据注入 bean 方法的参数中。

表 2.4. 表达式语言注释

注解描述

@Bean

注入 Bean 表达式。

@Constant

注入 Constant 表达式

@EL

注入 EL 表达式。

@Groovy

注入 Groovy 表达式。

@Header

注入标题表达式。

@JavaScript

注入 JavaScript 表达式。

@OGNL

注入 OGNL 表达式。

@PHP

注入 PHP 表达式。

@Python

注入 Python 表达式。

@Ruby

注入 Ruby 表达式。

@Simple

注入简单表达式。

@XPath

注入 XPath 表达式。

@XQuery

注入 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 类中使用注解 MyIdGeneratorgenerate() 方法签名的唯一限制是,它必须返回正确的类型来注入 @Bean 注释的参数。由于 @Bean 注释不要求您指定方法名称,因此注入机制只需在引用的 bean 中调用具有匹配返回类型的方法。

注意

某些语言注释在核心组件(@Bean@Constant@Simple@XPath)中可用。但是,对于非内核组件,您必须确定载入相关组件。例如,要使用 OGNL 脚本,您必须加载 camel-ognl 组件。

继承的注解

参数绑定注解可以从接口或从超级类继承。例如,如果您使用 标头 注释和 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 接口的类通常 受保护的private 或在 仅软件包 范围内。如果您在以这种方式限制的实施类上调用方法,则 bean 绑定将返回到调用对应的接口方法,该方法可以公开访问。

例如,请考虑以下公共 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 类,请更改Something()

// 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 容器中部署时,会设置一个 registry 链。首先,它会在 OSGi 服务 registry 中查找指定的类名称 ; 如果这个查找失败,它将回退到本地 Spring DM 或蓝图 registry。