此内容没有您所选择的语言版本。
2.4. Bean Integration
Overview
Bean integration provides a general purpose mechanism for processing messages using arbitrary Java objects. By inserting a bean reference into a route, you can call an arbitrary method on a Java object, which can then access and modify the incoming exchange. The mechanism that maps an exchange's contents to the parameters and return values of a bean method is known as parameter binding. Parameter binding can use any combination of the following approaches in order to initialize a method's parameters:
- Conventional method signatures — If the method signature conforms to certain conventions, the parameter binding can use Java reflection to determine what parameters to pass.
- Annotations and dependency injection — For a more flexible binding mechanism, employ Java annotations to specify what to inject into the method's arguments. This dependency injection mechanism relies on Spring 2.5 component scanning. Normally, if you are deploying your Apache Camel application into a Spring container, the dependency injection mechanism will work automatically.
- Explicitly specified parameters — You can specify parameters explicitly (either as constants or using the Simple language), at the point where the bean is invoked.
Bean registry
Beans are made accessible through a bean registry, which is a service that enables you to look up beans using either the class name or the bean ID as a key. The way that you create an entry in the bean registry depends on the underlying framework—for example, plain Java, Spring, Guice, or Blueprint. Registry entries are usually created implicitly (for example, when you instantiate a Spring bean in a Spring XML file).
Registry plug-in strategy
Apache Camel implements a plug-in strategy for the bean registry, defining an integration layer for accessing beans which makes the underlying registry implementation transparent. Hence, it is possible to integrate Apache Camel applications with a variety of different bean registries, as shown in Table 2.2, “Registry Plug-Ins”.
Registry Implementation | Camel Component with Registry Plug-In |
---|---|
Spring bean registry | camel-spring |
Guice bean registry | camel-guice |
Blueprint bean registry | camel-blueprint |
OSGi service registry | deployed in OSGi container |
JNDI registry |
Normally, you do not have to worry about configuring bean registries, because the relevant bean registry is automatically installed for you. For example, if you are using the Spring framework to define your routes, the Spring
ApplicationContextRegistry
plug-in is automatically installed in the current CamelContext
instance.
Deployment in an OSGi container is a special case. When an Apache Camel route is deployed into the OSGi container, the
CamelContext
automatically sets up a registry chain for resolving bean instances: the registry chain consists of the OSGi registry, followed by the Blueprint (or Spring) registry.
Accessing a bean created in Java
To process exchange objects using a Java bean (which is a plain old Java object or POJO), use the
bean()
processor, which binds the inbound exchange to a method on the Java object. For example, to process inbound exchanges using the class, MyBeanProcessor
, define a route like the following:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody") .to("file:data/outbound");
Where the
bean()
processor creates an instance of MyBeanProcessor
type and invokes the processBody()
method to process inbound exchanges. This approach is adequate if you only want to access the MyBeanProcessor
instance from a single route. However, if you want to access the same MyBeanProcessor
instance from multiple routes, use the variant of bean()
that takes the Object
type as its first argument. For example:
MyBeanProcessor myBean = new MyBeanProcessor(); from("file:data/inbound") .bean(myBean, "processBody") .to("file:data/outbound"); from("activemq:inboundData") .bean(myBean, "processBody") .to("activemq:outboundData");
Accessing overloaded bean methods
If a bean defines overloaded methods, you can choose which of the overloaded methods to invoke by specifying the method name along with its parameter types. For example, if the
MyBeanBrocessor
class has two overloaded methods, processBody(String)
and processBody(String,String)
, you can invoke the latter overloaded method as follows:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(String,String)") .to("file:data/outbound");
Alternatively, if you want to identify a method by the number of parameters it takes, rather than specifying the type of each parameter explicitly, you can use the wildcard character,
*
. For example, to invoke a method named processBody
that takes two parameters, irrespective of the exact type of the parameters, invoke the bean()
processor as follows:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(*,*)") .to("file:data/outbound");
When specifying the method, you can use either a simple unqualified type name—for example,
processBody(Exchange)
—or a fully qualified type name—for example, processBody(org.apache.camel.Exchange)
.
Note
In the current implementation, the specified type name must be an exact match of the parameter type. Type inheritance is not taken into account.
Specify parameters explicitly
You can specify parameter values explicitly, when you call the bean method. The following simple type values can be passed:
- Boolean:
true
orfalse
. - Numeric:
123
,7
, and so on. - String:
'In single quotes'
or"In double quotes"
. - Null object:
null
.
The following example shows how you can mix explicit parameter values with type specifiers in the same method invocation:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBody(String, 'Sample string value', true, 7)") .to("file:data/outbound");
In the preceding example, the value of the first parameter would presumably be determined by a parameter binding annotation (see the section called “Basic annotations”).
In addition to the simple type values, you can also specify parameter values using the Simple language (Chapter 30, The Simple Language). This means that the full power of the Simple language is available when specifying parameter values. For example, to pass the message body and the value of the
title
header to a bean method:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBodyAndHeader(${body},${header.title})") .to("file:data/outbound");
You can also pass the entire header hash map as a parameter. For example, in the following example, the second method parameter must be declared to be of type
java.util.Map
:
from("file:data/inbound") .bean(MyBeanProcessor.class, "processBodyAndAllHeaders(${body},${header})") .to("file:data/outbound");
Basic method signatures
To bind exchanges to a bean method, you can define a method signature that conforms to certain conventions. In particular, there are two basic conventions for method signatures:
Method signature for processing message bodies
If you want to implement a bean method that accesses or modifies the incoming message body, you must define a method signature that takes a single
String
argument and returns a String
value. For example:
// Java package com.acme; public class MyBeanProcessor { public String processBody(String body) { // Do whatever you like to 'body'... return newBody; } }
Method signature for processing exchanges
For greater flexibility, you can implement a bean method that accesses the incoming exchange. This enables you to access or modify all headers, bodies, and exchange properties. For processing exchanges, the method signature takes a single
org.apache.camel.Exchange
parameter and returns void
. For example:
// 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!"); } }
Accessing a Spring bean from Spring XML
Instead of creating a bean instance in Java, you can create an instance using Spring XML. In fact, this is the only feasible approach if you are defining your routes in XML. To define a bean in XML, use the standard Spring
bean
element. The following example shows how to create an instance of MyBeanProcessor
:
<beans ...> ... <bean id="myBeanId" class="com.acme.MyBeanProcessor"/> </beans>
It is also possible to pass data to the bean's constructor arguments using Spring syntax. For full details of how to use the Spring
bean
element, see The IoC Container from the Spring reference guide.
Where the
beanRef()
processor invokes the MyBeanProcessor.processBody()
method on the specified bean instance. You can also invoke the bean from within a Spring XML route, using the Camel schema's bean
element. For example:
<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>
For a slight efficiency gain, you can set the
cache
option to true
, which avoids looking up the registry every time a bean is used. For example, to enable caching, you can set the cache
attribute on the bean
element as follows:
<bean ref="myBeanId" method="processBody" cache="true"/>
Accessing a Spring bean from Java
When you create an object instance using the Spring
bean
element, you can reference it from Java using the bean's ID (the value of the bean
element's id
attribute). For example, given the bean
element with ID equal to myBeanId
, you can reference the bean in a Java DSL route using the beanRef()
processor, as follows:
from("file:data/inbound").beanRef("myBeanId", "processBody").to("file:data/outbound");
Alternatively, you can reference the Spring bean by injection, using the
@BeanInject
annotation as follows:
// Java import org.apache.camel.@BeanInject; ... public class MyRouteBuilder extends RouteBuilder { @BeanInject("myBeanId") com.acme.MyBeanProcessor bean; public void configure() throws Exception { .. } }
If you omit the bean ID from the
@BeanInject
annotation, Camel looks up the registry by type, but this only works if there is just a single bean of the given type. For example, to look up and inject the bean of com.acme.MyBeanProcessor
type:
@BeanInject com.acme.MyBeanProcessor bean;
Bean shutdown order in Spring XML
For the beans used by a Camel context, the correct shutdown order is usually:
- Shut down the
camelContext
instance, followed by; - Shut down the used beans.
If this shutdown order is reversed, then it could happen that the Camel context tries to access a bean that is already destroyed (either leading directly to an error; or the Camel context tries to create the missing bean while it is being destroyed, which also causes an error). The default shutdown order in Spring XML depends on the order in which the beans and the
camelContext
appear in the Spring XML file. In order to avoid random errors due to incorrect shutdown order, therefore, the camelContext
is configured to shut down before any of the other beans in the Spring XML file. This is the default behaviour since Apache Camel 2.13.0.
If you need to change this behaviour (so that the Camel context is not forced to shut down before the other beans), you can set the
shutdownEager
attribute on the camelContext
element to false
. In this case, you could potentially exercise more fine-grained control over shutdown order using the Spring depends-on
attribute.
Parameter binding annotations
The basic parameter bindings described in the section called “Basic method signatures” might not always be convenient to use. For example, if you have a legacy Java class that performs some data manipulation, you might want to extract data from an inbound exchange and map it to the arguments of an existing method signature. For this kind of parameter binding, Apache Camel provides the following kinds of Java annotation:
Basic annotations
Table 2.3, “Basic Bean Annotations” shows the annotations from the
org.apache.camel
Java package that you can use to inject message data into the arguments of a bean method.
Annotation | Meaning | Parameter? |
---|---|---|
@Attachments | Binds to a list of attachments. | |
@Body | Binds to an inbound message body. | |
@Header | Binds to an inbound message header. | String name of the header. |
@Headers | Binds to a java.util.Map of the inbound message headers. | |
@OutHeaders | Binds to a java.util.Map of the outbound message headers. | |
@Property | Binds to a named exchange property. | String name of the property. |
@Properties | Binds to a java.util.Map of the exchange properties. |
For example, the following class shows you how to use basic annotations to inject message data into the
processExchange()
method arguments.
// 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); } }
Notice how you are able to mix the annotations with the default conventions. As well as injecting the annotated arguments, the parameter binding also automatically injects the exchange object into the
org.apache.camel.Exchange
argument.
Expression language annotations
The expression language annotations provide a powerful mechanism for injecting message data into a bean method's arguments. Using these annotations, you can invoke an arbitrary script, written in the scripting language of your choice, to extract data from an inbound exchange and inject the data into a method argument. Table 2.4, “Expression Language Annotations” shows the annotations from the
org.apache.camel.language
package (and sub-packages, for the non-core annotations) that you can use to inject message data into the arguments of a bean method.
Annotation | Description |
---|---|
@Bean | Injects a Bean expression. |
@Constant | Injects a Constant expression |
@EL | Injects an EL expression. |
@Groovy | Injects a Groovy expression. |
@Header | Injects a Header expression. |
@JavaScript | Injects a JavaScript expression. |
@OGNL | Injects an OGNL expression. |
@PHP | Injects a PHP expression. |
@Python | Injects a Python expression. |
@Ruby | Injects a Ruby expression. |
@Simple | Injects a Simple expression. |
@XPath | Injects an XPath expression. |
@XQuery | Injects an XQuery expression. |
For example, the following class shows you how to use the
@XPath
annotation to extract a username and a password from the body of an incoming message in XML format:
// 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... ... } }
The
@Bean
annotation is a special case, because it enables you to inject the result of invoking a registered bean. For example, to inject a correlation ID into a method argument, you can use the @Bean
annotation to invoke an ID generator class, as follows:
// Java import org.apache.camel.language.*; public class MyBeanProcessor { public void processCorrelatedMsg( @Bean("myCorrIdGenerator") String corrId, @Body String body ) { // Check the user/pass credentials... ... } }
Where the string,
myCorrIdGenerator
, is the bean ID of the ID generator instance. The ID generator class can be instantiated using the spring bean
element, as follows:
<beans ...> ... <bean id="myCorrIdGenerator" class="com.acme.MyIdGenerator"/> </beans>
Where the
MyIdGenerator
class could be defined as follows:
// 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; } }
Notice that you can also use annotations in the referenced bean class,
MyIdGenerator
. The only restriction on the generate()
method signature is that it must return the correct type to inject into the argument annotated by @Bean
. Because the @Bean
annotation does not let you specify a method name, the injection mechanism simply invokes the first method in the referenced bean that has the matching return type.
Note
Some of the language annotations are available in the core component (
@Bean
, @Constant
, @Simple
, and @XPath
). For non-core components, however, you will have to make sure that you load the relevant component. For example, to use the OGNL script, you must load the camel-ognl
component.
Inherited annotations
Parameter binding annotations can be inherited from an interface or from a superclass. For example, if you define a Java interface with a
Header
annotation and a Body
annotation, as follows:
// Java import org.apache.camel.*; public interface MyBeanProcessorIntf { void processExchange( @Header(name="user") String user, @Body String body, Exchange exchange ); }
The overloaded methods defined in the implementation class,
MyBeanProcessor
, now inherit the annotations defined in the base interface, as follows:
// 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 ) { ... } }
Interface implementations
The class that implements a Java interface is often
protected
, private
or in package-only
scope. If you try to invoke a method on an implementation class that is restricted in this way, the bean binding falls back to invoking the corresponding interface method, which is publicly accessible.
For example, consider the following public
BeanIntf
interface:
// Java public interface BeanIntf { void processBodyAndHeader(String body, String title); }
Where the
BeanIntf
interface is implemented by the following protected BeanIntfImpl
class:
// Java protected class BeanIntfImpl implements BeanIntf { void processBodyAndHeader(String body, String title) { ... } }
The following bean invocation would fall back to invoking the public
BeanIntf.processBodyAndHeader
method:
from("file:data/inbound") .bean(BeanIntfImpl.class, "processBodyAndHeader(${body}, ${header.title})") .to("file:data/outbound");
Invoking static methods
Bean integration has the capability to invoke static methods without creating an instance of the associated class. For example, consider the following Java class that defines the static method,
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 } }
You can use bean integration to invoke the static
changeSomething
method, as follows:
from("direct:a")
.bean(MyStaticClass.class, "changeSomething")
.to("mock:a");
Note that, although this syntax looks identical to the invocation of an ordinary function, bean integration exploits Java reflection to identify the method as static and proceeds to invoke the method without instantiating
MyStaticClass
.
Invoking an OSGi service
In the special case where a route is deployed into a Red Hat JBoss Fuse container, it is possible to invoke an OSGi service directly using bean integration. For example, assuming that one of the bundles in the OSGi container has exported the service,
org.fusesource.example.HelloWorldOsgiService
, you could invoke the sayHello
method using the following bean integration code:
from("file:data/inbound") .bean(org.fusesource.example.HelloWorldOsgiService.class, "sayHello") .to("file:data/outbound");
You could also invoke the OSGi service from within a Spring or blueprint XML file, using the bean component, as follows:
<to uri="bean:org.fusesource.example.HelloWorldOsgiService?method=sayHello"/>
The way this works is that Apache Camel sets up a chain of registries when it is deployed in the OSGi container. First of all, it looks up the specified class name in the OSGi service registry; if this lookup fails, it then falls back to the local Spring DM or blueprint registry.