Apache Camel指南-第五章:路由径构建之Bean整合

总览

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

  • 常规方法签名 -如果方法签名符合某些约定,则参数绑定可以使用Java反射来确定要传递的参数。
  • 注释和依赖项注入 -为获得更灵活的绑定机制,请使用Java注释来指定要注入到方法参数中的内容。这种依赖性注入机制依赖于Spring 2.5组件扫描。通常,如果将Apache Camel应用程序部署到Spring容器中,则依赖项注入机制将自动运行。
  • 显式指定的参数 -您可以在调用Bean时显式地指定参数(使用常量或使用Simple语言)。

Bean注册表

可以通过bean注册表来访问Beanbean注册表是一项服务,使您可以使用类名或Bean ID作为键来查找Bean。在bean注册表中创建条目的方式取决于基础框架,例如,纯Java,Spring,Guice或Blueprint。注册表项通常是隐式创建的(例如,当您在Spring XML文件中实例化Spring bean时)。

注册表插件策略

Apache Camel为bean注册表实现了一种插件策略,定义了用于访问bean的集成层,从而使基础注册表实现透明。因此,可以将Apache Camel应用程序与各种不同的bean注册表集成在一起。

注册表实施带有注册表插件的Camel组件
Spring bean registrycamel-spring
Guice bean registrycamel-guice
Blueprint bean registrycamel-blueprint
OSGi service registry部署在OSGi容器中
JNDI registry

通常,您不必担心配置Bean注册表,因为会自动为您安装相关的Bean注册表。例如,如果您使用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(),将Object类型作为其第一个参数。例如:

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)或完全限定的类型名称(例如)processBody(org.apache.camel.Exchange)

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

明确指定参数

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

  • 布尔值:truefalse
  • 数字:1237等等。
  • 字符串:'In single quotes'"In double quotes"
  • 空对象:null

下面的示例说明如何在同一方法调用中将显式参数值与类型说明符混合使用:

from("file:data/inbound")
  .bean(MyBeanProcessor.class, "processBody(String, 'Sample string value', true, 7)")
  .to("file:data/outbound");

在前面的示例中,第一个参数的值大概由参数绑定批注确定(请参阅“基本批注”一节)。

除了简单类型值,您还可以使用简单语言(第30章,简单语言)指定参数值。这意味着在指定参数值时**,可以使用简单语言全部功能**。例如,要将消息正文和title标头的值传递给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方法,可以定义符合某些约定的方法签名。特别是,方法签名有两种基本约定:

  • 用于处理消息正文的方法签名
  • 处理交换的方法签名

用于处理消息正文的方法签名

如果要实现访问或修改传入消息正文的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的构造函数参数。
其中beanRef()处理器调用MyBeanProcessor.processBody()所指定的bean的实例方法。您还可以使用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时都查找注册表。例如,要启用缓存,可以cachebean元素上设置属性,如下所示:

<bean ref="myBeanId" method="processBody" cache="true"/>

从Java访问Spring bean

使用Spring bean元素创建对象实例时,可以使用Bean的ID(bean元素的id属性值)从Java引用它。例如,给定beanID为的元素myBeanId,您可以使用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按类型查找注册表,但这仅在给定类型的单个bean时有效。例如,要查找并注入以下com.acme.MyBeanProcessor类型的bean :

@BeanInject
com.acme.MyBeanProcessor bean;

Spring XML中的Bean关闭顺序

对于Camel上下文使用的bean,正确的关闭顺序通常是:

  1. 关闭camelContext实例,然后关闭;
  2. 关闭用过的豆子。

如果此关闭顺序相反,则可能会发生Camel上下文尝试访问已被销毁的bean的情况(要么直接导致错误;要么Camel上下文尝试在销毁它的同时创建丢失的bean,这也会导致错误)。Spring XML中的默认关闭顺序取决于Bean和camelContextSpring XML文件中出现的顺序。因此,为了避免由于错误的关闭顺序而导致的随机错误,请将设置camelContext为在Spring XML文件中的其他任何Bean 之前关闭。这是自Apache Camel 2.13.0起的默认行为。

如果您需要更改此行为(这样就不会在其他bean之前强制关闭Camel上下文),则可以将元素的shutdownEager属性设置camelContextfalse。在这种情况下,您可以使用Spring depends-on属性对关机顺序进行更细粒度的控制。

参数绑定注释

在“基本方法签名”描述的基本参数绑定可能并不总是很方便使用。例如,如果您有一个执行某些数据操作的旧版Java类,则可能要从入站交换中提取数据并将其映射到现有方法签名的参数。对于这种参数绑定,Apache Camel提供了以下几种Java注释:

  • 基本注释
  • 语言注释
  • 继承的注释

基本注释

“基本Bean注释”显示了org.apache.camelJava包中的注释,您可以使用这些注释将消息数据注入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);
    }
}

注意如何将注释与默认约定混合在一起。除了注入带注释的参数外,参数绑定还自动将交换对象注入org.apache.camel.Exchange参数中。

表达语言注释

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

注解描述
@Bean插入Bean表达式。
@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。可以使用spring bean元素实例化ID生成器类,如下所示:

<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接口往往是protectedprivatepackage-only范围。如果尝试在以这种方式限制的实现类上调用方法,则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调用将回退到调用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服务

在将路由部署到Red Hat JBoss Fuse容器的特殊情况下,可以使用bean集成直接调用OSGi服务。例如,假设OSGi容器中的捆绑包之一已导出服务,则org.fusesource.example.HelloWorldOsgiService可以sayHello使用以下bean集成代码来调用该方法:

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服务注册表中查找指定的类名。如果此查找失败,则它将退回到本地Spring DM或蓝图注册表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沙子可可

你的鼓励是我创造的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值