Getting started with Spring-OSGi
With the introduction of Spring OSGi,
it becomes relatively easy to split up a Spring application in separate OSGi bundles,
and have a much more modular application architecture, without all the classloading
hassle you may encounter when deploying components in J2EE application servers.
The purpose of this series of blog posts is to get you started with Spring-OSGi. We’ll
develop some simple examples that will get you up and running.
Theoretically, it is possible to use Eclipse for developing OSGi bundles using Spring,
but, at least in my experience, this is a bumpy road with lots of obstacles, and in the
end it does not lead to better understanding of the concepts. Therefore, the samples
presented here will be provided with simple ant build files and do not require special
tools to build or deploy. The complete source code of the samples can be found in this
zip file.
OSGi is (a standard defining) a service oriented framework as well as a number of
standard services that can be deployed on these frameworks. Key concepts are “bundles”
and “services”. A bundle is essentially a normal Java jar file and is the unit of
deployment, i.e. you can install, update and de-install them in a running
framework. Each bundle can publish one or more services, that can be used by other
bundles. Bundles can locate services by querying the OSGi service registry. One of the
most important aspects of OSGi, is that even bundles that use each others services,
have nothing more in common than the service interface.
A complete introduction on OSGi is beyond the scope of this blog post, but there is
enough information online, see for example
OSGi.nl,
www.aqute.biz/OSGi/Tutorial,
www.osgi.org,
felix.apache.org.
Hello…, OSGi
We’ll start with a simple “hello world” like sample, that exposes a trivial Spring bean
as an OSGi service. The next step will be to create a bundle that actually uses this
service, so we can run the demo in an OSGi framework and experiment with this. Also,
these samples will help us to setup the compile time and run time environment
correctly.
As said, we start with a simple, not to say trivial (Spring) bean. Here is its source:
package nl.luminis.demo.reversestring;
public class ReverseStringBean
{
public String reverse(String arg) {
String result = "";
for (int i = arg.length() - 1; i >= 0; i--)
result += arg.charAt(i);
return result;
}
}
To make it Spring bean, we have to add a configuration file that defines the bean. In
plain Spring this would be something like:
<beans xmlns="http://www.springframework.org/schema/beans">
<bean name="reverseBean"
class="nl.luminis.demo.reversestring.impl.ReverseStringImpl"/>
</beans>
(omitting xml namespace stuff for brevity, see the zip for complete source).
Exposing beans as OSGi services is fairly simple in Spring-OSGi. Basically, the only
thing you’ll have to do is to add an <osgi:service> element to the
spring xml configuration file like this:
<?xml version="1.0" encoding="UTF-8"?>
<beans> <!-- (again, namespace declarations omitted) -->
<bean name="reverseBean"
class="nl.luminis.demo.reversestring.ReverseStringImpl"/>
<osgi:service ref="reverseBean"
interface="nl.luminis.demo.string.ReverseString"/>
</beans>
Of course, we need an interface that defines the service we’re exposing, so we define
the ReverseString
interface and add the reverseString() method to it.
To make it a bundle, the last thing to do is add a manifest file with the (OSGi)
required entries:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: ReverseStringBean Bundle-SymbolicName: ReverseStringBean Bundle-Version: 1.0.0
Strictly speaking, not all of these entries are required, but it is common practice to
have at least these entries in the manifest file.
This completes our first bundle. If you’re familair with OSGi you might wonder if our
bundle doesnt’ need a BundleActivator, that provides start()
and stop() lifecyle methods that the OSGi framework can call to start or
stop our bundle. The answer is that it doesn’t need it, because Spring-OSGi works
similar to the OSGi declarative service specification: the framework will inspect the
service declaration and instantiate the service on our bundle’s behalf; instead of
publishing the service ourselves (from within the BundleActivators start method), the
framework “pulls” the service out of our bundle, so to speak.
Setting up the runtime environment
Now it’s time to set up our run time environment. For the demo we will
use Apache Felix 1.0, but you can deploy the
bundle in any OSGi framework, e.g. (Eclipse) Equinox, or Knopflerfish. Download felix
and start it by executing the jar file in its bin directory and enter a profile
name. If you enter ps on the prompt you’ll see that there are only three
bundles active: the system bundle (i.e. the framework ifself) and two bundles that
constitute the shell service that executes the commands your typing at the
prompt. Install our demo bundle by using the install command, passing it a
file url that points to the bundle:
-> install file:///Users/peter/.../reverestringbean.jar
You can start the bundle now, but it won’t register any services yet, as no
one is paying attention to our service declaration in the spring.xml
file. Execute the services command to confirm this.
As the final step in setting up the run time environment, we’ll install the Spring-OSGi
framework. For this demo, we used the milestone 2 release of Spring-OSGi 1.0; newer versions should work also, but of course, small (presumably subtle) differences may occur.
Make sure you download the “-with-dependencies” zip file.
Install these Spring-OSGi bundles from the dist directory:
- spring-osgi-core-1.0-m2.jar
- spring-osgi-extender-1.0-m2.jar
- spring-osgi-io-1.0-m2.jar
These bundles depend on (but do not include) the plain Spring code, so you have to install
a number of bundles (all of which can be found in the lib
directory) that contain the plain spring framework modules:
- spring-core-2.0.5-osgi-m2.jar (from spring-core/2.0.5-osgi-m2/)
- spring-aop-2.0.5-osgi-m2.jar (from spring-aop/2.0.5-osgi-m2/)
- spring-beans-2.0.5-osgi-m2.jar (from spring-beans/2.0.5-osgi-m2/)
- spring-context-2.0.5-osgi-m2.jar (from spring-context/2.0.5-osgi-m2/)
- aopalliance.osgi-1.0-SNAPSHOT.jar (from aopalliance.osgi/1.0-SNAPSHOT/)
- backport-util-concurrent-3.0-SNAPSHOT.jar (from backport-util-concurrent/3.0-SNAPSHOT/)
Additionally, some SLF4J libraries
have to be installed. SLF4J is a logging facade for several logging implementations,
similar to Apache Commons Logging. An important difference with Apache Commons Logging
is that in order to determine what implementation to use, it does not rely on
classloading tricks that can cause a lot of trouble, especially when
used in dynamic environments like an OSGi framework.
From SLF4J you need the following libraries:
- slf4j-api-1.4.3.jar (contains only the SLF4J API)
- slf4j-log4j12-1.4.3.jar (implementation of SFL4J, hard-wired to log4j)
- jcl104-over-slf4j-1.4.3.jar (100% compatible drop-in replacement for Commons Logging)
Just install them as normal bundles, these libraries are OSGi ready. The
jcl104-over-slf4j library contains modified versions
of org.apache.commons.logging.Log.java etc, that redirect to SLF4J. This
will take care of libraries using the Commons Logging API.
As we’ll be using log4j as the logging implementation that does the real work, we’ll
have to install that too. An OSGi compatible version of log4j can be found in the
Spring OSGi package
- log4j.osgi-1.2.13-SNAPSHOT.jar (from src/spring-modules/spring-required-libraries/log4j/target)
but, you’ll have to build it first (with maven). Alternatively, you can download the
file here.
After you’ve installed these bundles, you can start them all. Note that if you try to
start some bundles before you installed them all, you’re likely to get errors about
unresolved packages; to avoid that you would have to install them in the right order.
You’ll probably see some warnings about resources files and namespace handlers that
can’t be found, which you can just ignore.
Now the Spring-OSGi extender is running, it should have discovered and published our
service. Due to a bug in the M2 release (fixed in later releases), it doesn’t yet work this way and you have to
restart our bundle. If you do so, you’ll get some warnings about BeanInfo classes not
being found; ignore these too.
Using the service
If you issue the services command once again, you’ll notice that our simple demo
bundle provides to services: nl.luminis.demo.string.ReverseString
and org.springframework.context.ApplicationContext. The latter is the
well known Spring application context, that is merely provided as OSGi service for
debugging purposes (you shouldn’t use it to access a bean from outside its bundle).
Essentially, we’re ready now, as our first spring enabled bundle is up and running and
publishes its service. However, we can’t be satisfied until we’ve actually seen it do
something “usefull”. To make that happen, we’ll write an extension for the felix
command interpreter, that will call our service. This extension will be a bundle on its
own. As it hooks in into the Felix command shell, this of course is a Felix specific
solution, but similar constructs exist for Equinox and Klopflerfish.
The heart of this command bundle is the execute() method, that tries to
locate the service and calls it:
public void execute(String line, PrintStream out, PrintStream err)
{
// (...)
ServiceReference ref = m_context.getServiceReference("nl.luminis.demo.string.ReverseString");
Object service = (ref != null)? m_context.getService(ref): null;
if (service != null)
try {
out.println(((ReverseString) service).reverse(arg));
}
catch (Exception e) {
err.println("calling reverse string service failed: " + e);
}
else {
err.println("no reverse string service, ref=" + ref);
}
}
See the source.zip for the complete source code.
After installing and starting the command bundle, you’ll notice the new “reverse” command in the
help text:
-> help ... install <URL> [<URL> ...] - install bundle(s). ps [-l | -s | -u] - list installed bundles. resolve [<id> ...] - attempt to resolve the specified bundles. reverse <arg> - reverses a string, using a ReverseString service services [-u] [-a] [<id> ...] - list registered or used services. ...
and issuing this command will finally show that our service works correctly:
-> reverse OSGi is cool! !looc si iGSO ->
This concludes our first working Spring-OSGi sample. It’s not yet a full-blown J2EE
application, but it clearly shows what is needed to make a basic Spring-OSGi bundle
work and have it publish a bean as an OSGi service. In future installments, we’ll have a
look at using external OSGi services in our beans, and experiment with some less
trivial bundles, including a simple web application and database access.
