In the
previous
blog entry about Spring-OSGi, we demonstrated how to develop a simple
Spring-OSGi bundle that exposes a Spring bean as an OSGi service. In this installment
we’ll have a look at how you can use OSGi services in a Spring-OSGi bundle.
The sample code used in this blog builds on the sample of the previous blog
and can of course be
downloaded from our site.
For demonstrating the use of an external OSGi services, we’ll use a very simple bundle
exposing an AuditService service that looks like this:
public interface AuditService
{
public void audit(String action);
}
The bundle provides an implementation of this interface that simply prints a message to
system out. You can download this bundle here; of course the
complete bundle source is available in the source
zip. It’s a plain (non-Spring) bundle
that use a standard OSGi activator; don’t bother if you don’t grasp all the details of this bundle
implementation: we’ll just use it as an external service. If you install and start this
bundle in Felix, you’ll see the new service appear when issue the services command.
Binding service to Spring bean
The next thing to do, is of course to adapt our reversestringbean bundle,
to use this service. We change the reverse method to audit-log all
invocations of the reverse method:
private AuditService m_auditService;
public String reverse(String arg) {
if (m_auditService != null) {
m_auditService.audit("reverse(String) called with argument '" + arg + "'");
}
...
}
If you have Felix running with the previous version of
the reversestringbean bundle and made the change outlined above, you can
replace the old bundle with the new version (issue
an update command with the id of the old bundle and the file url of the
new version), and see that the reverse command is still working, even
though it has switched to the new version.
(If you get an error like “Unresolved package in bundle 19: package;
(package=nl.luminis.demo.audit)”, you forgot to install the audit bundle; install it
and try to start the reversestring bundle again.)
The new reversestringbean implementation is not using the audit service yet, because the service
reference is not set. When can make Spring do this, by specifying the service in
the spring.xml file:
<osgi:reference id="externalAuditService"
interface="nl.luminis.demo.audit.AuditService"/>
and use this reference as property value for the bean that was already defined in the
same spring.xml file:
<bean name="reverseBean" class="nl.luminis.demo.reversestring.ReverseStringBean">
<property name="auditService" ref="externalAuditService"/>
</bean>
When you build the bundle with the updated spring.xml file and reload it,
Spring will notice that
the reversestringbean bundle requires an AuditService, and
pass the bean a reference to that service. When you issue the reverse
command again, you’ll see the audit message printed to the console:
-> reverse hello there! AuditService: reverse(String) called with argument ' hello there!' !ereht olleh ->
Service not available
Now it’s time for a little experiment, to see how things behave when the audit service
is not (yet) available. After all, we’re now working in a service oriented environment
and service orientation is all about dynamics.
We’ll restart the Felix framework, with a non-active audit service. To do so, issue the
stop command on the audit service bundle and restart Felix. The framework will remember
each bundle’s last state, so after the restart, the state of the audit service bundle
will be “Resolved” (check this with the ps command). Note that we can’t
uninstall the audit service bundle, as it is the only bundle providing
the AuditService interface, and this interface (its class file)
is needed by the reversestringbean bundle. A better alternative would be
to split the audit service bundle and have the interface and implementation in two
separate bundles: in that case you could now uninstall the implementation bundle and
leave the interface bundle – this is left as an exercise for the reader
.
Issue a reverse again: you’ll get a message (from the command bundle)
telling you that there
is no string service. If you’d study the output of the services command,
you’ll notice that although the reversestringbean bundle is started and
active, it is not publishing any service: not even Spring’s
org.springframework.context.ApplicationContext service we saw earlier.
The explanation for this behaviour is that Spring will not activate the bundle before
all its required dependencies are satisfied. You can verify this by starting the audit
service and trying again: now it works as before.
To continue our little experiment, stop the audit service again, study the output of
the services command and issue the reverse command
again. This time, the reverse string service is still there, even though the bundle’s
required services are not satisfied anymore. So, Spring refuses to start our bean when
the required service(s) (i.e. the audit service) are not there, but it doesn’t bother
to stop the bean when the required service(s) disappear. Personally I dislike this
non-symmetric behaviour and I think it shows that Spring was never designed for dynamic
systems: somehow they can’t really stop an ApplicationContext (and
restart it later) once it is created, I guess.
Optional service dependency
To return to our sample bundle that won’t start when there is not audit service:
this is not what we want. The reversestringbean bundle
should always perform the reverse operation; whether or not the audit service is there
(in a banking example this might not be the case, but for this example, assume
auditting is not crucial).
The solution is in the word “required”: if
we define the audit service to be optional, Spring will start the bundle, even when the
audit service is not there yet.
Let’s try this: add cardinality “0 to 1″ to the service reference definition:
<osgi:reference id="externalAuditService"
interface="nl.luminis.demo.audit.AuditService"
cardinality="0..1" />
and additionally, add the lazy-init="true" to the bean definition. Now
build and reload the bundle and issue another reverse command. To your
surprise (I guess…, at least to mine!), it still won’t work; you’ll get
a ServiceUnavailableException now.
Check the output of the services command: the reverse string
service is published. What’s going on here?
The point is that, in constrast to ‘plain’ OSGi behaviour, Spring provides the bean
with a proxy to the service, instead of a plain Java reference to the service
implementation (as the OSGi framework itself does). Moreover, this proxy is set,
irrespective of whether the service is available. The
ServiceUnavailableException is intentional: you should always be prepared
to get an exception when calling an external service.
I can agree with this last rule: in a dynamic environment, you can never be sure
whether a service is there; even when it was there a few minutes ago, it might be gone
at the very moment you’re using the service. However, in a case like this, I wouldn’t
expect the service proxy to be passed to the bean in the first place, until the service it
represents actually exists. We’ll come back to this in a minute.
Let’s add a try-catch block to the code around calling the service and try again.
This time it works as expected, although it might take a little longer than you expect:
that’s because Spring has a default wait time when it tries to find a service. You can
skip this by adding a attribute timeout="0" to
the <osgi:reference> element in the spring.xml file.
Now, the service behaves as expected: it does its job whether or not the audit service
is available, and it does it without any additional delay.
Dynamic services
There is an alternative to the setter injection, that better suits dynamic
behaviour: callback methods on the bean. As you’d expect from the Spring framework,
your bean doesn’t have to implement an interface in order to be listener; just add two
methods and specify their names in the spring.xml:
<osgi:reference id="externalAuditService"
interface="nl.luminis.demo.audit.AuditService"
cardinality="0..1">
<osgi:listener ref="reverseBean"
bind-method="serviceAdded"
unbind-method="serviceRemoved"/>
</osgi:reference>
Note that the signature of the methods is fixed to this (at least in Spring-OSGi
1.0-m2; it was different in earlier versions, so it might as well differ in newer
versions):
public void serviceAdded(AuditService service, Dictionary d) {
m_auditService = service;
}
public void serviceRemoved(AuditService service, Dictionary d) {
m_auditService = null;
}
Don’t forget to remove the (setter injection) property from the bean definition. If you
add println's to these listener methods, you’ll see that these are indeed
called exactly at the moment the service appears or disappears.
You might wonder what happens when the cardinality is set to something else, e.g. 1 to
many 0 to many. In both cases, the setter that receives the service(s) is supposed to
receive a collection instead of a single service reference. The collection is managed
by Spring behind the scenes: each time you iterate over this collection, it’s contents
may be different, as it reflects the current state of the OSGi framework. Handling of
the case that there is no service is easy, as an iterator over the collection will
return nothing. However, one has still to cover for the
dreadfull ServiceUnavailableException, as a service may disappear between
the moment you get its reference from the collection and call a method on it.
Conclusion
We’ve seen how a normal Spring bean can interact with external OSGi services, and that
by using setter injection, the implementation of the bean doesn’t have to different
from a bean that is used within Spring exclusively. On the other hand, we’ve also seen
that a ServiceUnavailableException is always to be expected and that the
setter injection solution doesn’t combine very well with the dynamic behaviour of a
service oriented framework.
If you want to get it right, you can’t get away with a normal Spring bean
implementation that does not handle ServiceUnavailableException's and is
not prepared to handle missing dependencies. It’s an illusion that you can take an
existing Spring bean implementation and use it unmodified in an OSGi context. It’s
similar to having to deal with remote calls: you can’t hide remoteness from your
application, as remote calls might fail, and your code has to be aware of it – provided
you aim at robust code, of course. The same holds for a dynamic service oriented
environment: you have to deal with disappearing services, you can’t simply ignore
that. That is what makes OSGi different from old fashioned Spring usage that always
assumes that all beans are there, once the system is started.
The good news though, is that with Spring-OSGi, handling dynamic service is possible
and that it can’t be done without implementing specific interfaces. In that sense,
Spring-OSGi is non-intrusive, just like plain Spring is. You can’t ignore the fact that
you’ve to deal with disappearing services, but you can do it in a way that does not
limit reuse of your beans in other (non-OSGi) environments.
Finally a tip for those who want to experiment with the Spring-OSGi framework: enable
Spring (log4j) logging. It’s hard to get it all right the first time, especially with
all the xml-configuration stuff, and sometimes errors are swallowed by the framework,
leaving you in total dispair. Logging can enabled by passing a system property on the
java command line:
-Dlog4j.configuration=file:///your/path/to/spring-log4j.properties
You’ll find a sample log4j.properties file in the source zip. Happy coding!
