Returning a value from a Bean in Camel
Integration isn’t just about calling web services and pushing messages about. At some point you’ll probably need to generate or process your data, in a way that involves writing some custom Java code. You can have lots of Processor
s in your Camel routes to do this, but it’s a bit smelly.
A cleaner way is to write this business logic in a Java method, and stick it in a class. Camel has lots of ways that you can call out to a Java method when you want to do some advanced processing. Most of them are around using Beans.
But let’s start with the basics. What is a Bean, and how do you call a Bean from Camel, and return its values back to Camel? How do you return a value from a Bean back into your Camel Exchange?
Beans please
Beans are really cool because they allow you to be more modular with your Camel applications. You can write some logic once, and then use it in lots of different places.
Do you have a piece of code that you use for generating a customer ID, or perhaps for fetching some data? Stick it in a Bean. Almost any little fragment of code that you can write in Java, you can probably put in a Bean.
But the Camel syntax for integrating Beans can be a little tricky to get the hang of. How do you get started? And how does the value from your Bean end up back on the Exchange?
The simplest bean
Let’s start simple. There is nothing magic about beans in Camel. They are simply Java classes that contain a method or two.
So I’m going to start with an example of a really simple bean. I’ve included the complete file here, so you can see how simple the class is:
package com.cleverbuilder.examples;
public class RobotBean {
public String whatAmI() {
return "I am a robot, znnnnnnk.";
}
}
That’s it. This simple class has one method, whatAmI()
, which returns a String that sounds almost entirely unlike a robot.
Returning a value into the Body
So you’ve read about Beans, now you want to know how to use the value generated by your Bean method. Is it stored on the Exchange? And how can you use a value from a Bean in your route?
To incorporate this method into a Camel route, we can use the bean()
method in a route definition:
public class MyRouteBuilder extends RouteBuilder {
public void configure() {
from("timer:foo?period=1000")
.bean(RobotBean.class)
.log(">>> ${body}");
}
}
This route looks very simple. In fact, there are some things missing. Notice how I’ve only given the class name, and I’ve not explicitly created an object of the class RobotBean
. I also haven’t given a method name, and I haven’t even said what Camel should do with the return value from the method.
I don’t need to add these things, because they are handled implicitly by Camel. It makes an educated guess as to what you mean:
- I gave only the class name
RobotBean.class
→ Camel assumes that I want to instantiate an object of that class as my bean - I didn’t give a method name → Camel assumes I mean the method
whatAmI()
, because it’s the one and only method inRobotBean
- I didn’t say what to do with the return value from the method → Camel sets the return value from the method in the outbound Exchange Body
So then the output from my log looks like this:
INFO >>> I am a robot, znnnnnnk.
INFO >>> I am a robot, znnnnnnk.
INFO >>> I am a robot, znnnnnnk.
If I wanted to write my route more explicitly, then I could do this:
from("timer:foo?period=1000")
.bean(new RobotBean(), "whatAmI")
.log(">>> ${body}");
Notice how this time I have explicitly created a new instance of the bean using new RobotBean()
. I have also explicitly given the method name that I want to call ("whatAmI"
). Then, the result of the method call is still stored into the Exchange Body.
Returning a value into Headers
Sometimes we want to use a Bean to populate a Header on the Camel Exchange. For example, let’s say that you want to add in some metadata; like a customised timestamp, or maybe an ID number relating to the current message. Again, we can use Beans for this.
Here’s a new Bean with a method that increases a counter by 1 every time it’s called:
package com.cleverbuilder.examples;
public class MoneyBean {
private int counter = 0;
public int count() {
return counter++;
}
}
Now if we want to use the result of the method count()
and store it into a header on the Camel exchange, we can do this in our RouteBuilder
:
from("timer:foo?period=1000")
.setHeader("JellyBeans", method(MoneyBean.class))
.log(">>> Your wealth is: ${headers.JellyBeans} beans");
Notice how this time I’m using the setHeader()
method.
I’m also using a new Camel method called method()
. We use this instead of bean()
when we want the result of a method call to be used somewhere. In this case, I want the result to be used as a parameter to the setHeader()
function.
So, the code above says that I want to get the return value of the method in my MoneyBean
class, and store it in a header called "JellyBeans"
.
When I run the Camel route, I get this in the log:
INFO >>> Your wealth is: 0 beans
INFO >>> Your wealth is: 1 beans
INFO >>> Your wealth is: 2 beans
INFO >>> Your wealth is: 3 beans
INFO >>> Your wealth is: 4 beans
INFO >>> Your wealth is: 5 beans
INFO >>> Your wealth is: 6 beans
You’ll see that the numbers are going up, even though my MoneyBean
class explicitly initialises counter = 0
.
This demonstrates another important thing about Beans.
Beans are stateful and reusable. When you use Beans in Camel this way, Camel will invoke the same instance of the Bean when a new message flows through a route, rather than instantiating a new one.
This means that we use the same Bean between method calls. So when we use counter++
to increment the counter by 1, we can see that we’re actually always referring to the same counter
. And this is reflected in my log output.
When to use Beans?
Beans are great for encapsulating business logic, especially logic that you might want to repeat across several Camel routes, or elsewhere in your Java application.
You can see that you can use the stateful nature of beans to your advantage. Perhaps, you might use a Bean to store a count, or some other stateful piece of information.
Beans are a great way of using the power of the Java language to make your routes more modular, and isolate your custom Java code from your integration code.